APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補(bǔ)充

本篇文章主要內(nèi)容如下:

  • 1、在PackageManagerService的installPackageAsUser方法里面的代碼
  • 2、DefaultContainerService詳解
  • 3、mContainerService.getMinimalPackageInfo(String.int,String)方法與calculateInstalledSize(String,boolean,String)方法的講解
  • 4、為什么說mContext.bindServiceAsUser等于mContext.bindService
  • 5、HandlerParams與InstallParams簡介
  • 6、InstallArgs家族成員
  • 7、為什么新安裝的情況下 origin.staged等于false
  • 8、LocalSocket的跨進(jìn)程通信
  • 9、createInstallArgs(InstallParams)方法解答
  • 10、sVerificationEnabled(int userId, int installFlags) 的理解
  • 11、Context.sendBroadcast(Intent intent)的功能是和Context.sendBroadcastAsUser(Intent,UserHandle)一樣的解答
  • 12、Split APK(APK拆分)與Instant Run簡介

一、在PackageManagerService的installPackageAsUser方法里面的代碼mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null)

這個方法調(diào)用在PackageManagerService.java 9524行

(一) mContext是什么?

mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null)
要看這個方法內(nèi)部執(zhí)行,首先要知道這個mContext是什么,我們知道這個mContext是通過PackageManagerService的main方法傳入的,所以這個mContext就是SystemServer里面的mSystemContext。
代碼在SystemServer.java 366行如下:

        mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);

這樣我來追蹤下mSystemContext

我們找到了mSystemContext的初始化的地方在createSystemContext()里面
代碼在SystemServer.java 311行如下:

    private void createSystemContext() {
        ActivityThread activityThread = ActivityThread.systemMain();
        mSystemContext = activityThread.getSystemContext();
        mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
    }

那我們就來追中下ActivityThread 的getSystemContext()方法

代碼在ActivityThread.java 1886行如下:

    public ContextImpl getSystemContext() {
        synchronized (this) {
            if (mSystemContext == null) {
                mSystemContext = ContextImpl.createSystemContext(this);
            }
            return mSystemContext;
        }
    }

進(jìn)而追蹤到ContextImpl里面代碼在ContextImpl.java 1774行。

    static ContextImpl createSystemContext(ActivityThread mainThread) {
        LoadedApk packageInfo = new LoadedApk(mainThread);
        ContextImpl context = new ContextImpl(null, mainThread,
                packageInfo, null, null, false, null, null, Display.INVALID_DISPLAY);
        context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
                context.mResourcesManager.getDisplayMetricsLocked());
        return context;
    }

所以我們知道這個SystemContext其實就是ContextImpl,這就好辦了,我們直接找ContextImpl對應(yīng)的enforceCallingOrSelfPermission方法

代碼在ContextImpl.java 1468行

    @Override
    public void enforceCallingOrSelfPermission(
            String permission, String message) {
        enforce(permission,
                checkCallingOrSelfPermission(permission),
                true,
                Binder.getCallingUid(),
                message);
    }

這個方法首先調(diào)用了checkCallingOrSelfPermission方法,然后再調(diào)用enforce方法,那我們就依次看下。

(二)ContextImpl#checkCallingOrSelfPermission(String) 方法 簡介

代碼在ContextImpl.java 1416行

    @Override
    public int checkCallingOrSelfPermission(String permission) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }

        return checkPermission(permission, Binder.getCallingPid(),
                Binder.getCallingUid());
    }

這個方法首先做了入?yún)ermission的非空判斷,然后調(diào)用了checkPermission(String,int,int )方法

而在ContextImpl里面checkPermission(String,int,int )方法如下,代碼在ContextImpl.java 1374行:

    @Override
    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }

        try {
            return ActivityManagerNative.getDefault().checkPermission(
                    permission, pid, uid);
        } catch (RemoteException e) {
            return PackageManager.PERMISSION_DENIED;
        }
    }

我們看到的是最后調(diào)用的是 ActivityManagerNative.getDefault().checkPermission(permission, pid, uid);這里先提前說下這個方法其實是調(diào)用的ActivityServcieManager的checkPermission(String,int ,int)方法,關(guān)于為什么會這樣,我們后面講解Activity的啟動流程的時候會詳細(xì)講解。

在ActivityManagerService.java里面checkPermission(String,int,int )方法如下,代碼在ActivityManagerService.java 7108行:

    /**
     * As the only public entry point for permissions checking, this method
     * can enforce the semantic that requesting a check on a null global
     * permission is automatically denied.  (Internally a null permission
     * string is used when calling {@link #checkComponentPermission} in cases
     * when only uid-based security is needed.)
     *
     * This can be called with or without the global lock held.
     */
    @Override
    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            return PackageManager.PERMISSION_DENIED;
        }
        return checkComponentPermission(permission, pid, uid, -1, true);
    }

哈哈,有注釋了,我最喜歡有注釋的方法了,先簡單翻譯一下注釋的內(nèi)容:

權(quán)限檢查的唯一公共入口。這個方法可以強(qiáng)制執(zhí)行對全局權(quán)限請求的檢查和如果是空的權(quán)限可以自動拒絕。如果是在uid安全的情況,如果想使用空的字符串來檢查權(quán)限可以調(diào)用checkComponentPermission這個方法。
這個方法可以在有或者沒有全局鎖定的情況下使用。

通過上面注釋,我們知道了這是一個全局的檢查權(quán)限的入口了。我們看方法內(nèi)部最后調(diào)用了checkComponentPermission方法了,那我們就繼續(xù)跟蹤

int checkComponentPermission(String , int , int , int , boolean )方法如下,代碼在ActivityManagerService.java 7089行:

    /**
     * This can be called with or without the global lock held.
     */
    int checkComponentPermission(String permission, int pid, int uid,
            int owningUid, boolean exported) {
        if (pid == MY_PID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return ActivityManager.checkComponentPermission(permission, uid,
                owningUid, exported);
    }

通過注釋我們知道,這個方法可以在有全局鎖定或者沒有全局鎖定的時候調(diào)用。這個方法內(nèi)部只做了一個MY_PID的判斷,如果pid=MY_PID,而MY_PID其實就是當(dāng)前進(jìn)程的pid,則直接返回PackageManager.PERMISSION_GRANTED,而PackageManager.PERMISSION_GRANTED表示的意思是"授予",即檢查通過。那我們來看下ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);這個方法的具體執(zhí)行

