PostHub异步执行模型

Posted by 深几码谈 on August 31, 2020

一、问题

ActivityManagerService 和 ConnectivityService 都关注property:’service.launcher.boot.ready’的状态,当该property为1的时候,各自执行一些动作。而未达到这个状态的时候,需要记住这个动作。

二、方案1:轮询

通过android.os.Handler#sendMessageDelayed()来延迟一段时间来查询,如果还不满足就继续延迟一段时间来查询。这就是在Handler中来轮询实现。

2.1 实例 ConnectivityService

的代码如下:

    String launcherBooted = SystemProperties.get("service.launcher.boot.ready", "0");
    // ...
    if ("0".equals(launcherBooted)) {
        // ...
        sendStickyBroadcastDelayed(intent, 2500);
        return;
    }

sendStickyBroadcastDelayed

    private void sendStickyBroadcastDelayed(Intent intent, int delayMs) {
        if (delayMs <= 0) {
            sendStickyBroadcast(intent);
        } else {
            // ...
            mHandler.sendMessageDelayed(mHandler.obtainMessage(
                    EVENT_SEND_STICKY_BROADCAST_INTENT, intent), delayMs);
        }
    }

2.2 实例 ActivityManagerService

代码比 ConnectivityService 要复杂一些,就不完全展示了。其中核心:

    static final int MSG_LAUNCHER_READY = 10000;
    static final int MSG_BOOT_COMPLETE_MIN = 10001;
    static final int MSG_BOOT_COMPLETE_MAX = 10002;
    static final int SEND_BOOT_COMPLETE_MIN = 6 * 1000;
    static final int SEND_BOOT_COMPLETE_MAX = 90 * 1000;

    mHandler.sendEmptyMessageDelayed(MSG_BOOT_COMPLETE_MIN, SEND_BOOT_COMPLETE_MIN);
    mHandler.sendEmptyMessageDelayed(MSG_BOOT_COMPLETE_MAX, SEND_BOOT_COMPLETE_MAX);
    sendBootCompleted(false);

MSG_LAUNCHER_READY,MSG_BOOT_COMPLETE_MAX

    case MSG_BOOT_COMPLETE_MIN:
        mCanSendBootComplete = true;
        break;

    case MSG_LAUNCHER_READY:
        // 继续查询,此时 force 为 false,则继续延迟查询。
        sendBootCompleted(false);
        break;

    case MSG_BOOT_COMPLETE_MAX:
        mCanSendBootComplete = true;
         // SEND_BOOT_COMPLETE_MAX 时间到,则会处理 MSG_BOOT_COMPLETE_MAX;
         // 后续设置 property=1,并且不再延续查询。
        sendBootCompleted(true);
        break;

MSG_LAUNCHER_READY 继续延迟查询,MSG_BOOT_COMPLETE_MAX 意味这退出轮询,执行 Actions。

    final void sendBootCompleted(boolean force) {
        String launcherBooted = SystemProperties.get("service.launcher.boot.ready", "0");

        if (!force) {
            if ("0".equals(launcherBooted) || !mCanSendBootComplete || !mLoadDBComplete) {
                // 延迟1秒,继续查询(handleMessage(MSG_LAUNCHER_READY))。
                mHandler.sendEmptyMessageDelayed(MSG_LAUNCHER_READY, 1000);
                return;
            } else {
                mHandler.removeMessages(MSG_BOOT_COMPLETE_MAX);
            }
        } else {
            if ("0".equals(launcherBooted)) {
                // SEND_BOOT_COMPLETE_MAX 时间后,将执行到此。
                SystemProperties.set("service.launcher.boot.ready", "1");
            }
            if (!mCanSendBootComplete) {
                mCanSendBootComplete = true;
            }
            mHandler.removeMessages(MSG_LAUNCHER_READY);
        }


    // Actions
    // ...
    }

2.3 简化

MSG_BOOT_COMPLETE_MIN 在6秒后 mCanSendBootComplete = true; MSG_LAUNCHER_READY 每1秒查询一次,直到 property为1。 MSG_BOOT_COMPLETE_MAX 90秒后,设置 property为1。

