前言
我们常说,面向对象的开发语言一切皆对象,任何对象都有他的属性和成员,因此后来拓展出了"类"这个概念,类是对象的抽象,一切都能抽象成为类,但类也是对象,在抽象的过程中先有对象才有类。 Android 作为基于unix-like的操作系统,本自带了操作系统最核心的基因------一切皆文件,这个核心思想是通过I/O的思想简化组件通信模式,一个组件必须以Input、Output或者同时兼备的模式去适配,这也统一后续的系统层通信发展路线,避免了碎片化。
FD在unix-like系统中用于描述组件(如进程)、资源(如文件)、节点(如驱动)等,在Android中,我们使用的进程、线程、文件、MessageQueue、Socket、Binder、Pipe、数据库等均与其有关。
现状
FD释放
在很多情况下,对文件I/O读写必须及时close,当然为了避免被忽略,java官方开发了新的回收方式
java
try (FileInputStream fis = new FileInputStream("a.txt")) {
byte[] b = new byte[1024];
fis.read(b);
}catch (IOException e) {
System.out.println(e.getMessage());
}
同样,只要是Closeable的子类,均可以如此释放,如Socket等。但问题是,I/O操作并非单方法的,比如我们录音过程,如果要添加音效,势必会转移到其他线程处理,防止阻塞导致杂音问题,这个时候显然这个方法是不合适的,机遇上述情况,官方做法是利用CloseGaurd做法是对I/O对象进行监视,在对象finalize发出warning信息,严格模式也是利用这种实现。
java
class Foo {
private final CloseGuard guard = new CloseGuard();
public Foo() {
...;
guard.open("cleanup");
}
public void cleanup() {
guard.close();
...;
if (Build.VERSION.SDK_INT >= 28) {
Reference.reachabilityFence(this);
}
// For full correctness in the absence of a close() call, other methods may also need
// reachabilityFence() calls.
}
protected void finalize() throws Throwable {
try {
// Note that guard could be null if the constructor threw.
if (guard != null) {
guard.warnIfOpen();
}
cleanup();
} finally {
super.finalize();
}
}
}
}
fd 共享
fd共享本质尽可能让同一个资源被多次利用,而不是一个资源打开多次,典型的做法就是Sqlite的操作放到ContentProvider中,以及Okhttp 中的连接池也起到相同的作用,再后来,Http 2.0的多路复用实现,说到这里,作为协程的原始的老祖宗,epoll也是多路复用的实现,通过epoll可以做到监视多种fd。
fd 监控
在Android系统中,没有相应的api来获取应用的fd信息,更早期的Android是可以通过下面方式创建Shell(uid是相同的)进程去获取信息,类似CMD命令行一样,后来就不行了。
java
public Process exec(String[] cmdarray, String[] envp, File dir)
throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
在实际的线上环境中,很难通过Process去监控fd的数量变化,不过读取fd的方法还是有的,如何做到监控呢,实际上可以定时也可以在关闭特定页面时触发检测逻辑,对比前后变化的差异。
typescript
public static String readlink(String name) {
String FD_DIR = "/proc/" + Process.myPid() + "/fd";
String link = FD_DIR + File.separator + name;
Object realpath = BlockGuardHookerOs.invokeOsMethod("realpath", link);
if (realpath instanceof String) {
return (String) realpath;
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
try {
return Os.readlink(link);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
但是,这里仍然是有缺陷的,因为读取的到的fd有些 :node ,很难去识别,比如线程、进程和socket的。
FD OOM
fd是OOM的元凶之一,fd触发的OOM基本上分为三类,一类是FD超过了阈值数量(如线程创建、too open many file),另一类是过多的fd资源申请,导致其他操作无法申请到内存,最后一类是fd申请的资源(如内存)超过了系统所能提供的内存。
FD 优化
FD优化的工具其实并不多,常用的的方法有 及时关闭fd 避免短时间内申请大量的fd资源 (如mmap、socket、thread、进程),按优先级事项申请 控制fd数量,如线程的申请不宜过多,过多的线程并不能发挥cpu的优势,反而线程占用的内存比较高容易造成oom 监控fd,防止fd泄露
HandlerThread FD 优化
本篇的重点是通过共享HandlerThread实现FD数量优化。 在 Android 系统中,创建一个 Looper 通常会创建 2 个 FD,一个是线程的 FD,一个是 MessageQueue 的 FD,前者是系统机制,后者是为了 epoll 队列监控使用。这些 eventfd 由 MessageQueue 创建,每个 MessageQueue 会创建 eventfd 和 epollfd 两个 fd,在 Android 5.x上甚至会创建三个 FD,pipe_in、pipe_out 和 epollfd。
那么,在这里,我们的优化点显然不Looper,因为很难去更改系统底层实现,除非利用bhook这样的工具,那么,我们只能把视线放到HandlerThread上。
我们共享HandlerThread,这样每次只创建Thread,MessageQueue就可以避免创建了。
共享HandlerThread
实现自己的ShareHandlerThread和LightHandler
我们让HandlerThread 共享一个即可,思路就是模仿epoll多路复用机制,epoll多路复用和协程一样,一个监视线程,另一个是处理线程,我们这里让同一个HandlerThread监视我们我们自定义的ShareHandlerThread创建的所以Handler发送过来的Message,一旦到了执行时间,就发送Message到指定的线程执行,执行时到指定的Handler。不过要这么实现的话,Handler我们也用不了,只能使用LightHandler了,但是为了保证执行的先后顺序,我们需要引入执行队列。
为什么这样可以实现,首先Looper机制本身具备先后顺序,因此我们把MessageQueue的消息发送到执行队列,其实影响并不大。
定义ShareHandlerThread
java
public class ShareHandlerThread implements Runnable, Handler.Callback {
final static HandlerThread realThreadHandler = new HandlerThread("realThreadHandler");
static {
realThreadHandler.start();
}
private final Map<String, WeakReference<ShareLooperHandler>> lightHandlerMaps = new ConcurrentHashMap<>();
private final String threadName;
Thread thread = null;
boolean isQuited = false;
boolean isStarted = false;
private static final int MSG_QUIT = Integer.MAX_VALUE;
private AbstractQueue<Message> queue = null; //执行队列
public ShareHandlerThread(String name) {
this.threadName = "ShareHandlerThread#" + name;
}
public void start() {
if (isStarted) {
throw new IllegalThreadStateException();
}
if (this.queue == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.queue = new LinkedTransferQueue<>(); //尽量使用无界队列
} else {
this.queue = new LinkedBlockingQueue<>();
}
}
try {
thread = new Thread(this.threadName);
thread.start();
isStarted = true;
} catch (Throwable e) {
e.printStackTrace();
throw e;
}
}
//方便继承LightHandler时创建Handler
public ShareLooperHandler createHandlerFromClass(Class<? extends ShareLooperHandler> KlassLightHandler) {
return createHandlerFromClass(KlassLightHandler, null);
}
public ShareLooperHandler createHandlerFromClass(Class<? extends ShareLooperHandler> KlassLightHandler, Handler.Callback callback) {
try {
Constructor<? extends ShareLooperHandler> constructor = KlassLightHandler.getDeclaredConstructor(Handler.class, Handler.Callback.class, ShareHandlerThread.class, String.class);
Handler handler = new Handler(realThreadHandler.getLooper(), this);
return constructor.newInstance(handler, callback, this, generateHandlerName());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
@RequiresApi(api = Build.VERSION_CODES.P)
public ShareLooperHandler createAsyncHandlerFromClass(Class<? extends ShareLooperHandler> KlassLightHandler) {
return createAsyncHandlerFromClass(KlassLightHandler, null);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public ShareLooperHandler createAsyncHandlerFromClass(Class<? extends ShareLooperHandler> KlassLightHandler, Handler.Callback callback) {
try {
Constructor<? extends ShareLooperHandler> constructor = KlassLightHandler.getDeclaredConstructor(Handler.class, Handler.Callback.class, ShareHandlerThread.class, String.class);
Handler handler = Handler.createAsync(realThreadHandler.getLooper(), callback);
return constructor.newInstance(handler, callback, this, generateHandlerName());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
public ShareLooperHandler createHandler() {
return createHandler(null);
}
public ShareLooperHandler createHandler(Handler.Callback callback) {
ShareLooperHandler lightHandler = new ShareLooperHandler(new Handler(realThreadHandler.getLooper(), this), callback, this, generateHandlerName());
return lightHandler;
}
@NonNull
private String generateHandlerName() {
return threadName + "#" + SystemClock.uptimeMillis();
}
@RequiresApi(api = Build.VERSION_CODES.P)
public ShareLooperHandler createAsyncHandler() {
return createAsyncHandler(null);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public ShareLooperHandler createAsyncHandler(Handler.Callback callback) {
ShareLooperHandler lightHandler = new ShareLooperHandler(Handler.createAsync(realThreadHandler.getLooper(), this), callback, this, generateHandlerName());
return lightHandler;
}
@Override
public void run() {
while (!isQuited) {
Message msg = queue.poll();
if (msg == null) {
continue;
}
if (msg.what == Integer.MIN_VALUE) {
break;
}
doHandleMessage(msg);
msg.recycle();
}
}
private void doHandleMessage(Message msg) {
Bundle data = msg.getData();
String keyName = (String) data.get(ShareLooperHandler.KEY);
WeakReference<ShareLooperHandler> weakLightHandler = lightHandlerMaps.get(keyName);
if (weakLightHandler != null) {
ShareLooperHandler lightHandler = weakLightHandler.get();
if (lightHandler == null) {
return;
}
lightHandler.dispatchMessage(msg);
}
}
//利用Callback,优先拿到消息,然后塞进执行队列
@Override
public boolean handleMessage(Message msg) {
Message message = Message.obtain();
message.copyFrom(msg);
queue.offer(message);
//这里返回true,不要和共享looper关联
return true;
}
public void add(String handlerName, ShareLooperHandler lightHandler) {
lightHandlerMaps.put(handlerName,new WeakReference<ShareLooperHandler>(lightHandler));
}
//退出ShareHandlerThread
public void quit(){
Message message = Message.obtain();
message.what = MSG_QUIT;
queue.offer(message);
}
}
LightHandler实现
映射
这里有些难点,难点主要是如何让realHandlerThread中的消息执行时找到当前的LightHandler,为此我们这里做了个影射关系,在上面ShareHandlerThread中我们创建LightHandler给name的目的就是为了生成映射关系
java
private void attachLightHandler(Message msg) {
Bundle data = msg.getData();
if(data != null){
data.putString(KEY, handlerName);
}
msg.obj = proxyProtect(msg.obj);
}
最终映射关系会知道ShareHandlerThread总的LightHandler
java
private final Map<String, WeakReference<ShareLooperHandler>> lightHandlerMaps = new ConcurrentHashMap<>();
token 问题
我们通常使用Message的时候会有个msg.obj甚至删除消息的时候会传token,但是如何避免一个消息被不同的Handler发送之后,只删除目标Handler的消息呢?如下面的操作,怎么避免呢?
java
removeCallback(Runnable r,Object token);
这里我们对java 和Looper 比较熟悉的话,里面判断 token是不是同一个使用了 "==",因此我们这里就可以利用hashCode机制来进行处理,我们对原始的object添加一个保护。
java
static class $ {
String name;
Object token;
public $(Object token, String name) {
this.token = token;
this.name = name;
}
public static $ protect(Object token, String name) {
return new $(token, name);
}
public Object getToken() {
return token;
}
@Override
public boolean equals(Object token) {
return this.token == token;
}
@Override
public int hashCode() {
//解决不同handler删除消息的问题
return Math.abs(this.token.hashCode()) + Math.abs(name.hashCode());
}
}
这样remove的时候,我们就能安全操作
java
public final void removeCallbacks(Runnable r, Object token) {
token = proxyProtect(token);
this.handler.removeCallbacks(r, token);
}
getLooper问题
我们自定义的ShareHandlerThread显然是没有Looper的,因此我们最简单的办法就是把ShareHandlerThread对象返回出去即可。
java
public Object getLooper(){
return fakeLooper;
}
跨进程问题
如果你对Handler熟悉的话,就知道Handler是可以跨进程的,但LightHandler实现跨进程显然要修改Message或者在外面封装一下,考虑到Handler跨进程使用的机会并不多,因此我们暂时不做太多的处理。
完整代码
java
public class LightHandler {
private final Handler handler;
private final Handler.Callback callback;
private final String handlerName;
final static String KEY = "android.share.handler.thread.light.handlerName";
private ShareHandlerThread fakeLooper;
public LightHandler(Handler handler, Handler.Callback callback, ShareHandlerThread fakeLooper, String name) {
this.handler = handler;
this.callback = callback;
this.fakeLooper = fakeLooper;
this.handlerName = name;
fakeLooper.add(handlerName,this);
}
public final boolean sendMessage(Message msg) {
attachLightHandler(msg);
return this.handler.sendMessage(msg);
}
private void attachLightHandler(Message msg) {
Bundle data = msg.getData();
if(data != null){
data.putString(KEY, handlerName);
}
msg.obj = proxyProtect(msg.obj);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
attachLightHandler(msg);
return this.handler.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
attachLightHandler(msg);
return this.handler.sendMessageAtFrontOfQueue(msg);
}
public final boolean sendEmptyMessage(int what) {
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
Message msg = Message.obtain();
msg.what = what;
return this.handler.sendMessageAtTime(msg, uptimeMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
attachLightHandler(msg);
return this.handler.sendMessageAtTime(msg, uptimeMillis);
}
// obtain方法
public final Message obtainMessage() {
return Message.obtain(this.handler);
}
public final Message obtainMessage(int what) {
return Message.obtain(this.handler, what);
}
public final Message obtainMessage(int what, Object obj) {
return Message.obtain(this.handler, what, obj);
}
public final Message obtainMessage(int what, int arg1, int arg2) {
return Message.obtain(this.handler, what, arg1, arg2);
}
public final Message obtainMessage(int what, int arg1, int arg2, Object obj) {
return Message.obtain(this.handler, what, arg1, arg2, obj);
}
//runable方法
private Message getPostMessage(Runnable r) {
Message m = Message.obtain(this.handler, r);
m.setTarget(null);
return m;
}
private Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain(this.handler, r);
m.setTarget(null);
m.obj = proxyProtect(token);
return m;
}
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postAtFrontOfQueue(Runnable r) {
return sendMessageAtFrontOfQueue(getPostMessage(r));
}
public final boolean postAtTime(Runnable r, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final boolean postAtTime(
Runnable r, Object token, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
public final boolean postDelayed(Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean postDelayed(
Runnable r, Object token, long delayMillis) {
return sendMessageDelayed(getPostMessage(r, token), delayMillis);
}
//remove类方法
/**
* Remove any pending posts of messages with code 'what' that are in the
* message queue.
*/
public final void removeMessages(int what) {
removeMessages(what, null);
}
/**
* Remove any pending posts of messages with code 'what' and whose obj is
* 'object' that are in the message queue. If <var>object</var> is null,
* all messages will be removed.
*/
public final void removeMessages(int what, Object object) {
object = proxyProtect(object);
this.handler.removeMessages(what, object);
}
public final void removeCallbacksAndMessages(Object token) {
token = proxyProtect(token);
this.handler.removeCallbacksAndMessages(token);
}
private Object proxyProtect(Object object) {
if (object != null && !(object instanceof $)) {
object = $.protect(object, this.handlerName);
}
return object;
}
public final boolean hasMessages(int what) {
return this.handler.hasMessages(what);
}
public final boolean hasMessages(int what, Object object) {
return this.handler.hasMessages(what, proxyProtect(object));
}
public final boolean hasCallbacks(Runnable r) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return this.handler.hasCallbacks(r);
}
return false;
}
/**
* Remove any pending posts of Runnable r that are in the message queue.
*/
public final void removeCallbacks(Runnable r) {
removeCallbacks(r, null);
}
public final void removeCallbacks(Runnable r, Object token) {
token = proxyProtect(token);
this.handler.removeCallbacks(r, token);
}
public Object getLooper(){
return fakeLooper;
}
public void dispatchMessage(Message msg) {
try {
Handler.Callback callback = getCallback();
unload(msg);
if (callback != null) {
if (callback.handleMessage(msg)) {
return;
}
}
msg.setTarget(null);
handleMessage(msg,this);
}catch (Throwable e){
e.printStackTrace();
throw e;
}finally {
msg.recycle();
}
}
public void handleMessage(Message msg, LightHandler lightHandler) {
}
private void unload(Message msg) {
if(msg.obj instanceof $){
msg.obj = (($) msg.obj).getToken();
}
Bundle data = msg.peekData();
if(data != null){
data.remove(KEY);
}
}
public Handler.Callback getCallback() {
return callback;
}
static class $ {
String name;
Object token;
public $(Object token, String name) {
this.token = token;
this.name = name;
}
public static $ protect(Object token, String name) {
return new $(token, name);
}
public Object getToken() {
return token;
}
@Override
public boolean equals(Object token) {
return this.token == token;
}
@Override
public int hashCode() {
//解决不同handler删除消息的问题
return Math.abs(this.token.hashCode()) + Math.abs(name.hashCode());
}
}
}
总结
通过上述实现,我们就能实现共享Looper的的情况下,实现了消息的发送和到指定线程的处理,当然,这个是个简单的demo,实际过程中还需要优化。