代碼在ActivityManager.java) 2617行

    /** @hide */
    public static int checkComponentPermission(String permission, int uid,
            int owningUid, boolean exported) {
        // Root, system server get to do everything.
        // 首先判斷是不是root和system,如果是直接返回"授予",因為它們擁有最大權(quán)限
        final int appId = UserHandle.getAppId(uid);
        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
            return PackageManager.PERMISSION_GRANTED;
        }

        // Isolated processes don't get any permissions. 一般用不到,需要在AndroidManifest里面設(shè)置android:isolatedProcess=true
        // 判斷是否是隔離進(jìn)程 如果是隔離進(jìn)程則直接拒絕
        if (UserHandle.isIsolated(uid)) {
            return PackageManager.PERMISSION_DENIED;
        }
        // If there is a uid that owns whatever is being accessed, it has
        // blanket access to it regardless of the permissions it requires.
       // 如果是同一個應(yīng)用,則不需要監(jiān)測
        if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // If the target is not exported, then nobody else can get to it.
        //  如果設(shè)置了exported=false,(比如在AndroidManifest里面設(shè)置了exported=false) 則表明這個APP沒有授權(quán),所以拒絕
        if (!exported) {
            /*
            RuntimeException here = new RuntimeException("here");
            here.fillInStackTrace();
            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
                    here);
            */
            return PackageManager.PERMISSION_DENIED;
        }
       // 如果permission==null 則通過
        if (permission == null) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // 否則調(diào)用AppGlobals.getPackageManager().checkUidPermission(permission,uid)
        try {
            return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
        } catch (RemoteException e) {
            // Should never happen, but if it does... deny!
            Slog.e(TAG, "PackageManager is dead?!?", e);
        }
        return PackageManager.PERMISSION_DENIED;
    }

在講解這個方法的時候先補(bǔ)充一個知識點,即在Android的系統(tǒng)中,每一個APP都會分配一個uid,但是一個APP內(nèi)部可能會有多進(jìn)程,所以APP的內(nèi)部就可能存在不同的pid,但是其APP內(nèi)部的進(jìn)程共享一個uid。

方法內(nèi)部注釋已經(jīng)解釋的很清楚了,這里說下最后的AppGlobals.getPackageManager().checkUidPermission(permission,uid)方法,如果成功調(diào)用則直接放回,如果拋異常了,則返回拒絕(PackageManager.PERMISSION_DENIED),那我們就來看下AppGlobals.getPackageManager().checkUidPermission(permission, uid);里面的具體實現(xiàn)

這里首先要看下AppGlobals.getPackageManager()的值是什么?
代碼在AppGlobals.java 46行。

    /**
     * Return the raw interface to the package manager.
     * @return The package manager.
     */
    public static IPackageManager getPackageManager() {
        return ActivityThread.getPackageManager();
    }

我們前面的文章APK安裝流程詳解3——PackageManager與PackageManagerService我們知道最后到了PackageManagerService里面,咦好像又回來了。所以我們知道AppGlobals.getPackageManager()=PackageManagerService對象。所以AppGlobals.getPackageManager().checkUidPermission(permission, uid);這個方法其實可以理解為PackageManagerService#checkUidPermission(permission, uid)方法

代碼在PackageManagerService.java 3190行

    @Override
    public int checkUidPermission(String permName, int uid) {
        final int userId = UserHandle.getUserId(uid);
        // 判斷這個userId 對應(yīng)的App是否存在
        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }

        synchronized (mPackages) {
            // 獲取這個uid對應(yīng)的SettingBase
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
             // PackageManagerService.Setting.mUserIds數(shù)組中,根據(jù)uid查找uid也就是package的權(quán)限列表
            if (obj != null) {
                final SettingBase ps = (SettingBase) obj;
                // 獲取對應(yīng)的permissionsState 
                final PermissionsState permissionsState = ps.getPermissionsState();
                 // 如果permissionsState  里面包含這個permName,則通過
                if (permissionsState.hasPermission(permName, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
                 // ACCESS_COARSE_LOCATION的特殊情況,也是通過
                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
               // 如果上面都沒滿足,則拒絕權(quán)限
            } else {
                 // 系統(tǒng)級應(yīng)用uid 對應(yīng)的permission
                ArraySet<String> perms = mSystemPermissions.get(uid);
                if (perms != null) {
                    if (perms.contains(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
            }
        }
        return PackageManager.PERMISSION_DENIED;
    }

PS:我這里是Android 6.0的源碼,所以這個的代碼和Android 5.0是不同的,所以有心的同學(xué)可以去對比下,Android6.0里面多了一個PermissionsState,Android 6.0以后是對權(quán)限的操作是PermissionsState。有興趣的同學(xué)可以研究下PermissionsState,和它的hasPermission(String name, int userId)方法,這里面包含了除了聲明的權(quán)限,還必須是授權(quán)的權(quán)限。

上面代碼注釋已經(jīng)寫的很清楚了,大家可以自行查看,自此)ContextImpl#checkCallingOrSelfPermission(String)整個方法流程就已經(jīng)跟蹤完畢。

(三)、ContextImpl#enforce(String,int,boolean,int,String)方法簡介

代碼在ContextImpl.java 1434行

    private void enforce(
            String permission, int resultOfCheck,
            boolean selfToo, int uid, String message) {
        if (resultOfCheck != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException(
                    (message != null ? (message + ": ") : "") +
                    (selfToo
                     ? "Neither user " + uid + " nor current process has "
                     : "uid " + uid + " does not have ") +
                    permission +
                    ".");
        }
    }

這里面很簡單,主要判斷是上面checkCallingOrSelfPermission方法的返回值,如果不是PackageManager.PERMISSION_GRANTED則直接拋異常,如果是,則什么也不做。

(四)、總結(jié)

通過上述方法的解析,我們知道ContextImple#enforceCallingOrSelfPermission經(jīng)過一些列的調(diào)用,最后還是判斷這個APP的權(quán)限。

二、DefaultContainerService詳解

DefaultContainerService.java源碼地址

(一)、DefaultContainerService類簡介

/**
 * Service that offers to inspect and copy files that may reside on removable
 * storage. This is designed to prevent the system process from holding onto
 * open files that cause the kernel to kill it when the underlying device is
 * removed.
 */
public class DefaultContainerService extends IntentService {
          ...
}

首先我們知道DefaultContainerService繼承自IntentService,然后為了更好的理解設(shè)計者的意圖,我們還是看下面的注釋

提供檢查和復(fù)制文件的Service,這個Service既可以提供保存在存儲空間的服務(wù)也可以是提供刪除服務(wù)。這樣設(shè)計的的目的是防止:在系統(tǒng)進(jìn)程打開文件的時候,同時如果底層設(shè)備刪除了文件而內(nèi)核將其殺死的情況的發(fā)生。

所以我們總結(jié)一下DefaultContainerService是一個應(yīng)用服務(wù),具體負(fù)責(zé)實現(xiàn)APK等相關(guān)資源文件在內(nèi)部或者外部存儲器上的存儲工作。

(二)、DefaultContainerService類結(jié)構(gòu)

DefaultContainerService類結(jié)構(gòu).png

通過上面類結(jié)構(gòu)的圖,我們發(fā)現(xiàn)這個類的方法大多數(shù)是私有的或者"包"內(nèi)的方法,所以只要找到源頭,基本上能捋順這個類。而想要捋順這個類很簡單,因為它繼承IntentService,所以一般的Android開發(fā)工程師都是知道只要找他到的onHandlerIntent方法即可。那下面我們就來研究下這個方法

(三)、onHandlerIntent(Intent)方法

代碼在DefaultContainerService.java 271行

    @Override
    protected void onHandleIntent(Intent intent) {
        if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
           // 第一步
            final IPackageManager pm = IPackageManager.Stub.asInterface(
                    ServiceManager.getService("package"));
            PackageCleanItem item = null;
            try {
               // 第二步
                while ((item = pm.nextPackageToClean(item)) != null) {
                    final UserEnvironment userEnv = new UserEnvironment(item.userId);
                    eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName));
                    eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName));
                    if (item.andCode) {
                        eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName));
                    }
                }
            } catch (RemoteException e) {
            }
        }
    }

