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簡(jiǎn)介
  • 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簡(jiǎn)介

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

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

(一) mContext是什么?

mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null)
要看這個(gè)方法內(nèi)部執(zhí)行蚜锨,首先要知道這個(gè)mContext是什么档插,我們知道這個(gè)mContext是通過PackageManagerService的main方法傳入的,所以這個(gè)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;
    }

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

代碼在ContextImpl.java 1468行

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

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

(二)ContextImpl#checkCallingOrSelfPermission(String) 方法 簡(jiǎn)介

代碼在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());
    }

這個(gè)方法首先做了入?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);這里先提前說下這個(gè)方法其實(shí)是調(diào)用的ActivityServcieManager的checkPermission(String,int ,int)方法伪朽,關(guān)于為什么會(huì)這樣轴咱,我們后面講解Activity的啟動(dòng)流程的時(shí)候會(huì)詳細(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);
    }

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

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

通過上面注釋,我們知道了這是一個(gè)全局的檢查權(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);
    }

通過注釋我們知道敷存,這個(gè)方法可以在有全局鎖定或者沒有全局鎖定的時(shí)候調(diào)用墓造。這個(gè)方法內(nèi)部只做了一個(gè)MY_PID的判斷,如果pid=MY_PID锚烦,而MY_PID其實(shí)就是當(dāng)前進(jìn)程的pid觅闽,則直接返回PackageManager.PERMISSION_GRANTED,而PackageManager.PERMISSION_GRANTED表示的意思是"授予"涮俄,即檢查通過谱煤。那我們來看下ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);這個(gè)方法的具體執(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,如果是直接返回"授予"禽拔,因?yàn)樗鼈儞碛凶畲髾?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.
       // 如果是同一個(gè)應(yīng)用,則不需要監(jiān)測(cè)
        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) 則表明這個(gè)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;
    }

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

方法內(nèi)部注釋已經(jīng)解釋的很清楚了,這里說下最后的AppGlobals.getPackageManager().checkUidPermission(permission,uid)方法舀患,如果成功調(diào)用則直接放回徽级,如果拋異常了,則返回拒絕(PackageManager.PERMISSION_DENIED)聊浅,那我們就來看下AppGlobals.getPackageManager().checkUidPermission(permission, uid);里面的具體實(shí)現(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對(duì)象碳锈。所以AppGlobals.getPackageManager().checkUidPermission(permission, uid);這個(gè)方法其實(shí)可以理解為PackageManagerService#checkUidPermission(permission, uid)方法

代碼在PackageManagerService.java 3190行

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

        synchronized (mPackages) {
            // 獲取這個(gè)uid對(duì)應(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;
                // 獲取對(duì)應(yīng)的permissionsState 
                final PermissionsState permissionsState = ps.getPermissionsState();
                 // 如果permissionsState  里面包含這個(gè)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)級(jí)應(yīng)用uid 對(duì)應(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的源碼绞呈,所以這個(gè)的代碼和Android 5.0是不同的贸人,所以有心的同學(xué)可以去對(duì)比下,Android6.0里面多了一個(gè)PermissionsState报强,Android 6.0以后是對(duì)權(quán)限的操作是PermissionsState灸姊。有興趣的同學(xué)可以研究下PermissionsState拱燃,和它的hasPermission(String name, int userId)方法秉溉,這里面包含了除了聲明的權(quán)限,還必須是授權(quán)的權(quán)限碗誉。

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

(三)弄跌、ContextImpl#enforce(String,int,boolean,int,String)方法簡(jiǎn)介

代碼在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 +
                    ".");
        }
    }

這里面很簡(jiǎn)單,主要判斷是上面checkCallingOrSelfPermission方法的返回值尝苇,如果不是PackageManager.PERMISSION_GRANTED則直接拋異常铛只,如果是,則什么也不做糠溜。

(四)淳玩、總結(jié)

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

二、DefaultContainerService詳解