2.4 时间线(单位:秒)

  • 1-5 sendBootCompleted(false)
  • 6 mCanSendBootComplete = true,无后续; sendBootCompleted(false)
  • 7-89 sendBootCompleted(false)
  • 90 sendBootCompleted(true)

2.5 注解

这里 property:’service.launcher.boot.ready’ 代表了一个状态,其为1时,ActivityManagerService 和 ConnectivityService,各自执行一些动作。而它状态变化的条件是 MSG_BOOT_COMPLETE_MAX 时间结束。等价于 MSG_BOOT_COMPLETE_MAX 时间后,其关注者执行各自的动作,时间没到时,其它关注者订阅该事件。

三、方案2:PostHub

该场景适合于观察者模式,但这次笔者打算提炼一个适合于 Android 的 oneshot 的方案,即:PostHub。包括PostHub,PostDo,及客户代码。

3.1 PostDo

封装客户相关的 Handler 及 Runnable(具体动作由客户代码定义),在特定时机执行客户的Runnable;

package letv.util;

import android.os.Handler;
import android.util.Log;

public class PostDo {
    final String TAG = PostDo.class.getSimpleName();

    String name;

    Handler mHandler;
    Runnable mRunnable;

    public PostDo(String name, Handler handler, Runnable runnable) {
        this.name = name;
        this.mHandler = handler;
        this.mRunnable = runnable;
    }

    public void setRunnable(Runnable runnable) {
        this.mRunnable = runnable;
    }

    @Override
    public String toString() {
        return super.toString() + "{" +
                "name: " + name + "\n" +
                "handler: " + mHandler + "\n" +
                "runnable: " + mRunnable + "\n" +
                "}";
    }

    public void intercept() {
        Log.i(TAG, "intercept()");
        Log.i(TAG, "this: " + this.toString());
        if (mHandler != null) {
            mHandler.post(mRunnable);
        }
    }
}

3.2 PostHub

其中 PostHub 代替 property:’service.launcher.boot.ready’,用内部 done(boolean) 来表示。超时使用 android.os.Handler#postDelayed 来表达,然后 PostHub 内部有一个 Runnable(),在这个时机来触发所有 PostDo 设定好的 Runnable。

package letv.util;

import android.os.Handler;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class PostHub implements Runnable {
    private static final String TAG = PostHub.class.getSimpleName();
    static PostHub sInstance;
    private boolean mDone;

    public static PostHub getInstance() {
        if (sInstance == null) {
            sInstance = new PostHub();
        }
        return sInstance;
    }

    List<letv.util.PostDo> mPostDoList = new ArrayList<letv.util.PostDo>();

    public void add(letv.util.PostDo postDo) {
        Log.d(TAG, "add(" + postDo + ")");
        Log.d(TAG, "mPostDoList: " + mPostDoList.size());
        if (!mPostDoList.contains(postDo)) {
            mPostDoList.add(postDo);
        }
    }

    public void postDelayed(Handler handler, int delayInMills) {
        Log.d(TAG, "postDelayed(" + handler + ", " + delayInMills + ")");
        handler.postDelayed(this, delayInMills);
    }

    public boolean isDone() {
        return mDone;
    }

    void done() {
        mDone = true;
    }

    SjLogGen sjLogGen = new SjLogGen(TAG);

    @Override
    public void run() {
        SjLog sjLog = sjLogGen.build("run()");
        sjLog.in();
        {
            done();
            for (letv.util.PostDo postDo : mPostDoList) {
                postDo.intercept();
            }
        }
        sjLog.out();
    }
}

3.3 客户代码

ActivityManagerService

    {
        final String PostDoName = "Delay-BOOT_COMPLETE@" + this.getClass().getSimpleName();
        final PostHub postHub = PostHub.getInstance();

        final PostDo postDo = new PostDo(PostDoName, mHandler, null);
        final Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Log.i(TAG_CLI, PostDoName + ", " + "run()");

                // Actions
                sendBootCompleted();
            }
        };
        postDo.setRunnable(runnable);
        postHub.add(postDo);

        // postDelay, oneshot.
        postHub.postDelayed(mHandler, DELAY_BOOT_COMPLETE_TIMEOUT);
    }