我們把這個方法里面的內(nèi)容分為三大部分

  • 第一步:獲取IPackageManager對象,其實也就是PackageManagerService的代理對象pm
  • 第二步:遍歷pm即PackageManagerService中的已經(jīng)卸載了,但是仍然占用存儲空間的對象PackageCleanItem
  • 第三步:調(diào)用eraseFiles()方法來清除文件

這里面涉及三個內(nèi)容

  • 1、pm. nextPackageToClean(PackageCleanItem)方法
  • 2、UserEnvironment類及其方法
  • 3、本地的eraseFiles方法

那我們就依次來看下

1、pm. nextPackageToClean(PackageCleanItem)方法

我們知道pm其實是PackageManagerService的代理類,所以我們直接找PackageManagerService的nextPackageToClean(PackageCleanItem)方法

代碼在PackageManagerService.java 9457行

    @Override
    public PackageCleanItem nextPackageToClean(PackageCleanItem lastPackage) {
        // writer
        synchronized (mPackages) {
            // 第一步
            if (!isExternalMediaAvailable()) {
                // If the external storage is no longer mounted at this point,
                // the caller may not have been able to delete all of this
                // packages files and can not delete any more.  Bail.
                return null;
            }
           
            // 第二步
            final ArrayList<PackageCleanItem> pkgs = mSettings.mPackagesToBeCleaned;
            // 第三步
            if (lastPackage != null) {
                pkgs.remove(lastPackage);
            }
            // 第四步
            if (pkgs.size() > 0) {
                return pkgs.get(0);
            }
        }
        return null;
    }

這個方法內(nèi)部主要分為4步:

  • 第一步:判斷外部設(shè)備是否可用
  • 第二步:獲取已經(jīng)刪除了,但仍然占用存儲空間的列表
  • 第三步:這一步其實是遞歸的一個思路,如果是第一次調(diào)用nextPackageToClean,則lastPackage為null。如果不是第一次調(diào)用,則lastPackage為pkgs中目前元素的上一個元素。
  • 第四步:獲取pkgs中的第0位的元素,注意這里是get方法,這里獲取的元素,會在第三步中刪除的。

這里涉及到了mSettings.mPackagesToBeCleaned的概念,那我們來看下這個變量是什么?

    // Packages that have been uninstalled and still need their external
    // storage data deleted.
    final ArrayList<PackageCleanItem> mPackagesToBeCleaned = new ArrayList<PackageCleanItem>();

通過注釋我們知道,這個mPackagesToBeCleaned變量表示的是:已經(jīng)卸載了,但是仍占用外部存儲空間的軟件包。

至此pm. nextPackageToClean(PackageCleanItem)方法已經(jīng)跟蹤完畢

2、UserEnvironment類及其方法

UserEnvironment是Environment.java的靜態(tài)內(nèi)部類

UserEnvironment 我的理解就是某個應(yīng)用的存儲空間訪問類
我們常用的幾個方法是:

  • buildExternalStorageAppCacheDirs(packageName):對應(yīng)sdcard/android/0/包名/cache 目錄
  • buildExternalStorageAppDataDirs(packageName):對應(yīng)sdcard/android/0/包名/data 目錄
  • buildExternalStorageAppMediaDirs(packageName):對應(yīng)sdcard/android/0/包名/media 目錄
  • buildExternalStorageAppObbDirs(packageName):對應(yīng)sdcard/android/0/包名/obb 目錄
3、eraseFiles(File[])方法

代碼在DefaultContainerService.java 290行

    void eraseFiles(File[] paths) {
        for (File path : paths) {
            eraseFiles(path);
        }
    }

我們看到這個方法最后調(diào)用的重載的eraseFiles(String)方法
代碼在DefaultContainerService.java 296行

    void eraseFiles(File path) {
        if (path.isDirectory()) {
            String[] files = path.list();
            if (files != null) {
                for (String file : files) {
                    eraseFiles(new File(path, file));
                }
            }
        }
        path.delete();
    }

我們發(fā)現(xiàn)它使用遞歸的方法,依次刪除文件。

(四)、DefaultContainerService的重要變量mBinder

在DefaultContainerService里面有一個重要變量mBinder。我們來看下
代碼在DefaultContainerService.java 72行

  private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
        /**
         * Creates a new container and copies package there.
         *
         * @param packagePath absolute path to the package to be copied. Can be
         *            a single monolithic APK file or a cluster directory
         *            containing one or more APKs.
         * @param containerId the id of the secure container that should be used
         *            for creating a secure container into which the resource
         *            will be copied.
         * @param key Refers to key used for encrypting the secure container
         * @return Returns the new cache path where the resource has been copied
         *         into
         */
        @Override
        public String copyPackageToContainer(String packagePath, String containerId, String key,
                boolean isExternal, boolean isForwardLocked, String abiOverride) {
            if (packagePath == null || containerId == null) {
                return null;
            }

            if (isExternal) {
                // Make sure the sdcard is mounted.
                String status = Environment.getExternalStorageState();
                if (!status.equals(Environment.MEDIA_MOUNTED)) {
                    Slog.w(TAG, "Make sure sdcard is mounted.");
                    return null;
                }
            }

            PackageLite pkg = null;
            NativeLibraryHelper.Handle handle = null;
            try {
                final File packageFile = new File(packagePath);
                pkg = PackageParser.parsePackageLite(packageFile, 0);
                handle = NativeLibraryHelper.Handle.create(pkg);
                return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal,
                        isForwardLocked, abiOverride);
            } catch (PackageParserException | IOException e) {
                Slog.w(TAG, "Failed to copy package at " + packagePath, e);
                return null;
            } finally {
                IoUtils.closeQuietly(handle);
            }
        }

        /**
         * Copy package to the target location.
         *
         * @param packagePath absolute path to the package to be copied. Can be
         *            a single monolithic APK file or a cluster directory
         *            containing one or more APKs.
         * @return returns status code according to those in
         *         {@link PackageManager}
         */
        @Override
        public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
            if (packagePath == null || target == null) {
                return PackageManager.INSTALL_FAILED_INVALID_URI;
            }

            PackageLite pkg = null;
            try {
                final File packageFile = new File(packagePath);
                pkg = PackageParser.parsePackageLite(packageFile, 0);
                return copyPackageInner(pkg, target);
            } catch (PackageParserException | IOException | RemoteException e) {
                Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            }
        }

        /**
         * Parse given package and return minimal details.
         *
         * @param packagePath absolute path to the package to be copied. Can be
         *            a single monolithic APK file or a cluster directory
         *            containing one or more APKs.
         */
        @Override
        public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
                String abiOverride) {
            final Context context = DefaultContainerService.this;
            final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;

            PackageInfoLite ret = new PackageInfoLite();
            if (packagePath == null) {
                Slog.i(TAG, "Invalid package file " + packagePath);
                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
                return ret;
            }

            final File packageFile = new File(packagePath);
            final PackageParser.PackageLite pkg;
            final long sizeBytes;
            try {
                pkg = PackageParser.parsePackageLite(packageFile, 0);
                sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
            } catch (PackageParserException | IOException e) {
                Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);

                if (!packageFile.exists()) {
                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
                } else {
                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
                }

                return ret;
            }

            ret.packageName = pkg.packageName;
            ret.splitNames = pkg.splitNames;
            ret.versionCode = pkg.versionCode;
            ret.baseRevisionCode = pkg.baseRevisionCode;
            ret.splitRevisionCodes = pkg.splitRevisionCodes;
            ret.installLocation = pkg.installLocation;
            ret.verifiers = pkg.verifiers;
            ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
                    pkg.packageName, pkg.installLocation, sizeBytes, flags);
            ret.multiArch = pkg.multiArch;

            return ret;
        }

        @Override
        public ObbInfo getObbInfo(String filename) {
            try {
                return ObbScanner.getObbInfo(filename);
            } catch (IOException e) {
                Slog.d(TAG, "Couldn't get OBB info for " + filename);
                return null;
            }
        }

        @Override
        public long calculateDirectorySize(String path) throws RemoteException {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

            final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path));
            if (dir.exists() && dir.isDirectory()) {
                final String targetPath = dir.getAbsolutePath();
                return MeasurementUtils.measureDirectory(targetPath);
            } else {
                return 0L;
            }
        }

        @Override
        public long[] getFileSystemStats(String path) {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

            try {
                final StructStatVfs stat = Os.statvfs(path);
                final long totalSize = stat.f_blocks * stat.f_bsize;
                final long availSize = stat.f_bavail * stat.f_bsize;
                return new long[] { totalSize, availSize };
            } catch (ErrnoException e) {
                throw new IllegalStateException(e);
            }
        }

        @Override
        public void clearDirectory(String path) throws RemoteException {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

            final File directory = new File(path);
            if (directory.exists() && directory.isDirectory()) {
                eraseFiles(directory);
            }
        }

        /**
         * Calculate estimated footprint of given package post-installation.
         *
         * @param packagePath absolute path to the package to be copied. Can be
         *            a single monolithic APK file or a cluster directory
         *            containing one or more APKs.
         */
        @Override
        public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
                String abiOverride) throws RemoteException {
            final File packageFile = new File(packagePath);
            final PackageParser.PackageLite pkg;
            try {
                pkg = PackageParser.parsePackageLite(packageFile, 0);
                return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
            } catch (PackageParserException | IOException e) {
                Slog.w(TAG, "Failed to calculate installed size: " + e);
                return Long.MAX_VALUE;
            }
        }
    };

通過上面代碼我們知道,mBinder是IMediaContainerService.Stub類型,看到這個類型,大家一定很熟了,對的一看就是AIDL。而且是AIDL的"服務(wù)端"。

看到AIDL我們首先要找他的源碼地址IMediaContainerService.aidl地址

PS:DefaultContainerService的onBind方法返回的就是mBinder
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

三、mContainerService.getMinimalPackageInfo(String.int,String)方法與calculateInstalledSize(String,boolean,String)方法的講解

(一)、mContainerService是什么?

先說下這個方法調(diào)用的位置:
在PackageManagerService中的handleStartCopy()方法里面
在代碼PackageManagerService.java 10597行

                pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
                        packageAbiOverride);

要想知道這個方法的具體流程,首要先要明確mContainerService是一個什么東西。
而mContainerService其實是IMediaContainerService類型的,如下

    private IMediaContainerService mContainerService = null;

我們再來找下mContainerService初始化的位置:
在doHandleMessage(Message)方法里面MCS_BOUND的時候初始化的,在PackageManagerService.java 1173行

                case MCS_BOUND: {
                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
                    if (msg.obj != null) {
                        mContainerService = (IMediaContainerService) msg.obj;
                    }

而這里面的msg.obj是在DefaultContainerConnection對象mDefContainerConn"綁定"連接DefaultContainerService的時候執(zhí)行onServiceConnected的時候初始化的。如下:
PackageManagerService.java 928行

    class DefaultContainerConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
            IMediaContainerService imcs =
                IMediaContainerService.Stub.asInterface(service);
            mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
        }

        public void onServiceDisconnected(ComponentName name) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
        }
    }

通過上面的跟蹤我們知道了mContainerService其實就是上面方法通過 IMediaContainerService.Stub.asInterface(service)來獲取的,通過AIDL知識我們知道其實對應(yīng)的是DefaultContainerService的內(nèi)部變量mBinder。所以說

mContainerService對應(yīng)著DefaultContainerService的成員變量mBinder。所以mContainerService.getMinimalPackageInfo(String.int,String)方法對應(yīng)的是DefaultContainerService的成員變量mBinder的getMinimalPackageInfo(String.int,String)方法。

(二)、mContainerService.getMinimalPackageInfo(String.int,String)方法

代碼在DefaultContainerService.java 152行

        /**
         * Parse given package and return minimal details.
         *
         * @param packagePath absolute path to the package to be copied. Can be
         *            a single monolithic APK file or a cluster directory
         *            containing one or more APKs.
         */
        @Override
        public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
                String abiOverride) {
            // 第一步
            final Context context = DefaultContainerService.this;
            final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;

            PackageInfoLite ret = new PackageInfoLite();
             // 第二步
            if (packagePath == null) {
                Slog.i(TAG, "Invalid package file " + packagePath);
                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
                return ret;
            }

            final File packageFile = new File(packagePath);
            final PackageParser.PackageLite pkg;
            final long sizeBytes;
            try {
                pkg = PackageParser.parsePackageLite(packageFile, 0);
                sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
            } catch (PackageParserException | IOException e) {
                Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);

                if (!packageFile.exists()) {
                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
                } else {
                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
                }

                return ret;
            }

            // 第三步
            ret.packageName = pkg.packageName;
            ret.splitNames = pkg.splitNames;
            ret.versionCode = pkg.versionCode;
            ret.baseRevisionCode = pkg.baseRevisionCode;
            ret.splitRevisionCodes = pkg.splitRevisionCodes;
            ret.installLocation = pkg.installLocation;
            ret.verifiers = pkg.verifiers;
            ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
                    pkg.packageName, pkg.installLocation, sizeBytes, flags);
            ret.multiArch = pkg.multiArch;

            return ret;
        }

先來看下注釋:

解析包并獲去小的安裝包內(nèi)容

  • 入?yún)?packagePath:要復(fù)制包的絕對路徑。這個目錄可以包含單個APK也可以包含多個APK

我將這個方法分為三個部分

  • 第一步:初始化一些信息
  • 第二步:解析packagePath對應(yīng)的安裝包,獲取解析的出來的"輕"安裝包pkg
  • 第三步:把解析出來的"輕"安裝包的屬性賦值給PackageInfoLite對象ret并返回

(三)、calculateInstalledSize(origin.resolvedPath, isForwardLocked(), packageAbiOverride);方法

代碼在DefaultContainerService.java 251行

        /**
         * Calculate estimated footprint of given package post-installation.
         *
         * @param packagePath absolute path to the package to be copied. Can be
         *            a single monolithic APK file or a cluster directory
         *            containing one or more APKs.
         */
        @Override
        public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
                String abiOverride) throws RemoteException {
            final File packageFile = new File(packagePath);
            final PackageParser.PackageLite pkg;
            try {
                pkg = PackageParser.parsePackageLite(packageFile, 0);
                return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
            } catch (PackageParserException | IOException e) {
                Slog.w(TAG, "Failed to calculate installed size: " + e);
                return Long.MAX_VALUE;
            }
        }

先來看下注釋:

計算安裝包安裝后可能的大小

  • 入?yún)?packagePath:這個目錄可以包含單個APK也可以包含多個APK

四、為什么說mContext.bindServiceAsUser等于mContext.bindService

(一)先說下這個mContext.bindServiceAsUser在哪里被調(diào)用

PackageManagerService.java1109
在connectToService()方法里面被調(diào)用

        private boolean connectToService() {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
                    " DefaultContainerService");
            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
            if (mContext.bindServiceAsUser(service, mDefContainerConn,
                    Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                mBound = true;
                return true;
            }
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return false;
        }

通過前文我們知道這里的mContext其實就是ContextImpl,所以我們看下這個bindServiceAsUser方法的具體實現(xiàn)
代碼在ContextImpl.java) 1291

    @Override
    public boolean bindService(Intent service, ServiceConnection conn,
            int flags) {
        warnIfCallingFromSystemProcess();
        return bindServiceCommon(service, conn, flags, mMainThread.getHandler(),
                Process.myUserHandle());
    }

    /** @hide */
    @Override
    public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
            UserHandle user) {
        return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), user);
    }

大家看到?jīng)],bindService和bindServiceAsUser的內(nèi)部其實是用調(diào)用bindServiceCommon這個方法來實現(xiàn)的具體的邏輯的,所以說bindService方法和bindServiceAsUser其實內(nèi)部的執(zhí)行邏輯是一直的

五、HandlerParams與InstallParams簡介

在PackageManagerService進(jìn)行安裝的時候會涉及兩個概念即HandlerParams與InstallParams,那我們就依次介紹下:

(一)、HandlerParams類

代碼在PackageManagerService.java 10233行

    private abstract class HandlerParams {
        private static final int MAX_RETRIES = 4;

        /**
         * Number of times startCopy() has been attempted and had a non-fatal
         * error.
         */
        private int mRetries = 0;

        /** User handle for the user requesting the information or installation. */
        private final UserHandle mUser;

       HandlerParams(UserHandle user) {
            mUser = user;
        }

        UserHandle getUser() {
            return mUser;
        }

        final boolean startCopy() {
            boolean res;
            try {
                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);

                if (++mRetries > MAX_RETRIES) {
                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                    mHandler.sendEmptyMessage(MCS_GIVE_UP);
                    handleServiceError();
                    return false;
                } else {
                    handleStartCopy();
                    res = true;
                }
            } catch (RemoteException e) {
                if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
                mHandler.sendEmptyMessage(MCS_RECONNECT);
                res = false;
            }
            handleReturnCode();
            return res;
        }

        final void serviceError() {
            if (DEBUG_INSTALL) Slog.i(TAG, "serviceError");
            handleServiceError();
            handleReturnCode();
        }

        abstract void handleStartCopy() throws RemoteException;
        abstract void handleServiceError();
        abstract void handleReturnCode();
    }

我們看到這類,就一個構(gòu)造函數(shù)。而且在構(gòu)造這個類的時候,需要傳入一個UserHandle
它里面有三個抽象方法

  • abstract void handleStartCopy() throws RemoteException;
  • abstract void handleServiceError();
  • abstract void handleReturnCode();

有兩個核心非抽象方法,注意這兩個方法都是final的

  • final startCopy():
  • final serviceError():

startCopy()已經(jīng)在上一篇文章APK安裝流程詳解10——PMS中的新安裝流程HandlerParams的startCopy方法講解了,而serviceError()里面其實是調(diào)用了兩個handleServiceError()和handleReturnCode()抽象方法

小結(jié)=·

其實這個HandlerParams類主要就做了2件事,一個是抽象出三行為,即三個抽象方法,然后定義了重試4次,如果超過4次則放棄重試的規(guī)則。

下面我們就來看下InstallParams類

(二)、InstallParams類與HandlerParams的關(guān)系

代碼在PackageManagerService.java 10464行

    class InstallParams extends HandlerParams {
             ...
    }

首先我們知道InstallParams類繼承自HandlerParams,且InstallParams不是抽象方法,所以InstallParams必然實現(xiàn)了HandlerParams所對應(yīng)的三個方法。

所以InstallParams與HandlerParams關(guān)系如下:

image.png

所以說HandlerParams有兩個子類,分別是InstallParams和MeasureParams。

  • InstallParams:用于處理APK的安裝
  • MeasureParams:用于查詢某個已安裝的APK占據(jù)的存儲空間的大小,例如在設(shè)置程序中得到某個APK使用緩存文件的大小。

五、InstallArgs家族成員

InstallArgs是PackageManagerService的靜態(tài)內(nèi)部類
代碼在PackageManagerService.java 10907行

    static abstract class InstallArgs {
       ...
    }

通過上面代碼我們知道InstallArgs是抽象類,我們看到InstallArgs是靜態(tài)類,且不是"public"的,所以InstallArgs的所有子類肯定都在PackageManagerService中

我們找到了三個子類如下:

  • 1 FileInstallArgs:APK安裝在內(nèi)部存儲空間的時候使用的子類
  • 2 AsecInstallArgs:安裝到sdcard或者ForwardLocked的時候使用的子類
  • 3、MoveInstallArgs:移動包的位置,比如從內(nèi)部存儲移動到sdcard上的構(gòu)造方法中根據(jù)InstallParams會構(gòu)造出具體類型
image.png

設(shè)計這四個類的目的是什么意義?

這樣設(shè)計的目的是:APK可以安裝在內(nèi)部存儲空間或者SD卡上,已經(jīng)安裝的APK也可以在內(nèi)部存儲和SD之間進(jìn)行移動,PackageManagerService為此設(shè)計了InstallArgs這個抽象類的數(shù)據(jù)結(jié)構(gòu),它代表這三種情況通用的屬性,

