Android HandlerThread FD 优化

前言

我们常说,面向对象的开发语言一切皆对象,任何对象都有他的属性和成员,因此后来拓展出了"类"这个概念,类是对象的抽象,一切都能抽象成为类,但类也是对象,在抽象的过程中先有对象才有类。 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,实际过程中还需要优化。

相关推荐
轻口味13 分钟前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami16 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
古希腊掌管学习的神22 分钟前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法
qystca23 分钟前
洛谷 P1706 全排列问题 C语言
算法
浊酒南街29 分钟前
决策树(理论知识1)
算法·决策树·机器学习
就爱学编程36 分钟前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
学术头条41 分钟前
清华、智谱团队:探索 RLHF 的 scaling laws
人工智能·深度学习·算法·机器学习·语言模型·计算语言学
吃杠碰小鸡1 小时前
lodash常用函数
前端·javascript
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼1 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs