APK安裝流程系列文章整體內(nèi)容如下:
- APK安裝流程詳解0——前言
- APK安裝流程詳解1——有關(guān)"安裝ing"的實體類概述
- APK安裝流程詳解2——PackageManager簡介
- APK安裝流程詳解3——PackageManager與PackageManagerService
- APK安裝流程詳解4——安裝中關(guān)于so庫的那些事
- APK安裝流程詳解5——PackageInstallerService和Installer
- APK安裝流程詳解6——PackageManagerService啟動前奏
- APK安裝流程詳解7——PackageManagerService的啟動流程(上)
- APK安裝流程詳解8——PackageManagerService的啟動流程(下)
- APK安裝流程詳解9——PackageParser解析APK(上)
- APK安裝流程詳解10——PackageParser解析APK(下)
- APK安裝流程詳解11——普通應(yīng)用安裝簡介
- APK安裝流程詳解12——PackageManagerService中的新安裝流程上(拷貝)
- APK安裝流程詳解13——PackageManagerService中的新安裝流程下(裝載)
- APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補(bǔ)充
- APK安裝流程詳解15——PMS中的新安裝流程下(裝載)補(bǔ)充
- APK安裝流程詳解16——Android包管理總結(jié)
本片文章的主要內(nèi)容如下:
- 1、Settings類簡介
- 2、SystemConfig類簡介
- 3、ServiceThread類與PackageHandler類簡介
- 4、PackageManagerServcie的systemReady方法簡介
- 5、PackageManagerServcie的performBootDexOpt方法簡介
- 6、PackageManagerService啟動的預(yù)熱
- 7、關(guān)于shared UID相關(guān)問題
- 8、PackageManagerService方法名中"LI"、"LP"、"LPw"、"LPr"的含義
- 9、@GuardBy、@SystemApi、@hide Android注解簡介
一、Settings類簡介
由于在后面講解PackageManager流程啟動的時候會 涉及到Setting類,我們就先預(yù)熱下
Settings.java源碼地址
1、重要成員變量
- private final File mSettingsFilename:代表的是"/data/system/packages.xml"文件
- private final File mBackupSettingsFilename:代表的是"/data/system/packages_backup/xml"文件,這個文件不一定存在,如果存在,因為他是備份文件,如果它存在,則說明上次更新packages.xml文件出錯了。
- private final File mPackageListFilename:代表的是"/data/system/pcakages.list"文件
- final ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>():是一個ArrayMap的結(jié)構(gòu),key是包名,value是PackageSetting。PackageSetting主要包含了一個APP的基本信息,如安裝位置,lib位置等信息。
- final ArrayMap<String, SharedUserSetting> mSharedUsers =new ArrayMap<String, SharedUserSetting>():表示的是一個ArrayMap,key是類似于"android.ui.system"這樣的字段,在Android中每一個應(yīng)用都有一個UID,兩個相同的UID的應(yīng)用可以運(yùn)行在同一個進(jìn)程中,所以為了讓兩個應(yīng)用運(yùn)行在一個進(jìn)程中,往往會在AndroidManifest.xml文件中設(shè)置shareUserId這個屬性,這個屬性就是一個字符串,但是我們知道Linux系統(tǒng)中一個uid是一個整型,所以為了將字符串和整形對應(yīng)起來,就有了的ShareUserSetting類型,剛才說key是shareUserId這個屬性的值,那么值就是SharedUserSetting類型了,ShareUserdSetting中除了name(其實就是key),uid對應(yīng)Linux系統(tǒng)的uid,還有一個列表字段,記錄了當(dāng)前系統(tǒng)中有相同的shareUserId的應(yīng)用。
- final ArrayMap<String, BasePermission> mPermissions=new ArrayMap<String, BasePermission>():代表的是主要保存的是"/system/etc/permissions/platform.xml"中的permission標(biāo)簽內(nèi)容,因為Android系統(tǒng)是基于Linux系統(tǒng),所以也有用戶組的概念,在platform.xml中定義了一些權(quán)限,并且制定了哪些用戶具有這些權(quán)限,一旦一個應(yīng)用屬于某一個用戶組,那么它就擁有了這個用戶組的所有權(quán)限
- final ArrayMap<String, BasePermission> mPermissionTrees=new ArrayMap<String, BasePermission>():代表的是"packages.xml"文件的permission-trees標(biāo)簽
2、先來看下它的構(gòu)造函數(shù)
代碼在Settings.java 342行
Settings(Object lock) {
this(Environment.getDataDirectory(), lock);
}
Settings(File dataDir, Object lock) {
mLock = lock;
// 創(chuàng)建RuntimePermissionPersistence對象,其中 RuntimePermissionPersistence是Settings的內(nèi)部類
mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
//初始化mSystemDir
mSystemDir = new File(dataDir, "system");
mSystemDir.mkdirs();
// 設(shè)置權(quán)限
FileUtils.setPermissions(mSystemDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
// 初始化mSettingsFilename、mBackupSettingsFilename、mPackageListFilename,請注意他們的文件目錄
mSettingsFilename = new File(mSystemDir, "packages.xml");
mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
mPackageListFilename = new File(mSystemDir, "packages.list");
FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
// Deprecated: Needed for migration
// 通過注釋 ,知道下面兩個文件目錄是不推薦使用的
mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}
- 我們看到Settings的構(gòu)造函數(shù)主要工作就是創(chuàng)建了系統(tǒng)文件夾,一些包管理的文件:
- packages.xml和package-backup.xml為一組,用于描述系統(tǒng)所安裝的Package信息,其中packages-backup.xml是package.xml的備份。
- packages-list用于描述系統(tǒng)中存在的所有非系統(tǒng)自帶的apk信息及UID大于10000的apk。當(dāng)這些APK有變化時,PackageManagerService就會更新該文件
3、重要方法SharedUserSetting addSharedUserLPw(String, int, int, int)方法
添加特殊用戶名稱和UID并關(guān)聯(lián)
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
// 獲取SharedUserSetting對象,其中mSharedUsers是ArrayMap<String, SharedUserSetting> 來存儲的
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
if (s.userId == uid) {
return s;
}
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate shared user, keeping first: " + name);
return null;
}
//如果在ArrayMap中沒有找到對應(yīng)的SharedUserSetting
s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
s.userId = uid;
if (addUserIdLPw(uid, s, name)) {
// 將 name和SharedUserSetting對象保存到mShareUsers的一個ArrayMap中
mSharedUsers.put(name, s);
return s;
}
return null;
}
通過上面代碼可知,Setting中有一個mSharedUsers成員,該成員存儲的是字符串和SharedUserSetting鍵值對,通過addSharedUserLPw(String,int,int, int)方法將name和SharedUserSetting對象加到mSharedUsers列表中,這里我們主要關(guān)心兩點,一是ShareUserSetting的架構(gòu)、二是ShareUserSetting的作用:
3.1、舉例說明
在SystemUI的AndroidManifest.xml里面,如下所示
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.systemui"
coreApp="true"
android:sharedUserId="android.uid.system"
android:process="system">
在xml中,聲明了一個名為android:sharedUserId的屬性,其值為"android.uid.system"。shareUserId看起來和UID有關(guān),確實如此,它有兩個作用:
- 1 兩個或者多個聲明了同一種shareUserIds的APK可以共享彼此的數(shù)據(jù),并且可以運(yùn)行在同一個進(jìn)程中。
- 2 通過聲明特定的shareUserId,該APK所在進(jìn)程將被賦予指定的UID。例如,本例中的SystemUI聲明了system的uid,運(yùn)行SystemUI進(jìn)程就可共享system用戶所對應(yīng)的權(quán)限(實際上就是將該進(jìn)程的uid設(shè)置為system的uid)
除了在AndroidManifest.xml中聲明shareUserId外,APK在編譯時還必須使用對應(yīng)的證書進(jìn)行簽名。例如本例中的SystemUI,在Android.mk中需要額外聲明LOCAL_CERTIFICATE := platform,如此,才可獲得指定的UID。
還需要有三點需要引起大家注意:
- xml中的sharedUserId屬性指定了一個字符串,它是UID的字符串描述,故對應(yīng)數(shù)據(jù)結(jié)構(gòu)中也應(yīng)該有一個字符串,這樣就把代碼和XML中的屬性聯(lián)系起來。
- 在Linxu系統(tǒng)中,真正的UID是一個整形,所以該數(shù)據(jù)結(jié)構(gòu)中必然有一個整形變量。
- 多個Package可以聲明同一個shareUserId,因此數(shù)據(jù)結(jié)構(gòu)必然會保存那些聲明了相同的sharedUserId的Package的某些信息。
3.2、ShareUserSetting的架構(gòu)
PackageManagerService的構(gòu)造函數(shù)中創(chuàng)建了一個Settings的實例mSettings,mSettings中有三個成員變量mSharedUsers,mUserIds,mOtherUserIds。addSharedUserLPw()方法都涉及到了這三個成員變量,看到PackageManagerService中創(chuàng)建了Settings的實例對象mSettings,addSharedUserLPw()函數(shù)是對mSetting成員變量mShareUsers進(jìn)行操作。mShareUsers是以String類型的name為key,ShareUserSetting為value的ArrayMap,SharedUserSetting中的成員變量packages時一個PackageSetting類型的ArraySet;PackageSetting繼承自PackageSettingBase,我們可以看到PackageSetting保存著package的多種信息
3.3、SharedUserId作用
我們在系統(tǒng)應(yīng)用的AndroidManifest.xml中
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
coreApp="true"
package="com.android.settings"
android:sharedUserId="android.uid.system"
android:versionCode="1"
android:versionName="1.0">
</manifest>
這里的android:shareUserId的屬性對應(yīng)著ShareUserSetting中的name,上面的addSharedUserLPw函數(shù)將shareUserId name和一個int 類型的UID對應(yīng)起來,UID的定義在Process.java中
//系統(tǒng)進(jìn)程使用的UID/GID,值為1000
publicstatic final int SYSTEM_UID = 1000;
//Phone進(jìn)程使用的UID/GID,值為1001
publicstatic final int PHONE_UID = 1001;
//shell進(jìn)程使用的UID/GID,值為2000
publicstatic final int SHELL_UID = 2000;
//使用LOG的進(jìn)程所在的組的UID/GID為1007
publicstatic final int LOG_UID = 1007;
//供WIF相關(guān)進(jìn)程使用的UID/GID為1010
publicstatic final int WIFI_UID = 1010;
//mediaserver進(jìn)程使用的UID/GID為1013
publicstatic final int MEDIA_UID = 1013;
//設(shè)置能讀寫SD卡的進(jìn)程的GID為1015
publicstatic final int SDCARD_RW_GID = 1015;
//NFC相關(guān)的進(jìn)程的UID/GID為1025
publicstatic final int NFC_UID = 1025;
//有權(quán)限讀寫內(nèi)部存儲的進(jìn)程的GID為1023
publicstatic final int MEDIA_RW_GID = 1023;
//第一個應(yīng)用Package的起始UID為10000
publicstatic final int FIRST_APPLICATION_UID = 10000;
//系統(tǒng)所支持的最大的應(yīng)用Package的UID為99999
publicstatic final int LAST_APPLICATION_UID = 99999;
//和藍(lán)牙相關(guān)的進(jìn)程的GID為2000
publicstatic final int BLUETOOTH_GID = 2000;
shareUserId與UID相關(guān),作用是:
- 1、兩個或多個APK或者進(jìn)程聲明了一種shareUserId的APK可以共享彼此的數(shù)據(jù),并可以運(yùn)行在同一進(jìn)程中(相當(dāng)于進(jìn)程是系統(tǒng)的用戶,某些進(jìn)程可以歸為同一用戶使用,相當(dāng)于Linux系統(tǒng)的GroupId)。
- 2、通過聲明特定的sharedUserId,該APK所在的進(jìn)程將被賦予指定的UID,將被賦予該UID特定的權(quán)限。
3.4、總結(jié)
一圖勝千言
二、SystemConfig類簡介
SystemConfig.java源碼地址
SystemCofig代表系統(tǒng)配置信息
1、SystemConfig的構(gòu)造函數(shù)
代碼在SystemConfig.java 147行
SystemConfig() {
// Read configuration from system
// 從系統(tǒng)中讀取配置信息 目錄是etc/sysconfig
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "sysconfig"), false);
// Read configuration from the old permissions dir
// 從系統(tǒng)中讀取配置信息 目錄是etc/permissions
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "permissions"), false);
// Only read features from OEM config
//從oem 目錄下讀取sysconfig和permission目錄下的文件
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "sysconfig"), true);
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "permissions"), true);
}
SystemConfig的構(gòu)造函數(shù)中主要通過readPermission函數(shù)將對應(yīng)目錄下的xml文件中定義的各個節(jié)點讀取出來保存到SystemConfig成員變量中。第一個參數(shù)對應(yīng)文件目錄;第二個參數(shù)是從xml文件中解析內(nèi)容的范圍,比如對于system目錄,是全部解析:ALLOW_ALL。我們到system/etc/permission目錄下可以看到很多xml類的配置文件,如下:
android.hardware.bluetooth.xml
android.hardware.bluetooth_le.xml
android.hardware.camera.flash-autofocus.xml
android.hardware.camera.front.xml
...
platform.xml
...
這些都是編譯時從framwork指定位置拷貝過來的(framework/native/data/etc/)
readPermission(File,int)方法內(nèi)部調(diào)用readPermissionsFromXml()方法來解析xml中各個節(jié)點,其中xml涉及到的標(biāo)簽內(nèi)容有feature、library、permission、assign-permission等,這些標(biāo)簽的內(nèi)容都將解析出來保存到SystemConfig的對應(yīng)數(shù)據(jù)結(jié)構(gòu)的全局變量中以便以后查詢管理。festure標(biāo)簽用來描述設(shè)備是否支持硬件特性;library用于指定系統(tǒng)庫,當(dāng)應(yīng)用程序運(yùn)行時,系統(tǒng)會為進(jìn)程加載一些必要的庫,permission用于將permission與gid關(guān)聯(lián),系統(tǒng)會為進(jìn)程加載一些必要庫,permission用于將permission與gid關(guān)聯(lián),assign-permission將system中描述的permission與uid關(guān)聯(lián)等等;其中解析permission調(diào)用了readPermission()方法進(jìn)行權(quán)限的解析
總結(jié)下SystemConfig()初始化時解析的xml文件節(jié)點及對應(yīng)的全局變量
如下圖
三、ServiceThread類與PackageHandler類簡介
1、ServiceThread類
ServiceThread.java源碼地址
代碼很簡單如下:
/**
* Special handler thread that we create for system services that require their own loopers.
*/
public class ServiceThread extends HandlerThread {
private static final String TAG = "ServiceThread";
private final boolean mAllowIo;
public ServiceThread(String name, int priority, boolean allowIo) {
super(name, priority);
mAllowIo = allowIo;
}
@Override
public void run() {
Process.setCanSelfBackground(false);
// For debug builds, log event loop stalls to dropbox for analysis.
if (!mAllowIo && StrictMode.conditionallyEnableDebugLogging()) {
Slog.i(TAG, "Enabled StrictMode logging for " + getName() + " looper.");
}
super.run();
}
}
通過注釋我們知道,這個類其實是System server創(chuàng)建的線程,由于它是繼承自HandlerThread,所以它有一個自己的Looper。
2、PackageHandler類
PackageHandler是PackageManagerService的內(nèi)部類。
它的代碼比較多,我就不粘貼了,代碼在PackageManagerService.java 1099行
PackageManageService啟動的時候會將PackageHandler和ServiceThread進(jìn)行綁定。ServiceThread其實就是PackageManageService的工作線程,PackageManageService的各種操作都將利用PackageHandler分發(fā)到HandlerThread去處理。
四、PackageManagerServcie的systemReady方法簡介
代碼在PackageManagerService.java 14658行
@Override
public void systemReady() {
mSystemReady = true;
// Read the compatibilty setting when the system is ready.
boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt(
mContext.getContentResolver(),
android.provider.Settings.Global.COMPATIBILITY_MODE, 1) == 1;
PackageParser.setCompatibilityModeEnabled(compatibilityModeEnabled);
if (DEBUG_SETTINGS) {
Log.d(TAG, "compatibility mode:" + compatibilityModeEnabled);
}
int[] grantPermissionsUserIds = EMPTY_INT_ARRAY;
synchronized (mPackages) {
// Verify that all of the preferred activity components actually
// exist. It is possible for applications to be updated and at
// that point remove a previously declared activity component that
// had been set as a preferred activity. We try to clean this up
// the next time we encounter that preferred activity, but it is
// possible for the user flow to never be able to return to that
// situation so here we do a sanity check to make sure we haven't
// left any junk around.
ArrayList<PreferredActivity> removed = new ArrayList<PreferredActivity>();
for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i);
removed.clear();
for (PreferredActivity pa : pir.filterSet()) {
if (mActivities.mActivities.get(pa.mPref.mComponent) == null) {
removed.add(pa);
}
}
if (removed.size() > 0) {
for (int r=0; r<removed.size(); r++) {
PreferredActivity pa = removed.get(r);
Slog.w(TAG, "Removing dangling preferred activity: "
+ pa.mPref.mComponent);
pir.removeFilter(pa);
}
mSettings.writePackageRestrictionsLPr(
mSettings.mPreferredActivities.keyAt(i));
}
}
for (int userId : UserManagerService.getInstance().getUserIds()) {
if (!mSettings.areDefaultRuntimePermissionsGrantedLPr(userId)) {
grantPermissionsUserIds = ArrayUtils.appendInt(
grantPermissionsUserIds, userId);
}
}
}
// 多用戶服務(wù)
sUserManager.systemReady();
// If we upgraded grant all default permissions before kicking off.
// 升級所有已獲的默認(rèn)權(quán)限
for (int userId : grantPermissionsUserIds) {
mDefaultPermissionPolicy.grantDefaultPermissions(userId);
}
// Kick off any messages waiting for system ready
// 處理所有等待系統(tǒng)準(zhǔn)備就緒的消息
if (mPostSystemReadyMessages != null) {
for (Message msg : mPostSystemReadyMessages) {
msg.sendToTarget();
}
mPostSystemReadyMessages = null;
}
// Watch for external volumes that come and go over time
// 觀察外部存儲設(shè)備
final StorageManager storage = mContext.getSystemService(StorageManager.class);
storage.registerListener(mStorageListener);
mInstallerService.systemReady();
mPackageDexOptimizer.systemReady();
MountServiceInternal mountServiceInternal = LocalServices.getService(
MountServiceInternal.class);
mountServiceInternal.addExternalStoragePolicy(
new MountServiceInternal.ExternalStorageMountPolicy() {
@Override
public int getMountMode(int uid, String packageName) {
if (Process.isIsolated(uid)) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
if (checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_READ;
}
return Zygote.MOUNT_EXTERNAL_WRITE;
}
@Override
public boolean hasExternalStorage(int uid, String packageName) {
return true;
}
});
}
五、PackageManagerServcie的performBootDexOpt方法簡介
代碼在PackageManagerService.java 6024行
@Override
public void performBootDexOpt() {
// 確保只有system或者 root uid有權(quán)限執(zhí)行該方法
enforceSystemOrRoot("Only the system can request dexopt be performed");
// Before everything else, see whether we need to fstrim.
try {
// 運(yùn)行在同一個進(jìn)程,此處拿到MountServcie服務(wù)端
IMountService ms = PackageHelper.getMountService();
if (ms != null) {
//處于更新狀態(tài),則執(zhí)行fstrim
final boolean isUpgrade = isUpgrade();
boolean doTrim = isUpgrade;
if (doTrim) {
Slog.w(TAG, "Running disk maintenance immediately due to system update");
} else {
// interval默認(rèn)值為3天
final long interval = android.provider.Settings.Global.getLong(
mContext.getContentResolver(),
android.provider.Settings.Global.FSTRIM_MANDATORY_INTERVAL,
DEFAULT_MANDATORY_FSTRIM_INTERVAL);
if (interval > 0) {
final long timeSinceLast = System.currentTimeMillis() - ms.lastMaintenance();
if (timeSinceLast > interval) {
// 距離他上次fstrim時間超過3天,則執(zhí)行fstrim
doTrim = true;
Slog.w(TAG, "No disk maintenance in " + timeSinceLast
+ "; running immediately");
}
}
}
if (doTrim) {
if (!isFirstBoot()) {
try {
ActivityManagerNative.getDefault().showBootMessage(
mContext.getResources().getString(
R.string.android_upgrading_fstrim), true);
} catch (RemoteException e) {
}
}
// 此處ms是指MountServcie,該過程發(fā)送消息H_FSTRIM給handler,然后再向vold發(fā)送fstrim命令
ms.runMaintenance();
}
} else {
Slog.e(TAG, "Mount service unavailable!");
}
} catch (RemoteException e) {
// Can't happen; MountService is local
}
final ArraySet<PackageParser.Package> pkgs;
synchronized (mPackages) {
// 清空延遲執(zhí)行dexopt操作的app,獲取dexopt操作的app集合
pkgs = mPackageDexOptimizer.clearDeferredDexOptPackages();
}
if (pkgs != null) {
// Sort apps by importance for dexopt ordering. Important apps are given more priority
// in case the device runs out of space.
ArrayList<PackageParser.Package> sortedPkgs = new ArrayList<PackageParser.Package>();
// Give priority to core apps.
for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
PackageParser.Package pkg = it.next();
// 將pkgs中的核心app添加到sortedPkgs
if (pkg.coreApp) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Adding core app " + sortedPkgs.size() + ": " + pkg.packageName);
}
sortedPkgs.add(pkg);
it.remove();
}
}
// Give priority to system apps that listen for pre boot complete.
// 獲取監(jiān)聽PRE_BOOT_COMPLETE的系統(tǒng)app集合
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
ArraySet<String> pkgNames = getPackageNamesForIntent(intent);
for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
PackageParser.Package pkg = it.next();
// 將pkg監(jiān)聽PRE_BOOT_COMPLETE的app添加到sortedPkgs
if (pkgNames.contains(pkg.packageName)) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Adding pre boot system app " + sortedPkgs.size() + ": " + pkg.packageName);
}
sortedPkgs.add(pkg);
it.remove();
}
}
// Filter out packages that aren't recently used.
// 獲取pkgs中最近一周使用過的app
filterRecentlyUsedApps(pkgs);
// Add all remaining apps.
for (PackageParser.Package pkg : pkgs) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Adding app " + sortedPkgs.size() + ": " + pkg.packageName);
}
// 將最近一周的app添加到sortedPkgs
sortedPkgs.add(pkg);
}
// If we want to be lazy, filter everything that wasn't recently used.
if (mLazyDexOpt) {
filterRecentlyUsedApps(sortedPkgs);
}
int i = 0;
int total = sortedPkgs.size();
File dataDir = Environment.getDataDirectory();
long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
if (lowThreshold == 0) {
throw new IllegalStateException("Invalid low memory threshold");
}
for (PackageParser.Package pkg : sortedPkgs) {
long usableSpace = dataDir.getUsableSpace();
if (usableSpace < lowThreshold) {
Log.w(TAG, "Not running dexopt on remaining apps due to low memory: " + usableSpace);
break;
}
performBootDexOpt(pkg, ++i, total);
}
}
}
本方法的主要功能:
當(dāng)處于升級或者三天未執(zhí)行fstrim,則本地會是否會執(zhí)行fstrim操作,對sortedPkgs中的app執(zhí)行dexopt優(yōu)化,其中包含:
- mDeferredDexOpt中的核心app
- mDeferredDexopt中監(jiān)聽PRE_BOOT_COMPLETE的app
- mDeferredDexOpt中最近一周使用過的app
上面方法中涉及了2個核心方法:
- private void filterRecentlyUsedApps(Collection<PackageParser.Package>) 方法
- private void performBootDexOpt(PackageParser.Package, int, int)方法
下面我們就來依次看下:
1、filterRecentlyUsedApps(Collection<PackageParser.Package>) 方法
代碼在PackageManagerService.java 6133行
private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs) {
// Filter out packages that aren't recently used.
//
// The exception is first boot of a non-eng device (aka !mLazyDexOpt), which
// should do a full dexopt.
if (mLazyDexOpt || (!isFirstBoot() && mPackageUsage.isHistoricalPackageUsageAvailable())) {
int total = pkgs.size();
int skipped = 0;
long now = System.currentTimeMillis();
for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
PackageParser.Package pkg = i.next();
// 過濾最近使用過的app
long then = pkg.mLastPackageUsageTimeInMills;
if (then + mDexOptLRUThresholdInMills < now) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
((then == 0) ? "never" : new Date(then)));
}
i.remove();
skipped++;
}
}
if (DEBUG_DEXOPT) {
Log.i(TAG, "Skipped optimizing " + skipped + " of " + total);
}
}
}
這個方法主要是就是過濾掉最近使用過的app,過濾條件就是篩選mDexOptLRUThresholdInMills時間。不同版本的條件不同:
- 對于Eng版本,則只會對30分鐘之內(nèi)使用過的app執(zhí)行優(yōu)化
- 對于用戶版本,則會將用戶最近以后組使用過的app執(zhí)行優(yōu)化
2、performBootDexOpt(PackageParser.Package, int, int)方法
代碼在PackageManagerService.java 6176行
private void performBootDexOpt(PackageParser.Package pkg, int curr, int total) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Optimizing app " + curr + " of " + total + ": " + pkg.packageName);
}
if (!isFirstBoot()) {
try {
ActivityManagerNative.getDefault().showBootMessage(
mContext.getResources().getString(R.string.android_upgrading_apk,
curr, total), true);
} catch (RemoteException e) {
}
}
PackageParser.Package p = pkg;
synchronized (mInstallLock) {
mPackageDexOptimizer.performDexOpt(p, null /* instruction sets */,
false /* force dex */, false /* defer */, true /* include dependencies */,
false /* boot complete */);
}
}
我們看到這個方法其實內(nèi)部很簡單,主要就是調(diào)用mPackageDexOptimizer的performDexOpt方法,那我們就來看下這個方法的具體實現(xiàn)
2.1、performBootDexOpt(PackageParser.Package, int, int)方法
代碼在PackageDexOptimizer.java 73行
/**
* Performs dexopt on all code paths and libraries of the specified package for specified
* instruction sets.
*
* <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on
* {@link PackageManagerService#mInstallLock}.
*/
int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
boolean forceDex, boolean defer, boolean inclDependencies, boolean bootComplete) {
ArraySet<String> done;
if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
done = new ArraySet<String>();
done.add(pkg.packageName);
} else {
done = null;
}
synchronized (mPackageManagerService.mInstallLock) {
final boolean useLock = mSystemReady;
if (useLock) {
mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
mDexoptWakeLock.acquire();
}
try {
return performDexOptLI(pkg, instructionSets, forceDex, defer, bootComplete, done);
} finally {
if (useLock) {
mDexoptWakeLock.release();
}
}
}
}
2.2、performBootDexOpt(PackageParser.Package, int, int)方法
代碼在PackageDexOptimizer.java 73行
/**
* Performs dexopt on all code paths and libraries of the specified package for specified
* instruction sets.
*
* <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on
* {@link PackageManagerService#mInstallLock}.
*/
int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
boolean forceDex, boolean defer, boolean inclDependencies, boolean bootComplete) {
ArraySet<String> done;
// 是否有依賴庫
if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
done = new ArraySet<String>();
done.add(pkg.packageName);
} else {
done = null;
}
synchronized (mPackageManagerService.mInstallLock) {
final boolean useLock = mSystemReady;
if (useLock) {
mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
mDexoptWakeLock.acquire();
}
try {
// 核心代碼
return performDexOptLI(pkg, instructionSets, forceDex, defer, bootComplete, done);
} finally {
if (useLock) {
mDexoptWakeLock.release();
}
}
}
}
通過這個方法我們知道,其實它本質(zhì)是調(diào)用performDexOptLI(PackageParser.Package,String[],String[],boolean.String,CompilerStats.PackageStats)方法
2.3、performDexOptLI(PackageParser.Package,String[],String[],boolean.String,CompilerStats.PackageStats)方法
代碼在PackageDexOptimizer.java 98行
private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter,
CompilerStats.PackageStats packageStats) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
if (!canOptimizePackage(pkg)) {
return DEX_OPT_SKIPPED;
}
final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
boolean isProfileGuidedFilter = DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter);
// If any part of the app is used by other apps, we cannot use profile-guided
// compilation.
if (isProfileGuidedFilter && isUsedByOtherApps(pkg)) {
checkProfiles = false;
targetCompilerFilter = getNonProfileGuidedCompilerFilter(targetCompilerFilter);
if (DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) {
throw new IllegalStateException(targetCompilerFilter);
}
isProfileGuidedFilter = false;
}
// If we're asked to take profile updates into account, check now.
boolean newProfile = false;
if (checkProfiles && isProfileGuidedFilter) {
// Merge profiles, see if we need to do anything.
try {
newProfile = mInstaller.mergeProfiles(sharedGid, pkg.packageName);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to merge profiles", e);
}
}
final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
boolean performedDexOpt = false;
boolean successfulDexOpt = true;
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
for (String path : paths) {
int dexoptNeeded;
try {
dexoptNeeded = DexFile.getDexOptNeeded(path,
dexCodeInstructionSet, targetCompilerFilter, newProfile);
} catch (IOException ioe) {
Slog.w(TAG, "IOException reading apk: " + path, ioe);
return DEX_OPT_FAILED;
}
dexoptNeeded = adjustDexoptNeeded(dexoptNeeded);
if (PackageManagerService.DEBUG_DEXOPT) {
Log.i(TAG, "DexoptNeeded for " + path + "@" + targetCompilerFilter + " is " +
dexoptNeeded);
}
final String dexoptType;
String oatDir = null;
// 判斷類別
switch (dexoptNeeded) {
case DexFile.NO_DEXOPT_NEEDED:
continue;
case DexFile.DEX2OAT_NEEDED:
// 需要把dex轉(zhuǎn)化為oat
dexoptType = "dex2oat";
oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
break;
case DexFile.PATCHOAT_NEEDED:
dexoptType = "patchoat";
break;
case DexFile.SELF_PATCHOAT_NEEDED:
dexoptType = "self patchoat";
break;
default:
throw new IllegalStateException("Invalid dexopt:" + dexoptNeeded);
}
String sharedLibrariesPath = null;
if (sharedLibraries != null && sharedLibraries.length != 0) {
StringBuilder sb = new StringBuilder();
for (String lib : sharedLibraries) {
if (sb.length() != 0) {
sb.append(":");
}
sb.append(lib);
}
sharedLibrariesPath = sb.toString();
}
Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
+ pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
+ " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
+ " target-filter=" + targetCompilerFilter + " oatDir = " + oatDir
+ " sharedLibraries=" + sharedLibrariesPath);
// Profile guide compiled oat files should not be public.
final boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
final int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
final int dexFlags = adjustDexoptFlags(
( isPublic ? DEXOPT_PUBLIC : 0)
| (vmSafeMode ? DEXOPT_SAFEMODE : 0)
| (debuggable ? DEXOPT_DEBUGGABLE : 0)
| profileFlag
| DEXOPT_BOOTCOMPLETE);
try {
long startTime = System.currentTimeMillis();
// 核心代碼
mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid,
sharedLibrariesPath);
performedDexOpt = true;
if (packageStats != null) {
long endTime = System.currentTimeMillis();
packageStats.setCompileTime(path, (int)(endTime - startTime));
}
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dexopt", e);
successfulDexOpt = false;
}
}
}
if (successfulDexOpt) {
// If we've gotten here, we're sure that no error occurred. We've either
// dex-opted one or more paths or instruction sets or we've skipped
// all of them because they are up to date. In both cases this package
// doesn't need dexopt any longer.
return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
} else {
return DEX_OPT_FAILED;
}
}
通過上面代碼我們知道這塊代碼最后是調(diào)用的mInstaller的dexopt方法,關(guān)于Installer我們將會在下一篇文章中詳細(xì)講解
六、PackageManagerService啟動的預(yù)熱
PackageManagerService 在啟動時會掃描所有APK文件和Jar包,然后把它們的信息讀取出來,保存在內(nèi)存中,這樣系統(tǒng)運(yùn)行時就能迅速找到各種應(yīng)用和組件的信息。掃描過程中如果遇到?jīng)]有優(yōu)化過的文件,還要執(zhí)行轉(zhuǎn)化工作。(Android 5.0是odex格式,Android 5.0之后是oat格式)。啟動后,PackageManagerService將提供安裝包的信息查詢服務(wù)以及應(yīng)用的安裝和卸載服務(wù)。
PackageManagerService中有一些規(guī)則比較難以理解,而在代碼中有很大的區(qū)域在描述這些規(guī)則,這些規(guī)則設(shè)計安裝包的關(guān)系。所以說如果能理解它們的關(guān)系,對于后續(xù)理解PackageManagerService的啟動有很大的幫助
(一)、應(yīng)用分類
Android 中應(yīng)用可以簡單地分成兩大類:"系統(tǒng)應(yīng)用"和"普通應(yīng)用"
- 1、系統(tǒng)應(yīng)用是指位于/system/app 或者 /System/priv-app 目錄下的應(yīng)用。priv-app目錄是從Android 4.4開始出現(xiàn)的目錄,它存放的是一些系統(tǒng)底層的應(yīng)用,如Settin、SystemUI等。/system/app中存放的是一些系統(tǒng)級別的應(yīng)用,如:Phone、Contact等。在PackageManagerService 中,所謂的system 應(yīng)用包括這兩個目錄下的應(yīng)用,而所謂的private應(yīng)用就是指 /priv-app 目錄下的應(yīng)用。
- 2、普通應(yīng)用就是用戶安裝的應(yīng)用,位于目錄/data/app下。普通應(yīng)用也可以安裝在SD上,但是系統(tǒng)應(yīng)用不可以。
(二)、幾種特殊情況的
1、系統(tǒng)應(yīng)用升級不在同一個目錄里面
通常情況下系統(tǒng)應(yīng)用是不能刪除的,但是可以升級。升級的方法是安裝一個包名相同,但是如果更高的版本號的應(yīng)用在/data/app目錄下。對于系統(tǒng)中這種的升級情況,Android會在/data/system/package.xml文件中用<update-package>記錄被覆蓋的系統(tǒng)應(yīng)用信息。
2、兩個包名的情況
我們知道應(yīng)用的包名通常是AndroidManifest.xml里面用"package"屬性來指定,同時還可以用<original-package>來指定原始包的名字。但是,這樣的一個應(yīng)用運(yùn)行時,看到的包名是什么?答案是兩種都有可能。如果安裝的設(shè)備中不存在和原始報名相同的系統(tǒng)應(yīng)用,看到的包名將是package屬性定義的名稱。如果設(shè)備上還存在低版本的,而且包名和原始包名不相同的應(yīng)用,這樣雖然最后安裝運(yùn)行的是新安裝的應(yīng)用,但是我們看到應(yīng)用的名稱還是原始的包名,因為,這樣也構(gòu)成了升級關(guān)系。Android會在package.xml中用標(biāo)簽<rename-package>記錄這種改名的情況。
####### 3、其他內(nèi)容
每個應(yīng)用還有保存數(shù)據(jù)的目錄,位于/data/data/packageName/目錄下。數(shù)據(jù)目錄中常見的兩個子目錄:shared_prefs目錄中保存的是應(yīng)用的設(shè)置文件,database目錄中保存的是應(yīng)用的數(shù)據(jù)庫文件
七、關(guān)于shared UID相關(guān)問題
(一)、Android中的UID、GID與GIDS的簡介
說ShardUID就不能不說下Android中的UID、GID與GIDS
Android 是一個權(quán)限分離的操作系統(tǒng)。這是因為Android是基于Linux系統(tǒng)的權(quán)限管理機(jī)制,通過為每一個Application分配不同的uid和gid,從而使得不同的Application之間的私有數(shù)據(jù)和訪問達(dá)到隔離的目的。Android的權(quán)限分離的基礎(chǔ)是Linux已有的uid、gid、gids基礎(chǔ)上的。
1、UID
在Android系統(tǒng)上安裝一個應(yīng)用程序,就會為他分配一個UID,其中普通的Android APP的UID是從10000開始分配的。而10000以下則是系統(tǒng)UID。
2、GID
對于普通的應(yīng)用程序來說,GID等于UID。由于每個應(yīng)用程序的UID和GID不相同,因為不管是Native層還是Java層都能夠達(dá)到保護(hù)私有數(shù)據(jù)的作用。
3、GIDS
GIDS是由框架在Application安裝過程中生成的,與Application申請的權(quán)限有關(guān)。如果Application申請相應(yīng)的權(quán)限被granted,而且其中有對應(yīng)的GIDS,那么這個Application的GIDS將包含這個GIDS
(二)、shared UID
假設(shè)app A要和app B 共享一個UID,那么 app B 不需要配置 Share UID,它會獲得一個普通的UID,需要配置 Share UID 的app A ,這時候系統(tǒng)會將 app B的 UID分配給 app A,這樣就達(dá)到了共享 UID的目的。所以 兩個app 有相同的 UID,并不意味著 它們會運(yùn)行在同一個進(jìn)程中。一個 app 運(yùn)行在哪一個進(jìn)程,一般是由它們 package名稱和 UID來決定的,也就是說,UID相同,但是package名稱不同的兩個 app是運(yùn)行在兩個不同的進(jìn)程中的。如果兩個app,具有相同的UID和包名相同,加上簽名相同,一般認(rèn)為是一個應(yīng)用程序,即覆蓋安裝。
系統(tǒng)給每一個app都分配一個 UID 是用來控制權(quán)限的,因為兩個程序具有相同的UID,就意味著它們有相同的權(quán)限,可以進(jìn)行資源共享。相同的UID的資源共享只是針對Linux文件系統(tǒng)的訪問全權(quán)限控制,不同進(jìn)程間的數(shù)據(jù)是無法共享訪問的。
八、PackageManagerService方法名中"LI"、"LP"、"LPw"、"LPr"的含義
(一)、結(jié)尾是"LI"、"LP"、"LPw"、"LPr"的方法
Android 7.0多了一個"LIF"方法,我也加上去了,主要是Android 6.0的部分"LI"方法變更為"LIF"方法。
(二) L、I、P、w、r的含義
要想明白方法名中 LI、LIF、LPw、LPr的含義需要首先了解PackageManagerService內(nèi)部使用的兩個鎖。因為LI、LIF、LPw 、LPr中的"L" 指的是Lock,而后面跟的"I"和"P"指的是兩個鎖,"I" 表示 mInstallLock 同步鎖。"P"表示 mPackage 同步鎖。為什么我會這么說,大家請看下面代碼
代碼在PackageManagerService.java 469行
// Lock for state used when installing and doing other long running
// operations. Methods that must be called with this lock held have
// the suffix "LI".
final Object mInstallLock = new Object();
// Keys are String (package name), values are Package. This also serves
// as the lock for the global state. Methods that must be called with
// this lock held have the prefix "LP".
@GuardedBy("mPackages")
final ArrayMap<String, PackageParser.Package> mPackages =
new ArrayMap<String, PackageParser.Package>();
先來簡單的翻譯一下注釋:
- 在安裝和執(zhí)行其他耗時操作的鎖定狀態(tài)。必須使用此鎖定調(diào)用的方法,該方法后綴帶有"LI"
- 鍵(key)是String類型,值(value)是包。這也是全局的鎖,必須使用這個鎖來調(diào)用具有前綴"LP"的方法
所以說:
- mPackage 同步鎖:是指操作mPackage時,用synchronized (mPackages) {}保護(hù)起來。mPackage同步鎖用來保護(hù)內(nèi)存中已經(jīng)解析的包信息和其他相關(guān)狀態(tài)。mPackage同步鎖是細(xì)顆粒度的鎖,只能短時間支有這個鎖,因為掙搶這個鎖的請求很多。短時間持有mPackage鎖,可以讓其他請求等待的時間短些
- mInstallLock同步鎖:是指安裝APK的時候,對安裝的處理要用用synchronized (mInstallLock) {}保護(hù)起來。mInstallLock 用來保護(hù)所有對 installd 的訪問。installd通常包含對應(yīng)用數(shù)據(jù)的繁重操作。
PS:由于installd 是單線程的,并且installd的操作通常很慢,所以在已經(jīng)持有mPackage同步鎖的時候,不要再去請求mInstallLock 同步鎖。反之,在已經(jīng)持有mInstallLock 同步鎖的時候,可以去請求mPackage同步鎖
- r 表示讀
- w 表示寫
(三) 、LI、LIF、LPw、LPr的含義
方法名 | 使用方式 |
---|---|
xxxLI() | 必須先持有mInstallLock的鎖 |
xxxLP() | 必須先持有mPackage的鎖 |
xxxLIF() | 必須先持有mInstallLock的鎖,并且正在被修改的包(package) 必須被凍結(jié)(be frozen) |
xxxLPr() | 必須先持有mPackages鎖,并且只用于讀操作 |
xxxLPw() | 必須先持有 mPackage所以,并且只用于寫操作 |
(四) 、總結(jié)
簡單的總結(jié)下就是
上面中的"L"代表lock的首字母L,”I“表示InstallLock的首字母,"P"表示package的首字母,"r"表示read的首字母,”w“表示write的首字母
九、@GuardBy、@SystemApi、@hide Android注解簡介
(一)、@GuardBy注解
在閱讀PackageManagerService.java的源碼時,里面有一個重要的成員變量mInstaller,它使用了@GuardBy注解
如下,代碼在PackageManagerService.java 448行
@GuardedBy("mInstallLock")
final Installer mInstaller;
它類似于Java關(guān)鍵字——synchronized關(guān)鍵字,但使用代替鎖。可以這樣使用這個注解
public class demo {
@GuardedBy("this")
public String string;
}
正如我們看到的,用法是@GuardedBy(lock),這意味著有保護(hù)的字段或者方法只能在線程持有鎖時被某些線程訪問。我們可以將鎖指定為以下類型:
- this:在其類中定義字段的對象的固有鎖。
- classs-name.this:對于內(nèi)部類,可能有必要消除"this"的歧義;class-name.this指定允許你指定"this"引用的意圖。
- itself:僅供參考字段;字段引用的對象。
- field-name:鎖定對象由字段名指定的(實例或靜態(tài))字段引用。
- class-name.field-name:鎖對象由class-name.field-name指定的靜態(tài)字段引用
- method-name():鎖定對象通過調(diào)用命名的nil-ary方法返回。
- class-name:指定類的Class對象做鎖定對象
說白了,就是告知開發(fā)者,被@GuardedBy 注解標(biāo)注的變量要用同步鎖保護(hù)起來
舉例說明
public class BankAccount {
private Object personInfo = new Object();
@GuardedBy("personInfo")
private int amount;
}
在上面的代碼中,當(dāng)有人獲取了個人信息的同步鎖時,可以訪問金額,因此BankAccount中的金額由個人信息保護(hù)。
(二)、@SystemApi、@PrivateApi與@hide注解簡介
在閱讀PackageManager.java的源碼時,里面有會大量用到兩個注解@SystemApi和@hide注解
如下,代碼在PackageManager.java 448行
/**
* Listener for changes in permissions granted to a UID.
*
* @hide
*/
@SystemApi
public interface OnPermissionsChangedListener {
/**
* Called when the permissions for a UID change.
* @param uid The UID with a change.
*/
public void onPermissionsChanged(int uid);
}
@SystemApi 其實是@PrivateApi的別名;使用@hide標(biāo)記的API可以不使用@SystemApi進(jìn)行標(biāo)記;但是當(dāng)使用@SystemApi標(biāo)記的API則必須使用@hide標(biāo)記,在Android源碼中,有兩種類型的API無法通過標(biāo)準(zhǔn)的SDK進(jìn)行訪問。
@SystemApi和@hide的區(qū)別:
- 隱藏的方法(使用@hide修飾的)仍然可以通過Java反射機(jī)制進(jìn)行訪問;@hide標(biāo)示只是javadoc的一部分(也是Android doc的一部分),所以@hide修飾符僅僅指示了method/class/field不會被暴露到API中。
- 使用@SystemApi修飾的method/class/field,無法通過java反射機(jī)制進(jìn)行訪問(會觸發(fā)invocationTargetException異常)
上一篇文章 APK安裝流程詳解5——PackageInstallerService和Installer
上一篇文章 APK安裝流程詳解7——PackageManagerService的啟動流程(上)