问题背景
我们知道android启动后会先到Launcher主界面,但是对于定制开发机来说一般是开机后自启一个app。
从app收到开机广播后一般会有2秒左右的时间应用才起来,所以中间有2-3秒停留在Launcher界面上,这样看起来怪怪的。
分析
首先看为什么会首先启动Laucnher
在AMS执行到systemReady的时候,会启动startHomeActivityLocked的方法
public void systemReady(final Runnable goingCallback) {
....
startHomeActivityLocked(currentUserId, "systemReady");
....
}
这个方法实际是去寻找了CATEGORY_HOME属性的app,我们看一下Luancher的Manifest文件
<activity
android:name="com.android.launcher3.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:theme="@style/LauncherTheme"
android:windowSoftInputMode="adjustPan"
android:configChanges="keyboard|keyboardHidden|navigation"
android:resumeWhilePausing="true"
android:taskAffinity=""
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
</intent-filter>
</activity>
果然有HOME的属性
# 解决办法:
- 在开机广播中加入priority属性,这样能提高收到开机广播的顺序。
<intent-filter android:priority="2147483647">
<!--.接收启动完成的广播-->
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
- 修改Laucnher apk的category,去掉HOME的属性,把自己的app改为HOME,这样开机就直接启动自己的app了。这样做的缺点是不方便用户切到主界面去做其他操作,app得提供去laucnher的入口,同时还得考虑原本home按键的安排;也得考虑一直点击返回按钮后的安排;默认这两个按钮都会返回到HOME属性的app。
还有没有其他办法呢?
我考虑一种解决办法是延长开机动画的时间,让开机动画掩盖Laucnher的启动,当应用app收到开机广播启动后,再停止开机动画,这样就可以无缝切换了。
frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java 中,内部类KeyguardShowDelegate中有个onDrawn回调,可以在回调中去停止动画。具体方案是在回调中延时并查询前台进程,如果起来了就可以停止动画了。
// A delegate class to map a particular invocation with a ShowListener object.
private final class KeyguardShowDelegate extends IKeyguardDrawnCallback.Stub {
private DrawnListener mDrawnListener;
private int cnt_seconds = 0;
KeyguardShowDelegate(DrawnListener drawnListener) {
mDrawnListener = drawnListener;
}
@Override
public void onDrawn() throws RemoteException {
if (DEBUG) Log.v(TAG, "**** SHOWN CALLED ****");
//keyguard drawn complete ,can exit bootanim
try {
cnt_seconds = 0;
while( !isActivityForeground() && cnt_seconds < WAIT_SENSETIME_APP_TIME){
Thread.sleep(1000);
cnt_seconds++;
}
//ensure app has launch completed
Thread.sleep(1000);
} catch (Exception e) {
}
android.os.SystemProperties.set("service.bootanim.exit", "1");
SystemService.stop("bootanim");
android.os.SystemProperties.set("sys.bootvideo.closed", "1");
}
if (mDrawnListener != null) {
mDrawnListener.onDrawn();
}
hideScrim();
}
};
isActivityForeground()根据实际情况自己去实现就好了,这里给个例子。
private boolean isActivityForeground() {
ActivityManager am = (ActivityManager) mContext.getSystemService(
Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> list = am.getRunningTasks(1);
if (list != null && list.size() > 0) {
ComponentName cpn = list.get(0).topActivity;
Log.d(TAG,cpn.getClassName());
if ( cpn.getClassName().startsWith("com.xx.xxx") ) {
Log.d(TAG," app is foreground!");
return true;
}
}
Log.d(TAG," app is background!");
return false;
}