ConnectivityService

    final PostHub postHub = PostHub.getInstance();
    Log.i(TAG_CLI, "PostHub.done: " + postHub.isDone());
    if (!postHub.isDone()) {
        sendStvStickyBroadcast(intent);
        {
            final String postDoName = "Delay-BOOT_COMPLETE@" + this.getClass().getSimpleName();

            final PostDo postDo = new PostDo(postDoName, mHandler, null);
            final Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    Log.i(TAG_CLI, postDoName + ", " + "run()");
                    // Actions
                    sendStickyBroadcast(intent);
                }
            };
            postDo.setRunnable(runnable);
            postHub.add(postDo);
        }
        return;
    }

3.4 运行

root@GraceHD:/ # logcat -v threadtime -s PostDo:V PostHub:V PostHub-Cli:V *:S
--------- beginning of main
--------- beginning of system
01-01 08:00:29.018  3457  3524 I PostHub-Cli: sendStickyBroadcast(Intent { act=android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE (has extras) })
01-01 08:00:29.053  3457  3524 I PostHub-Cli: PostHub.done: false
01-01 08:00:29.055  3457  3524 D PostHub : add(letv.util.PostDo@e2aa25b{name: Delay-BOOT_COMPLETE@ConnectivityService
01-01 08:00:29.055  3457  3524 D PostHub : handler: Handler (com.android.server.ConnectivityService$InternalHandler) {33010af8}
01-01 08:00:29.055  3457  3524 D PostHub : runnable: com.android.server.ConnectivityService$4@3c996bd1
01-01 08:00:29.055  3457  3524 D PostHub : })
01-01 08:00:29.056  3457  3524 D PostHub : mPostDoList: 0
01-01 08:00:29.056  3457  3524 I PostHub-Cli: sendStickyBroadcast(Intent { act=android.net.conn.CONNECTIVITY_CHANGE (has extras) })
01-01 08:00:29.056  3457  3524 I PostHub-Cli: PostHub.done: false
01-01 08:00:30.164  3457  3524 D PostHub : add(letv.util.PostDo@1a7beb41{name: Delay-BOOT_COMPLETE@ConnectivityService
01-01 08:00:30.164  3457  3524 D PostHub : handler: Handler (com.android.server.ConnectivityService$InternalHandler) {33010af8}
01-01 08:00:30.164  3457  3524 D PostHub : runnable: com.android.server.ConnectivityService$4@3db728e6
01-01 08:00:30.164  3457  3524 D PostHub : })
01-01 08:00:30.164  3457  3524 D PostHub : mPostDoList: 1
09-25 14:29:47.904  3457  3474 I PostHub-Cli: => finishBooting()
09-25 14:29:49.328  3457  3479 I PostHub-Cli: => finishBooting()
09-25 14:29:49.340  3457  3479 D PostHub : add(letv.util.PostDo@1b6bb3e6{name: Delay-BOOT_COMPLETE@ActivityManagerService
09-25 14:29:49.340  3457  3479 D PostHub : handler: Handler (com.android.server.am.ActivityManagerService$MainHandler) {78fdd27}
09-25 14:29:49.340  3457  3479 D PostHub : runnable: com.android.server.am.ActivityManagerService$9@cd3bd4
09-25 14:29:49.340  3457  3479 D PostHub : })
09-25 14:29:49.340  3457  3479 D PostHub : mPostDoList: 2
09-25 14:29:49.341  3457  3479 D PostHub : postDelayed(Handler (com.android.server.am.ActivityManagerService$MainHandler) {78fdd27}, 90000)
09-25 14:29:49.341  3457  3479 I PostHub-Cli: <- finishBooting()
09-25 14:31:19.347  3457  3474 I PostHub : => run()
09-25 14:31:19.347  3457  3474 I PostDo  : intercept()
09-25 14:31:19.347  3457  3474 I PostDo  : this: letv.util.PostDo@e2aa25b{name: Delay-BOOT_COMPLETE@ConnectivityService
09-25 14:31:19.347  3457  3474 I PostDo  : handler: Handler (com.android.server.ConnectivityService$InternalHandler) {33010af8}
09-25 14:31:19.347  3457  3474 I PostDo  : runnable: com.android.server.ConnectivityService$4@3c996bd1
09-25 14:31:19.347  3457  3474 I PostDo  : }
09-25 14:31:19.347  3457  3474 I PostDo  : intercept()
09-25 14:31:19.347  3457  3474 I PostDo  : this: letv.util.PostDo@1a7beb41{name: Delay-BOOT_COMPLETE@ConnectivityService
09-25 14:31:19.347  3457  3474 I PostDo  : handler: Handler (com.android.server.ConnectivityService$InternalHandler) {33010af8}
09-25 14:31:19.347  3457  3474 I PostDo  : runnable: com.android.server.ConnectivityService$4@3db728e6
09-25 14:31:19.347  3457  3474 I PostDo  : }
09-25 14:31:19.347  3457  3474 I PostDo  : intercept()
09-25 14:31:19.347  3457  3474 I PostDo  : this: letv.util.PostDo@1b6bb3e6{name: Delay-BOOT_COMPLETE@ActivityManagerService
09-25 14:31:19.347  3457  3474 I PostDo  : handler: Handler (com.android.server.am.ActivityManagerService$MainHandler) {78fdd27}
09-25 14:31:19.347  3457  3474 I PostDo  : runnable: com.android.server.am.ActivityManagerService$9@cd3bd4
09-25 14:31:19.347  3457  3474 I PostDo  : }
09-25 14:31:19.348  3457  3474 I PostHub : <- run()
09-25 14:31:19.348  3457  3474 I PostHub-Cli: Delay-BOOT_COMPLETE@ActivityManagerService, run()
09-25 14:31:19.348  3457  3474 I PostHub-Cli: => sendBootCompleted()
09-25 14:31:19.350  3457  3524 I PostHub-Cli: Delay-BOOT_COMPLETE@ConnectivityService, run()
09-25 14:31:19.350  3457  3524 I PostHub-Cli: sendStickyBroadcast(Intent { act=android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE (has extras) })
09-25 14:31:19.350  3457  3524 I PostHub-Cli: PostHub.done: true
09-25 14:31:19.357  3457  3524 I PostHub-Cli: Delay-BOOT_COMPLETE@ConnectivityService, run()
09-25 14:31:19.358  3457  3524 I PostHub-Cli: sendStickyBroadcast(Intent { act=android.net.conn.CONNECTIVITY_CHANGE (has extras) })
09-25 14:31:19.358  3457  3524 I PostHub-Cli: PostHub.done: true
09-25 14:31:19.358  3457  3474 I PostHub-Cli: <- sendBootCompleted()

在该情形中,第一次利用 ActivityManagerService 的 handler.postDelay() 来启动一次 Runnable(PostHub实例),然后 PostDo 启动各自的 PostDo#mHandler.post() 启动各自的 Runnable 实例,这些 Runnable 由客户代码定义。

四、总结

4.1 适用场景

  • 同一个进程 完整的说是同一个进程不同的线程之间,多对象响应事件,来协作,独立执行各自动作。
  • 一次执行 目前定义的是只执行一次,如果需要多次执行需要改造。
  • 利用现有的 Handler/Looper 对于 ServiceThread HandlerThread 的实例来讲正好是现成的,如果不是,需要客户代码来负责提供和维护。
  • 无锁 尽量不使用带锁的设计,否则将引入潜在的阻塞问题,增加运行期的复杂性。

4.2 推广

在设计中使用到 android.os.Handler#postDelayed android.os.Handler#post 方法来实现异步执行(无锁),用 letv.util.PostHub#mDone 来表达状态,借助客户的 handler 来实现超时后触发事件。 总体设计简单可靠,值得推广,只要是同一个进程中即可,包括 system_server 以及普通应用进程。

4.3 补充

不同的情形,使用不同的事件机制。property 适用于跨越 native, jvm 进程,如果仅仅是 jvm 进程,用广播就可以。如果在同一个进程内,可以使用代价更小的机制,比如PostHub。总之,代价最小。


| 微博 weibo.com/liuxk99