DefaultContainerService.java源碼地址

(一)红柱、DefaultContainerService類簡(jiǎn)介

/**
 * 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è)計(jì)者的意圖,我們還是看下面的注釋

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

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

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

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

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

(三)纽什、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) {
            }
        }
    }

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

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

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

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

那我們就依次來看下

1让蕾、pm. nextPackageToClean(PackageCleanItem)方法

我們知道pm其實(shí)是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;
    }

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

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

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

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

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

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

2揭糕、UserEnvironment類及其方法

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

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

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

代碼在DefaultContainerService.java 290行

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

我們看到這個(gè)方法最后調(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里面有一個(gè)重要變量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類型蝠咆,看到這個(gè)類型啡邑,大家一定很熟了,對(duì)的一看就是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是什么奠衔?

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

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

要想知道這個(gè)方法的具體流程,首要先要明確mContainerService是一個(gè)什么東西塘娶。
而mContainerService其實(shí)是IMediaContainerService類型的归斤,如下

    private IMediaContainerService mContainerService = null;

我們?cè)賮碚蚁耺ContainerService初始化的位置:
在doHandleMessage(Message)方法里面MCS_BOUND的時(shí)候初始化的,在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對(duì)象mDefContainerConn"綁定"連接DefaultContainerService的時(shí)候執(zhí)行onServiceConnected的時(shí)候初始化的刁岸。如下:
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其實(shí)就是上面方法通過 IMediaContainerService.Stub.asInterface(service)來獲取的脏里,通過AIDL知識(shí)我們知道其實(shí)對(duì)應(yīng)的是DefaultContainerService的內(nèi)部變量mBinder。所以說

mContainerService對(duì)應(yīng)著DefaultContainerService的成員變量mBinder虹曙。所以mContainerService.getMinimalPackageInfo(String.int,String)方法對(duì)應(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ù)制包的絕對(duì)路徑根吁。這個(gè)目錄可以包含單個(gè)APK也可以包含多個(gè)APK

我將這個(gè)方法分為三個(gè)部分

  • 第一步:初始化一些信息
  • 第二步:解析packagePath對(duì)應(yīng)的安裝包员淫,獲取解析的出來的"輕"安裝包pkg
  • 第三步:把解析出來的"輕"安裝包的屬性賦值給PackageInfoLite對(duì)象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;
            }
        }

先來看下注釋:

計(jì)算安裝包安裝后可能的大小

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

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

(一)先說下這個(gè)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其實(shí)就是ContextImpl,所以我們看下這個(gè)bindServiceAsUser方法的具體實(shí)現(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)部其實(shí)是用調(diào)用bindServiceCommon這個(gè)方法來實(shí)現(xiàn)的具體的邏輯的沃斤,所以說bindService方法和bindServiceAsUser其實(shí)內(nèi)部的執(zhí)行邏輯是一直的

五、HandlerParams與InstallParams簡(jiǎn)介

在PackageManagerService進(jìn)行安裝的時(shí)候會(huì)涉及兩個(gè)概念即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è)構(gòu)造函數(shù)牲证。而且在構(gòu)造這個(gè)類的時(shí)候哮针,需要傳入一個(gè)UserHandle
它里面有三個(gè)抽象方法

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

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

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

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

小結(jié)=·

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

下面我們就來看下InstallParams類

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

代碼在PackageManagerService.java 10464行

    class InstallParams extends HandlerParams {
             ...
    }

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

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

image.png

所以說HandlerParams有兩個(gè)子類娩嚼,分別是InstallParams和MeasureParams蘑险。

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

五漠其、InstallArgs家族成員

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

    static abstract class InstallArgs {
       ...
    }

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

我們找到了三個(gè)子類如下:

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

設(shè)計(jì)這四個(gè)類的目的是什么意義柴信?

這樣設(shè)計(jì)的目的是:APK可以安裝在內(nèi)部存儲(chǔ)空間或者SD卡上,已經(jīng)安裝的APK也可以在內(nèi)部存儲(chǔ)和SD之間進(jìn)行移動(dòng)宽气,PackageManagerService為此設(shè)計(jì)了InstallArgs這個(gè)抽象類的數(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è)構(gòu)造函數(shù)通過注釋我們知道绪氛,帶有InstallParams參數(shù)的構(gòu)造函數(shù)是新安裝,而三個(gè)參數(shù)的構(gòu)造函數(shù)則是更新操作的構(gòu)造函數(shù)涝影。

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

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

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

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

先找到這個(gè)問題的位置伯襟,這個(gè)問題是在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是什么時(shí)候初始化的姆怪。我們知道了origin是在發(fā)送what值為INIT_COPY的Message的時(shí)候初始化的
代碼在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了一個(gè)OriginInfo對(duì)象,而OriginInfo就一個(gè)構(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對(duì)應(yīng)的第三個(gè)入?yún)ⅲ@個(gè)new OriginInfo(file, null, false, false)方法中第三個(gè)參數(shù)是false淀衣。所以我們說如果在新安裝的情況下origin.staged等于false

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

(一)、Socket

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

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

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

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

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

如下圖:


LocalSocket通信.png

里面涉及幾個(gè)概念

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

看下這幾個(gè)類的對(duì)應(yīng)關(guān)系渐行,如下圖:


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

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

九、createInstallArgs(InstallParams)方法解答

先看下這個(gè)方法在哪里被調(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)行分支判斷
那我們一個(gè)一個(gè)來判斷粟害,我們看下InstallParams的move的值

1、判斷params.move是否為null

這時(shí)候我們要看下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;
        }

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

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

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

首先我們來看下params.installFlag的值,通過上面InstallParams的值我們知道InstallParams的installFlag其實(shí)在構(gòu)造InstallParams的時(shí)候豌习,傳入的變量installFlags存谎,那我們向前捋捋,看看這個(gè)這個(gè)installFlags是什么時(shí)候初始化的肥隆,后續(xù)是否有發(fā)生什么值變化既荚。我們發(fā)現(xiàn)這個(gè)installFlags其實(shí)是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ù)跟蹤,因?yàn)楦櫟膬?nèi)容太多了件蚕。希望大家理解孙技。最后的結(jié)果是installOnExternalAsec(params.installFlags)是false和params.isForwardLocked()也是false。所以這個(gè)方法最后返回的是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是個(gè)常量,為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
           // 如果是測(cè)試工具,則不用檢查
            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安裝的時(shí)候不需要檢查包裳瘪,則不用檢查
            if (android.provider.Settings.Global.getInt(mContext.getContentResolver(),
                    android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) == 0) {
                return false;
            }
        }

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

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

先翻譯一下注釋:

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

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

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

這行的代碼意思如下:

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

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

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

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

我們知道Context是一個(gè)抽象類,而具體實(shí)現(xiàn)類是ContextImpl愈腾。所以Context.sendBroadcast(Intent intent)的具體實(shí)現(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)的具體實(shí)現(xiàn)

我們知道Context是一個(gè)抽象類,而具體實(shí)現(xiàn)類是ContextImpl虱黄。所以Context.sendBroadcast(Intent intent)的具體實(shí)現(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);
        }
    }

其實(shí)大家自己對(duì)比兩個(gè)方法悦即,會(huì)發(fā)現(xiàn),這兩個(gè)方法其實(shí)都是調(diào)用ActivityManagerNative.getDefault().broadcastIntent方法而已橱乱,唯一的不同是辜梳,最后一個(gè)參數(shù)不同:sendBroadcast最后一個(gè)參數(shù)是getUserId(),而sendBroadcastAsUser方法最后一個(gè)參數(shù)是user.getIdentifier()泳叠。

這樣我們?cè)诳聪耮etUserId()方法里面的具體內(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簡(jiǎn)介

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

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

Split APK是Google為了解決66536上線种蝶,以及APK安裝包越來越大等問題契耿,在Android 5.0中引入的一種機(jī)制。Split APK可以將一個(gè)龐大的APK文件螃征,按屏幕密度搪桂、ABI等形式拆分成多個(gè)獨(dú)立的APK,在應(yīng)用程序更新時(shí)会傲,不必下載整個(gè)APK锅棕,只需要下載某個(gè)某塊即可安裝更新。Split APK 將原來一個(gè)APK中多個(gè)模塊共享一份資源的模型分離成多個(gè)APK使用各自的資源淌山,并且可以繼承Base APK中的資源,多個(gè)APK有相同的data顾瞻、cache目錄泼疑、多個(gè)dex文件、相同的進(jìn)程荷荤,在Settings.apk中只顯示一個(gè)APK退渗,并且使用相同的包名移稳。

如下圖


Splite APK.png

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

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

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

多APK.png

看上圖钱雷,一個(gè)外殼base.apk,一個(gè)依賴split_lib_dependencies_apk吹零,然后將我們的業(yè)務(wù)代碼分成了10份急波,其中base.apk中的dex只包含了instant-run-server的代碼以及我們?cè)贏ndroidManifest、資源文件等瘪校。查看這些分割的APK文件澄暮,我們會(huì)發(fā)現(xiàn)里面只有一個(gè)dex、AndroidManifest和mf文件夾阱扬。打開AndroidManifest文件泣懊,僅有一個(gè)manifest標(biāo)簽,然后有個(gè)split屬性麻惶。而base.apk的manifest文件是沒有這個(gè)屬性的馍刮。從安裝的源碼可以看出,安裝時(shí)必須要有一個(gè)apk是沒有這個(gè)屬性的窃蹋,這個(gè)就是base.apk

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

要說Split APK就不得不說下 Instant Run大州,我們?cè)谶@里簡(jiǎn)單的介紹下Instant Run

(三)、Instant Run簡(jiǎn)介

Instant Run官網(wǎng)

1垂谢、 Instant Run 介紹

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

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

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

Instant Run.png

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

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

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

只對(duì)代碼改變部分做構(gòu)建和部署砰识,并不重新安裝應(yīng)用能扒,并不重啟應(yīng)用,不重啟Activity辫狼,就就會(huì)大大縮短時(shí)間初斑。

3、 Instant Run的分類

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

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

如下圖:


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

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


image.png

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

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


首次執(zhí)行.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市教翩,隨后出現(xiàn)的幾起案子杆勇,更是在濱河造成了極大的恐慌,老刑警劉巖饱亿,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚜退,死亡現(xiàn)場(chǎng)離奇詭異闰靴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)钻注,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蚂且,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人幅恋,你說我怎么就攤上這事杏死。” “怎么了捆交?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵淑翼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我品追,道長(zhǎng)玄括,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任肉瓦,我火速辦了婚禮遭京,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘风宁。我一直安慰自己洁墙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布戒财。 她就那樣靜靜地躺著热监,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饮寞。 梳的紋絲不亂的頭發(fā)上孝扛,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音幽崩,去河邊找鬼苦始。 笑死,一個(gè)胖子當(dāng)著我的面吹牛慌申,可吹牛的內(nèi)容都是我干的陌选。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蹄溉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼咨油!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柒爵,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤役电,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后棉胀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體法瑟,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冀膝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霎挟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窝剖。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖氓扛,靈堂內(nèi)的尸體忽然破棺而出枯芬,到底是詐尸還是另有隱情论笔,我是刑警寧澤采郎,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站狂魔,受9級(jí)特大地震影響蒜埋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜最楷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一整份、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧籽孙,春花似錦烈评、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至适瓦,卻和暖如春竿开,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玻熙。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工否彩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嗦随。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓列荔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親枚尼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贴浙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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