一、问题
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