標籤:category.home launch startactivity provision settings.global.devi
前段時間做了一個功能,就是鎖定主launch,機器上只能跑我們定義的launch,當時沒注意影響,
最近發現就是因為在AMS中加了這個鎖定過濾條件導致原生Browser無法啟動了,
把我鬱悶的,當時怎麼想都覺得奇怪,這完全不相關的兩件事怎麼會影響到~ 這裡記錄一下
撰寫不易,轉載請註明出處:http://blog.csdn.net/jscese/article/details/41015941
鎖定主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 !代表鍵盤鎖定 導致程式finishreturn 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