首页 > 代码库 > Android——锁定launch - 原生Browser启动 -引导provision
Android——锁定launch - 原生Browser启动 -引导provision
前段时间做了一个功能,就是锁定主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";
注释写的很明白~
Android——锁定launch - 原生Browser启动 -引导provision