前段时间做了一个功能,就是锁定主launch,机器上只能跑我们定义的launch,当时没注意影响,
最近发现就是因为在AMS中加了这个锁定过滤条件导致原生Browser无法启动了,
把我郁闷的,当时怎么想都觉得奇怪,这完全不相关的两件事怎么会影响到~ 这里记录一下
锁定主launch
启动android系统launch的过程原理可参考Android——启动过程详解 中的HOME启动,
这个网上的方法比较多,最常见的就是修改原生的 CATEGORY_HOME 变量或者添加一个新的变量来做筛选条件,需要修改源码中出现CATEGORY_HOME的地方.
比如我在/frameworks/base/core/java/android/content/Intent.java 中把CATEGORY_HOME 改为:
public static final String CATEGORY_HOME = "android.intent.category.JSCESE_HOME";
这样的话在源码中其它地方使用的 CATEGORY_HOME 变量都可以不动,整体编译需要使用 make update-api更新api.
然后只需要把我们想要当作launch的apk的AndroidManifest.xml文件中:
<category android:name="android.intent.category.HOME" />
//改为
<category android:name="android.intent.category.JSCESE_HOME" />
这样一来只有定义了andorid.intent.category.JSCESE_HOME 这个category的launch 才能被系统当作HOME launch启动起来!
貌似一看没有任何问题~
原生Browser启动
像上面介绍的那样可以正常锁定住我们指定的launch,但是问题来了~,当启动Browser的时候直接就退出来了,启动其它的apk,或者使用其它的浏览器都能正常运行,
我一查发现退出原因是在BrowserActivity.java中的:
private boolean shouldIgnoreIntents() {
...
ignore |= mKeyguardManager.inKeyguardRestrictedInputMode(); //这里为 true !代表键盘锁定 导致程序finish
return ignore;
}
最后一路跟踪调试,WindowManagerService——>PhoneWindowManager——>KeyguardViewMediator中的:
/**
* Given the state of the keyguard, is the input restricted?
* Input is restricted when the keyguard is showing, or when the keyguard
* was suppressed by an app that disabled the keyguard or we haven't been provisioned yet.
*/
public boolean isInputRestricted() {
Log.d(TAG,"jscese display mShowing =="+mShowing+" "+mNeedToReshowWhenReenabled+" "+!mUpdateMonitor.isDeviceProvisioned());
return mShowing || mNeedToReshowWhenReenabled || !mUpdateMonitor.isDeviceProvisioned();
}
我发现 !mUpdateMonitor.isDeviceProvisioned()==true !
追根溯源到KeyguardUpdateMonitor.java中的:
mDeviceProvisioned = Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
问题就出在这里,这里得到的是 0 ~
我果断的到setting.db中去查了下global表中的device_provisioned:
select * from global where name='device_provisioned';
果然在db中的值为 0 ! 查询方法可参考:Android——sqlite3 基本命令操作
源码全局一搜,发现在/packages/apps/Provision/src/com/android/provision/DefaultActivity.java 中有这么一行:
Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
马蛋,我瞬间明白了前因后果,我看这个DefaultActivity.java 也眼熟... 原来是漏掉了这个真正的 系统第一 apk ~
引导provision
这个引导apk一般被很多人忽视,这次我也忽视掉了,最开始接触android的时候还知道这东西,久没接触给忘掉了~
这个apk是作为android第一引导apk的,AndroidManifest.xml中和一般的launch一样定义的:
<intent-filter android:priority="1">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
所以其实在 AMS中的 startHomeActivityLocked 启动HOME activity的时候,这个 provision的 DefaultActivity也是被查询出来的,
而且因为优先级=1 高于 一般的launch,而被直接启动,不算作多HOME launch.
看下这个 DefaultActivity.java:
/**
* Application that sets the provisioned bit, like SetupWizard does.
*/
public class DefaultActivity extends Activity {
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Add a persistent setting to allow other apps to know the device has been provisioned.
Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1); // 这里设置了一个状态值 ,也就是上面说到的 device_provisioned,代表升级完成,设备准备好了~
// remove this activity from the package manager.
PackageManager pm = getPackageManager();
ComponentName name = new ComponentName(this, DefaultActivity.class);
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, //这个是直接从packagemanager中 把自己剔除,也就是说 这个activity 只启动这么一次
PackageManager.DONT_KILL_APP);
// terminate the activity.
finish();
}
}
这个引导除了设置准备完成标志,把自己屏蔽掉之外没做什么其它操作,
DEVICE_PROVISIONED:
上面有说道设置进了setting.db的global表里面,这个标志很重要,像上面就是因为键盘检测这个标志还为 0 ,导致键盘是锁定的状态,无法使用Browser,
另外还有 锁屏程序不会锁屏;对HOME key的处理也不同;电话也是打不进来的
另外从PackageManager中剔除的操作保存在 /data/system目录下的packages.xml中
另外注释上有看到 这个activity 是用来做设置向导的~所以一些第一次起机需要做的一些操作可以加在这里让其启动:
Intent intent = new Intent();
ComponentName componentName = new ComponentName("com.xxx.xxx", "com.xxx.xxx.yourAcitvity");
intent.setComponent(componentName);
startactivity(intent);
也有直接在这里直接查询HOME的 ,然后跳转指定的 launch~
像我上面说的那种锁定launch,就是因为启动时漏掉了这个引导provision,所以无法启动Browser,而且还有其它的功能隐患!
解决办法就是把这个Provision的AndroidManifest.xml 也:
<category android:name="android.intent.category.HOME" />
//改为
<category android:name="android.intent.category.JSCESE_HOME" />
Intent隐式启动,Activity启动选择框
当使用intent隐式启动activity时,都是通过PackageManager 查询满足条件的activity,如果有不只一项满足,那么就会弹出一个dialog,让用户选择!
上面说到的多个HOME launch状态下,想要锁定我自己的launch也是出于这个原因!
大体记录一下流程:
一般启动一个activity时都是context.startActivity(intent)之类的,初步调用到/frameworks/base/core/java/android/app/ContextImpl.java:
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1, options);
}
调用到同目录下的Instrumentation.java中的execStartActivity,再调用ActivityManagerNative.getDefault().startActivity(*);
很明显接下来就是调用到AMS中的startAcitivity
public final int startActivity(IApplicationThread caller,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags,
String profileFile, ParcelFileDescriptor profileFd, Bundle options) {
return startActivityAsUser(caller, intent, resolvedType, resultTo, resultWho, requestCode,
startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId());
}
public final int startActivityAsUser(IApplicationThread caller,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags,
String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivity");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, true, "startActivity", null);
return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
null, null, options, userId);
}
调用到/frameworks/base/services/java/com/android/server/am/ActivityStack.java中:
final int startActivityMayWait(IApplicationThread caller, int callingUid,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, String profileFile,
ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
Bundle options, int userId) {
...
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
aInfo.packageName);
newIntent.setFlags(intent.getFlags());
newIntent.setClassName("android",
HeavyWeightSwitcherActivity.class.getName()); //可以看到符合上面的一系列判定条件之后,发现如果是多个activity满足条件,在这里就先启动了一个选择activity
intent = newIntent;
...
int res = startActivityLocked(caller, intent, resolvedType,
aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,
startFlags, options, componentSpecified, null);
...
return res;
}
可以看下/frameworks/base/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java:
/**
* This activity is displayed when the system attempts to start an Intent for
* which there is more than one matching activity, allowing the user to decide
* which to go to. It is not normally used directly by application developers.
*/
public class HeavyWeightSwitcherActivity extends Activity {
/** The PendingIntent of the new activity being launched. */
public static final String KEY_INTENT = "intent";
/** Set if the caller is requesting a result. */
public static final String KEY_HAS_RESULT = "has_result";
/** Package of current heavy-weight app. */
public static final String KEY_CUR_APP = "cur_app";
/** Task that current heavy-weight activity is running in. */
public static final String KEY_CUR_TASK = "cur_task";
/** Package of newly requested heavy-weight app. */
public static final String KEY_NEW_APP = "new_app";
注释写的很明白~