這里我們用FileInstallArgs類舉例,說一下FileInstallArgs與InstallParams的關(guān)系
代碼如下:

    /**
     * Logic to handle installation of non-ASEC applications, including copying
     * and renaming logic.
     */
    class FileInstallArgs extends InstallArgs {
        private File codeFile;
        private File resourceFile;

        // Example topology:
        // /data/app/com.example/base.apk
        // /data/app/com.example/split_foo.apk
        // /data/app/com.example/lib/arm/libfoo.so
        // /data/app/com.example/lib/arm64/libfoo.so
        // /data/app/com.example/dalvik/arm/base.apk@classes.dex

        /** New install */
        FileInstallArgs(InstallParams params) {
            super(params.origin, params.move, params.observer, params.installFlags,
                    params.installerPackageName, params.volumeUuid, params.getManifestDigest(),
                    params.getUser(), null /* instruction sets */, params.packageAbiOverride,
                    params.grantedRuntimePermissions);
            if (isFwdLocked()) {
                throw new IllegalArgumentException("Forward locking only supported in ASEC");
            }
        }

        /** Existing install */
        FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
            super(OriginInfo.fromNothing(), null, null, 0, null, null, null, null, instructionSets,
                    null, null);
            this.codeFile = (codePath != null) ? new File(codePath) : null;
            this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
        }
    ...
    }

注意:它的兩個構(gòu)造函數(shù)通過注釋我們知道,帶有InstallParams參數(shù)的構(gòu)造函數(shù)是新安裝,而三個參數(shù)的構(gòu)造函數(shù)則是更新操作的構(gòu)造函數(shù)。

所以他們的關(guān)系如下圖:

InstallParams與InstallArgs的關(guān)系.png

同理:AsecInstallArgs類和FileInstallArgs一樣 也有兩個構(gòu)造函數(shù),一個是一個InstallParams參數(shù)的,用于新安裝,其中還有一個多參數(shù)的構(gòu)造函數(shù),用于更新安裝

七、為什么新安裝的情況下 origin.staged等于false

先找到這個問題的位置,這個問題是在handleStartCopy()方法里面涉及到下面代碼:

            if (origin.staged) {
                if (origin.file != null) {
                    installFlags |= PackageManager.INSTALL_INTERNAL;
                    installFlags &= ~PackageManager.INSTALL_EXTERNAL;
                } else if (origin.cid != null) {
                    installFlags |= PackageManager.INSTALL_EXTERNAL;
                    installFlags &= ~PackageManager.INSTALL_INTERNAL;
                } else {
                    throw new IllegalStateException("Invalid stage location");
                }
            }

里面的if判斷為false。

如果想獲取origin.staged,就必須要要知道origin是什么時候初始化的。我們知道了origin是在發(fā)送what值為INIT_COPY的Message的時候初始化的
代碼在PackageManagerService里面的installPackageAsUser方法里面:
代碼在PackageManagerService.java 9569行

      final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);

由于OriginInfo是PackageManagerService的內(nèi)部類,我們直接找到OriginInfo的fromUntrustedFile靜態(tài)方法

代碼在PackageManagerService.java 10408行

        static OriginInfo fromUntrustedFile(File file) {
            return new OriginInfo(file, null, false, false);
        }

我們看到fromUntrustedFile方法直接new了一個OriginInfo對象,而OriginInfo就一個構(gòu)造函數(shù),我們來看下構(gòu)造函數(shù)。

代碼在PackageManagerService.java 10424行

        private OriginInfo(File file, String cid, boolean staged, boolean existing) {
            this.file = file;
            this.cid = cid;
            this.staged = staged;
            this.existing = existing;

            if (cid != null) {
                resolvedPath = PackageHelper.getSdDir(cid);
                resolvedFile = new File(resolvedPath);
            } else if (file != null) {
                resolvedPath = file.getAbsolutePath();
                resolvedFile = file;
            } else {
                resolvedPath = null;
                resolvedFile = null;
            }
        }

我們看到staged對應(yīng)的第三個入?yún)ⅲ@個new OriginInfo(file, null, false, false)方法中第三個參數(shù)是false。所以我們說如果在新安裝的情況下origin.staged等于false

八、LocalSocket的跨進(jìn)程通信

(一)、Socket

Socket最初用于基于TCP/IP網(wǎng)絡(luò)間進(jìn)程通信中,以客戶端/服務(wù)器模式進(jìn)行通信。實現(xiàn)異步操作,共享資源集中處理,提高客戶端響應(yīng)能力

socketAPI 原本是未網(wǎng)絡(luò)通訊設(shè)計的,但后來在socket的框架上發(fā)展處一種IPC機(jī)制,就是UNIX Demain Socket。雖然網(wǎng)絡(luò)socket也可用于同一臺主機(jī)的進(jìn)程間通信(通過loopback地址127.0.0.1),但是UNIX Demain Socket 用于IPC更有效率:不需要經(jīng)過網(wǎng)絡(luò)協(xié)議棧,不需要打包拆包、計算校驗和、維護(hù)序列號和應(yīng)答等等,只是將應(yīng)用層數(shù)據(jù)從一個進(jìn)程寶貝到另一個進(jìn)程。這是因為,IPC機(jī)制本質(zhì)上是可靠的通信,而網(wǎng)絡(luò)協(xié)議是為不可靠的通訊設(shè)計的。UNIX Demain Socket也提供面向流和面向數(shù)據(jù)包兩種API接口,類似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。

UNIX Domain Socket是全雙工的,API接口語義豐富,相比其他IPC機(jī)制有明顯的優(yōu)越性,目前已成為使用最廣泛的IPC機(jī)制,比如Window服務(wù)器和GUI程序之間就是通過UNIX Domain Socket通訊的。

(二)、Android的進(jìn)程間通信

我們知道,Android上常見的進(jìn)程間通信有以下幾種情況:

  • AIDL進(jìn)程通信接口
  • Binder進(jìn)程通信接口
  • Message通信框架
  • Messager通信框架
  • BroadCastReciever廣播
  • ContentProvider

其實還有一種方案上就是基于Unix進(jìn)程通信的LocalSocket

(三)、LocalSocket的相關(guān)結(jié)構(gòu)

如下圖:


LocalSocket通信.png

里面涉及幾個概念

  • LocalSocket:客戶端的套接字,在Unix域名空間創(chuàng)建的一個套接字,是對Linux中Socket進(jìn)行了封裝,采用JNI方式調(diào)用,實現(xiàn)進(jìn)程間通信。具體就是Native層Server和Framework層Client進(jìn)行通信,或在各層次中能使用Client/Server模式實現(xiàn)通信
  • LocalSocketAddress:套接字地址,其實就是文件描述符(主要是服務(wù)器地址,當(dāng)然也可以客戶端自己綁定地址)
  • LocalServerSocket:服務(wù)端的套接字,與LocalSocket相對應(yīng),創(chuàng)建套接字同時制定文件描述符
  • LocalSocketImpl:Framework層Socket的實現(xiàn),通過JNI調(diào)用系統(tǒng)socket API
  • JNI訪問接口:frameworks/base/core/jni/android_net_LocalSocketImpl.cpp)里面幾個核心方法
    • socket_connect_local
    • socket_bind_local
    • socket_listen

看下這幾個類的對應(yīng)關(guān)系,如下圖:


對應(yīng)關(guān)系.png

使用Android的LocalSocket建立socket通信,是基于網(wǎng)絡(luò)socket過程一致的。

