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,实际过程中还需要优化。

相关推荐
Jay Kay9 分钟前
GVPO:Group Variance Policy Optimization
人工智能·算法·机器学习
Epiphany.55621 分钟前
蓝桥杯备赛题目-----爆破
算法·职场和发展·蓝桥杯
Ticnix23 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人26 分钟前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl30 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅33 分钟前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人41 分钟前
vue3使用jsx语法详解
前端·vue.js
YuTaoShao43 分钟前
【LeetCode 每日一题】1653. 使字符串平衡的最少删除次数——(解法三)DP 空间优化
算法·leetcode·职场和发展
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
茉莉玫瑰花茶1 小时前
C++ 17 详细特性解析(5)
开发语言·c++·算法