APK安裝流程詳解6——PackageManagerService啟動前奏

APK安裝流程系列文章整體內(nèi)容如下:

本片文章的主要內(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的多種信息

ShareUserSetting的架構(gòu)1.png
ShareUserSetting的架構(gòu)2.png

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é)

一圖勝千言


mSetting與ShareUserSetting.png

二、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)的全局變量

如下圖


SystemConfig.png

三、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"方法。


方法.png

(二) 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的啟動流程(上)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容