九、createInstallArgs(InstallParams)方法解答

先看下這個方法在哪里被調(diào)用了?
是在handleStartCopy()方法里面被調(diào)用
代碼在PackageManagerService.java 10669行

final InstallArgs args = createInstallArgs(this);

我們來看下方法內(nèi)部的執(zhí)行

代碼在PackageManagerService.java 10669行

   private InstallArgs createInstallArgs(InstallParams params) {
        if (params.move != null) {
            return new MoveInstallArgs(params);
        } else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
            return new AsecInstallArgs(params);
        } else {
            return new FileInstallArgs(params);
        }
    }

這里面分別根據(jù)move字段和installOnExternalAsec方法來進(jìn)入不同分支來進(jìn)行分支判斷
那我們一個一個來判斷,我們看下InstallParams的move的值

1、判斷params.move是否為null

這時候我們要看下InstallParams的初始化地方在在PackageManagerService的installPackageAsUser()方法里面
代碼在PackageManagerService.java 9572行

        msg.obj = new InstallParams(origin, null, observer, installFlags, installerPackageName,
                null, verificationParams, user, packageAbiOverride, null);

大家注意下InstallParams的參數(shù),下面我們來看下InstallParams的構(gòu)造函數(shù)

        InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
                int installFlags, String installerPackageName, String volumeUuid,
                VerificationParams verificationParams, UserHandle user, String packageAbiOverride,
                String[] grantedPermissions) {
            super(user);
            this.origin = origin;
            this.move = move;
            this.observer = observer;
            this.installFlags = installFlags;
            this.installerPackageName = installerPackageName;
            this.volumeUuid = volumeUuid;
            this.verificationParams = verificationParams;
            this.packageAbiOverride = packageAbiOverride;
            this.grantedRuntimePermissions = grantedPermissions;
        }

其中我們看到第二個參數(shù)是對應(yīng)的move字段,而在new InstallParams對象的時候,我看到第二個參數(shù)是null。而在后續(xù)的整個流程,并沒有給這move字段賦值,所以params.move等于null

結(jié)論:params.move等于null。

2、判斷installOnExternalAsec(params.installFlag)和params.isForwardLocked()的值

首先我們來看下params.installFlag的值,通過上面InstallParams的值我們知道InstallParams的installFlag其實在構(gòu)造InstallParams的時候,傳入的變量installFlags,那我們向前捋捋,看看這個這個installFlags是什么時候初始化的,后續(xù)是否有發(fā)生什么值變化。我們發(fā)現(xiàn)這個installFlags其實是installPackageAsUser()的入?yún)ⅲ俏覀兙驮傧蚯罢?

發(fā)現(xiàn)在InstallAppProgress.java 228行的的initView() 方法里面

    public void initView() {
        ...
        int installFlags = 0;
        ...
    }

我們發(fā)現(xiàn)installFlags等于0,并且"新安裝"的情況下,是沒有變更installFlags的值的。所以在PackageService的installPackageAsUser方法里面的入?yún)nstallFlags也是0,在進(jìn)入installPackageAsUser里面有變更installFlags的值地方即在PackageManagerService.java 9546行

            installFlags &= ~PackageManager.INSTALL_FROM_ADB;
            installFlags &= ~PackageManager.INSTALL_ALL_USERS;

通過代碼我們知道
它顯示先"取反",然后依次進(jìn)行位"與"操作。不好意思。由于篇幅管理,我這里就不后續(xù)跟蹤,因為跟蹤的內(nèi)容太多了。希望大家理解。最后的結(jié)果是installOnExternalAsec(params.installFlags)是false和params.isForwardLocked()也是false。所以這個方法最后返回的是FileInstallArgs

十、isVerificationEnabled(int userId, int installFlags) 的理解

代碼在PackageManagerService.java 9957行

    /**
     * Check whether or not package verification has been enabled.
     *
     * @return true if verification should be performed
     */
    private boolean isVerificationEnabled(int userId, int installFlags) {
         // DEFAULT_VERIFY_ENABLE是個常量,為true
        if (!DEFAULT_VERIFY_ENABLE) {
            return false;
        }
        // 檢查是否是否受限用戶,如果是受限用戶,則要進(jìn)行檢查
        boolean ensureVerifyAppsEnabled = isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS);

        // Check if installing from ADB
         // 如果是 從通過ADB安裝
        if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
            // Do not run verification in a test harness environment
           // 如果是測試工具,則不用檢查
            if (ActivityManager.isRunningInTestHarness()) {
                return false;
            }
             // 如果是受限用戶,則要進(jìn)行檢查
            if (ensureVerifyAppsEnabled) {
                return true;
            }
            // Check if the developer does not want package verification for ADB installs
             // 如果開發(fā)設(shè)置了在ADB安裝的時候不需要檢查包,則不用檢查
            if (android.provider.Settings.Global.getInt(mContext.getContentResolver(),
                    android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) == 0) {
                return false;
            }
        }

        // 如果是受限用戶,則一定要進(jìn)行包檢驗
        if (ensureVerifyAppsEnabled) {
            return true;
        }

        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
                android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 1;
    }

先翻譯一下注釋:

檢查是否啟用包驗證
如果執(zhí)行驗證,則返回true

上面的注釋已經(jīng)解釋的很清楚了,讓我們來看下最后一行代碼

android.provider.Settings.Global.getInt(mContext.getContentResolver(),
                android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) 

這行的代碼意思如下:

PackageManagerServcie在安裝之前是否發(fā)送廣播以驗證應(yīng)用

  • 1:表示 如果驗證者存在,則在安裝應(yīng)用之前進(jìn)行包驗證,
  • 0:表示 安裝器那不要驗證應(yīng)用程序

十一、Context.sendBroadcast(Intent intent)的功能是和Context.sendBroadcastAsUser(Intent,UserHandle)一樣的解答

1、Context.sendBroadcast(Intent intent)的具體實現(xiàn)

我們知道Context是一個抽象類,而具體實現(xiàn)類是ContextImpl。所以Context.sendBroadcast(Intent intent)的具體實現(xiàn)如下:
代碼在ContextImpl.java 762行

    @Override
    public void sendBroadcast(Intent intent) {
        warnIfCallingFromSystemProcess();
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            intent.prepareToLeaveProcess();
            ActivityManagerNative.getDefault().broadcastIntent(
                    mMainThread.getApplicationThread(), intent, resolvedType, null,
                    Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
                    getUserId());
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
    }

2、Context. sendBroadcastAsUsersendBroadcastAsUser(Intent, UserHandle)的具體實現(xiàn)

我們知道Context是一個抽象類,而具體實現(xiàn)類是ContextImpl。所以Context.sendBroadcast(Intent intent)的具體實現(xiàn)如下:
代碼在ContextImpl.java 923行

    @Override
    public void sendBroadcastAsUser(Intent intent, UserHandle user) {
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            intent.prepareToLeaveProcess();
            ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(),
                    intent, resolvedType, null, Activity.RESULT_OK, null, null, null,
                    AppOpsManager.OP_NONE, null, false, false, user.getIdentifier());
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
    }

其實大家自己對比兩個方法,會發(fā)現(xiàn),這兩個方法其實都是調(diào)用ActivityManagerNative.getDefault().broadcastIntent方法而已,唯一的不同是,最后一個參數(shù)不同:sendBroadcast最后一個參數(shù)是getUserId(),而sendBroadcastAsUser方法最后一個參數(shù)是user.getIdentifier()。

這樣我們在看下getUserId()方法里面的具體內(nèi)容,如下圖:
代碼在ContextImpl.java 1770行

    /** {@hide} */
    @Override
    public int getUserId() {
        return mUser.getIdentifier();
    }

我們發(fā)現(xiàn)getUserId()方法內(nèi)部也是調(diào)用的mUser.getIdentifier(),所以我們說

Context.sendBroadcast(Intent intent)的功能是和Context.sendBroadcastAsUser(Intent,UserHandle)一樣的

十二、Split APK(APK拆分)與Instant Run簡介

如果想了解官網(wǎng),推薦Android官方技術(shù)文檔翻譯——Apk 拆分機(jī)制

(一)、什么是Split APK(APK 拆分)

Split APK是Google為了解決66536上線,以及APK安裝包越來越大等問題,在Android 5.0中引入的一種機(jī)制。Split APK可以將一個龐大的APK文件,按屏幕密度、ABI等形式拆分成多個獨立的APK,在應(yīng)用程序更新時,不必下載整個APK,只需要下載某個某塊即可安裝更新。Split APK 將原來一個APK中多個模塊共享一份資源的模型分離成多個APK使用各自的資源,并且可以繼承Base APK中的資源,多個APK有相同的data、cache目錄、多個dex文件、相同的進(jìn)程,在Settings.apk中只顯示一個APK,并且使用相同的包名。

如下圖


Splite APK.png

PS:在Android Studio 2.3上,instant run的部署方案與之前的版本相比有了很大變化,之前是通過分dex來實現(xiàn)動態(tài)部署,而從Android Studio 2.3上則是通過Split APK技術(shù)。而在Android Studio 2.2,只有部署到Android Studio 6.0以上的設(shè)備才會使用Split APK 方案。 Android Studio 2.3則是連Android 5.0都會使用Split APK。在安裝時會通過adb install-multiple 指令一次性安裝。

(二)、Splite APK效果圖及解析

通俗的理解,之前我們是一個APK,而現(xiàn)在是通過Splite APK,則在我們的APK安裝目錄下有多個APK。如下圖:

多APK.png

看上圖,一個外殼base.apk,一個依賴split_lib_dependencies_apk,然后將我們的業(yè)務(wù)代碼分成了10份,其中base.apk中的dex只包含了instant-run-server的代碼以及我們在AndroidManifest、資源文件等。查看這些分割的APK文件,我們會發(fā)現(xiàn)里面只有一個dex、AndroidManifest和mf文件夾。打開AndroidManifest文件,僅有一個manifest標(biāo)簽,然后有個split屬性。而base.apk的manifest文件是沒有這個屬性的。從安裝的源碼可以看出,安裝時必須要有一個apk是沒有這個屬性的,這個就是base.apk

整個原理就是比較簡單了,生成多個apk文件,把資源文件、manifest等放到base.apk,然后把業(yè)務(wù)dex分散到其他apk去,并且加入一個空的manifest文件,并指定split屬性。在打包的過程中,業(yè)務(wù)dex不再打入主apk,而是和各自的manifest文件打包成新的apk。

要說Split APK就不得不說下 Instant Run,我們在這里簡單的介紹下Instant Run

(三)、Instant Run簡介

Instant Run官網(wǎng)

1、 Instant Run 介紹

Instant Run,是android studio 2.0新增的一個運(yùn)行機(jī)制,在編碼開發(fā)、測試或debug的時候,它能顯著減少你對當(dāng)前APP"構(gòu)建"和"部署"的時間。當(dāng)我們第一次點擊run、debug按鈕的時候,它運(yùn)行時間和我們平常一樣,但是在后續(xù)的流程中,你每次修改代碼后,點擊run、debug按鈕,對應(yīng)的"改變"將迅速的部署到你正在運(yùn)行的程序上,速度超級快。

2、產(chǎn)生Instant Run的背景

在沒有Instant Run的時候,我們一般修改代碼,然后點擊"run"的流程如此:構(gòu)建->部署->安裝->app登錄->activity創(chuàng)建
如下圖:

Instant Run.png

每一次都是重新安裝,但是這樣會導(dǎo)致大量的時間花在"構(gòu)建->部署->安裝->app登錄->activity創(chuàng)建"上,這樣就產(chǎn)生了一個需求,能否縮短這個時間。所有就有了Instant Run

Instant Run產(chǎn)生的目的就是: 盡可能多的剔除不必要的流程,然后提升必要的流程的效率,從而縮短時間。

結(jié)合上圖的流程,大家想一下,怎樣才縮短時間?

只對代碼改變部分做構(gòu)建和部署,并不重新安裝應(yīng)用,并不重啟應(yīng)用,不重啟Activity,就就會大大縮短時間。

3、 Instant Run的分類

按照是否需要重啟當(dāng)前Activity、是否需要重啟APP(不是重新安裝)這兩個條件,把Instant Run分為3類:

  • Hot Swp——熱插拔
    改變的代碼被應(yīng)用投射到APP上,不需要重啟應(yīng)用,不需要重新啟動當(dāng)前Activity。
    一般適用簡單的改變的場景,比如一些方法簡單的修改等
  • Warm Swap——溫插拔
    Activity需要被重啟才能看到所需修改
    一般適用涉及到了資源文件的修改,比如Resources。
  • Cold Swap——冷插拔
    APP需要被重啟,這里說的重啟,并不是重新安裝。
    一般適用結(jié)構(gòu)性變化的場景,比如修改了繼承規(guī)則等。

如下圖:


Instant_Run的分類.png
4、Instant Run的原理

Manifest整合,然后跟res、dex.file一起被合并到APK


image.png

manifest文件合并、打包,和res一起被AAPT合并到APK中,同樣項目代碼被編譯成字節(jié)碼,然后轉(zhuǎn)換成.dex文件,也被合并到APK中。

下面我們來看下首次運(yùn)行Instant Run,Gradle執(zhí)行的操作。


首次執(zhí)行.png

在有Instant Run的環(huán)境下:一個新的App Server類會被注入到App中,與Bytecode instrumentation協(xié)同監(jiān)控代碼的變化。同時會有一個新的Application類,它注入了一個自定義類加載器(Class Loader),同時該Application類會啟動我們所需要的新注入的App Server。于是Manifest會被修改來確保我們的應(yīng)用能使用這個新的Application類(這里不比擔(dān)心自己繼承定義了Application類,Instant Run添加的這個新Application類會代理我們自定義的Application類)。至此Instant Run可以跑起來了,在我們使用的時候,它會通過決策,合理運(yùn)用熱溫冷插拔來協(xié)助我們大量地縮短構(gòu)建程序時間。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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