Android PMS處理APK的複製

2021-03-02 劉望舒
前言

在上一篇文章Android包管理機制之PackageInstaller安裝APK中,我們學習了PackageInstaller是如何安裝APK的,最後會將APK的信息交由PMS處理。那麼PMS是如何處理的呢?主要是APK的複製和安裝,由於公號文章字數的限制,這篇文章只能介紹 PMS處理APK的複製,APK安裝過程會在後續文章講解。

1.PackageHandler處理安裝消息

APK的信息交由PMS後,PMS通過向PackageHandler發送消息來驅動APK的複製和安裝工作。
先來查看PackageHandler處理安裝消息的調用時序圖。

接著上一篇文章的代碼邏輯來查看PMS的installStage方法。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

 void installStage(String packageName, File stagedDir, String stagedCid,
            IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
            String installerPackageName, int installerUid, UserHandle user,
            Certificate[][] certificates) {
        ...
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        final int installReason = fixUpInstallReason(installerPackageName, installerUid,
                sessionParams.installReason);
        final InstallParams params = new InstallParams(origin, null, observer,
                sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
                verificationInfo, user, sessionParams.abiOverride,
                sessionParams.grantedRuntimePermissions, certificates, installReason);
        params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
        msg.obj = params;
        ...
        mHandler.sendMessage(msg);
    }

注釋2處創建InstallParams,它對應於包的安裝數據。注釋1處創建了類型為INIT_COPY的消息,在注釋3處將InstallParams通過消息發送出去。

1.1 對INIT_COPY的消息的處理

處理INIT_COPY類型的消息的代碼如下所示。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler

 void doHandleMessage(Message msg) {
     switch (msg.what) {
         case INIT_COPY: {
            HandlerParams params = (HandlerParams) msg.obj;
            int idx = mPendingInstalls.size();
            if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                 
               if (!mBound) {
                   Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                      System.identityHashCode(mHandler));
                  
                  if (!connectToService()) {
                      Slog.e(TAG, "Failed to bind to media container service");
                      params.serviceError();
                      Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                                    System.identityHashCode(mHandler));
                     if (params.traceMethod != null) {
                         Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod,
                                        params.traceCookie);
                        }
                            
                       return;
                      } else {
                            
                        mPendingInstalls.add(idx, params);
                        }
                    } else {
                    
                      mPendingInstalls.add(idx, params);
                       if (idx == 0) {
                          mHandler.sendEmptyMessage(MCS_BOUND);
                        }
                    }
                    break;
                }
                ....
        }
    }
 }

PackageHandler繼承自Handler,它被定義在PMS中,doHandleMessage方法用於處理各個類型的消息,來查看對INIT_COPY類型消息的處理。注釋1處的mBound用於標識是否綁定了DefaultContainerService,默認值為false。DefaultContainerService是用於檢查和複製可移動文件的服務,這是一個比較耗時的操作,因此DefaultContainerService沒有和PMS運行在同一進程中,它運行在com.android.defcontainer進程,通過IMediaContainerService和PMS進行IPC通信,如下圖所示。

注釋2處的connectToService方法用來綁定DefaultContainerService,注釋3處發送MCS_BOUND類型的消息,觸發處理第一個安裝請求。
查看注釋2處的connectToService方法:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler

  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;
        }

注釋2處如果綁定DefaultContainerService成功,mBound會置為ture 。注釋1處的bindServiceAsUser方法會傳入mDefContainerConn,bindServiceAsUser方法的處理邏輯和我們調用bindService是類似的,服務建立連接後,會調用onServiceConnected方法:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java 

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

注釋1處發送了MCS_BOUND類型的消息,與PackageHandler.doHandleMessage方法的注釋3處不同的是,這裡發送消息帶了Object類型的參數,這裡會對這兩種情況來進行講解,一種是消息不帶Object類型的參數,一種是消息帶Object類型的參數。

1.2 對MCS_BOUND類型的消息的處理

消息不帶Object類型的參數

查看對MCS_BOUND類型消息的處理:

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

case MCS_BOUND: {
            if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
            if (msg.obj != null) {
                mContainerService = (IMediaContainerService) msg.obj;
                Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                        System.identityHashCode(mHandler));
            }
            if (mContainerService == null) {
                if (!mBound) {
                      Slog.e(TAG, "Cannot bind to media container service");
                      for (HandlerParams params : mPendingInstalls) {
                          params.serviceError();
                          Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                                        System.identityHashCode(params));
                          if (params.traceMethod != null) {
                          Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER,
                           params.traceMethod, params.traceCookie);
                          }
                          return;
                      }   
                          
                          mPendingInstalls.clear();
                   } else {
                          
                          Slog.w(TAG, "Waiting to connect to media container service");
                   }
            } else if (mPendingInstalls.size() > 0) {
              ...
              else {
                   Slog.w(TAG, "Empty queue");
                   }
            break;
        }

如果消息不帶Object類型的參數,就無法滿足注釋1處的條件,注釋2處的IMediaContainerService類型的mContainerService也無法被賦值,這樣就滿足了注釋3處的條件。
如果滿足注釋4處的條件,說明還沒有綁定服務,而此前已經在PackageHandler.doHandleMessage方法的注釋2處調用綁定服務的方法了,這顯然是不正常的,因此在注釋5處負責處理服務發生錯誤的情況。如果不滿足注釋4處的條件,說明已經綁定服務了,就會列印出系統log,告知用戶等待系統綁定服務。

消息帶Object類型的參數
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

case MCS_BOUND: {
            if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
            if (msg.obj != null) {
            ...
            }
            if (mContainerService == null) {
             ...
            } else if (mPendingInstalls.size() > 0) {
                          HandlerParams params = mPendingInstalls.get(0);
                        if (params != null) {
                            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                                    System.identityHashCode(params));
                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
                            if (params.startCopy()) {
                                if (DEBUG_SD_INSTALL) Log.i(TAG,
                                        "Checking for more work or unbind...");
                                 
                                if (mPendingInstalls.size() > 0) {
                                    mPendingInstalls.remove(0);
                                }
                                if (mPendingInstalls.size() == 0) {
                                    if (mBound) {
                                    
                                        if (DEBUG_SD_INSTALL) Log.i(TAG,
                                                "Posting delayed MCS_UNBIND");
                                        removeMessages(MCS_UNBIND);
                                        Message ubmsg = obtainMessage(MCS_UNBIND);
                                        sendMessageDelayed(ubmsg, 10000);
                                    }
                                } else {
                                    if (DEBUG_SD_INSTALL) Log.i(TAG,
                                            "Posting MCS_BOUND for next work");
                                   
                                    mHandler.sendEmptyMessage(MCS_BOUND);
                                }
                            }
                            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                        }else {
                        Slog.w(TAG, "Empty queue");
                    }
            break;
        }

如果MCS_BOUND類型消息帶Object類型的參數就不會滿足注釋1處的條件,就會調用注釋2處的判斷,如果安裝請求數不大於0就會列印出注釋6處的log,說明安裝請求隊列是空的。安裝完一個APK後,就會在注釋5處發出MSC_BOUND消息,繼續處理剩下的安裝請求直到安裝請求隊列為空。
注釋3處得到安裝請求隊列第一個請求HandlerParams ,如果HandlerParams 不為null就會調用注釋4處的HandlerParams的startCopy方法,用於開始複製APK的流程。

2.複製APK

先來查看複製APK的時序圖。

HandlerParams是PMS中的抽象類,它的實現類為PMS的內部類InstallParams。HandlerParams的startCopy方法如下所示。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#HandlerParams

 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;
        }

注釋1處的mRetries用於記錄startCopy方法調用的次數,調用startCopy方法時會先自動加1,如果次數大於4次就放棄這個安裝請求:在注釋2處發送MCS_GIVE_UP類型消息,將第一個安裝請求(本次安裝請求)從安裝請求隊列mPendingInstalls中移除掉。注釋4處用於處理複製APK後的安裝APK邏輯,第3小節中會再次提到它。注釋3處調用了抽象方法handleStartCopy,它的實現在InstallParams中,如下所示。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#InstallParams

     public void handleStartCopy() throws RemoteException {
            ...
            
            final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
            final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
            final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
            PackageInfoLite pkgLite = null;
            if (onInt && onSd) {
              
                Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
              
            } else if (onSd && ephemeral) {
                Slog.w(TAG,  "Conflicting flags specified for installing ephemeral on external");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
            } else {
                 
                pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
                        packageAbiOverride);
                if (DEBUG_EPHEMERAL && ephemeral) {
                    Slog.v(TAG, "pkgLite for install: " + pkgLite);
                }
            ...
            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                 
                int loc = pkgLite.recommendedInstallLocation;
                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
                    ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                } 
                ...
                }else{
                  loc = installLocationPolicy(pkgLite);
                  ...
                }
            }
            
            final InstallArgs args = createInstallArgs(this);
            mArgs = args;
            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                   ...
                if (!origin.existing && requiredUid != -1
                        && isVerificationEnabled(
                              verifierUser.getIdentifier(), installFlags, installerUid)) {
                      ...
                } else{
                    ret = args.copyApk(mContainerService, true);
                }
            }
            mRet = ret;
        }

handleStartCopy方法的代碼很多,這裡截取關鍵的部分。
注釋1處通過IMediaContainerService跨進程調用DefaultContainerService的getMinimalPackageInfo方法,該方法輕量解析APK並得到APK的少量信息,輕量解析的原因是這裡不需要得到APK的全部信息,APK的少量信息會封裝到PackageInfoLite中。接著在注釋2處確定APK的安裝位置。注釋3處創建了InstallArgs,InstallArgs 是一個抽象類,定義了APK的安裝邏輯,比如複製和重命名APK等,它有3個子類,都被定義在PMS中,如下圖所示。


其中FileInstallArgs用於處理安裝到非ASEC的存儲空間的APK,也就是內部存儲空間(Data分區),AsecInstallArgs用於處理安裝到ASEC中(mnt/asec)即SD卡中的APK。MoveInstallArgs用於處理已安裝APK的移動的邏輯。
對APK進行檢查後就會在注釋4處調用InstallArgs的copyApk方法進行安裝。
不同的InstallArgs子類會有著不同的處理,這裡以FileInstallArgs為例。FileInstallArgs的copyApk方法中會直接return FileInstallArgs的doCopyApk方法:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#FileInstallArgs

   private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
           ...
            try {
                final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
                
                final File tempDir =
                        mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
                codeFile = tempDir;
                resourceFile = tempDir;
            } catch (IOException e) {
                Slog.w(TAG, "Failed to create copy file: " + e);
                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            }
            ...
            int ret = PackageManager.INSTALL_SUCCEEDED;
            ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
            ...
            return ret;
        }

注釋1處用於創建臨時存儲目錄,比如/data/app/vmdl18300388.tmp,其中18300388是安裝的sessionId。注釋2處通過IMediaContainerService跨進程調用DefaultContainerService的copyPackage方法,這個方法會在DefaultContainerService所在的進程中將APK複製到臨時存儲目錄,比如/data/app/vmdl18300388.tmp/base.apk。目前為止APK的複製工作就完成了,接著就是APK的安裝過程了。

3.總結 

本文主要講解了PMS是如何處理APK複製的,主要有兩個步驟:

PackageInstaller安裝APK時會將APK的信息交由PMS處理,PMS通過向PackageHandler發送消息來驅動APK的複製和安裝工作。

PMS發送INIT_COPY和MCS_BOUND類型的消息,控制PackageHandler來綁定DefaultContainerService,完成複製APK等工作。

相關焦點

  • Android Studio打包apk,aar,jar包
    一片楓葉_劉超的博客地址:http://blog.csdn.net/qq_23547831作者編寫了github項目解析、android源碼分析以及產品研發多個專題,有興趣的可以關注下學習學習~文本我們將講解android studio打包apk,aar,jar包的相關知識。
  • android apk 防反編譯技術第一篇-加殼技術
    現在將最近學習成果做一下整理總結。學習的這些成果我會做成一個系列慢慢寫出來與大家分享,共同進步。這篇主要講apk的加殼技術,廢話不多說了直接進入正題。一、加殼技術原理 所謂apk的加殼技術和pc exe的加殼原理一樣,就是在程序的外面再包裹上另外一段代碼,保護裡面的代碼不被非法修改或反編譯,在程序運行的時候優先取得程序的控制權做一些我們自己想做的工作。
  • Android模擬器和安裝APK文件
    好了進入正題,今天要講的是關於android模擬器和apk鏡像文件的一些事情。一.如何正確的啟動模擬器(早於Android 1.5的開發版本跳過此步) :關於在eclipse裡面如何集成android這些問題就不說了,這寫問題我想還是不用在這裡廢話的。
  • 將兩個 Crosswalk* Android* APK 文件提交到 Google Play Store*...
    這兩個 APK 文件的名稱採用以下格式: AppName.android.crosswalk.x86.timestamp.apk, 例如 ExampleApp.android.crosswalk.x86.20140418132640.apk AppName.android.crosswalk.arm.
  • APK詳解一籮筐
    Dalvik 虛擬機不支持直接執行 JAVA 字節碼,所以會對編譯生成的 .class 文件進行翻譯、重構、解釋、壓縮等處理,這個處理過程是由 dx/d8/r8(這是一個工具,位置為$ANDROID_HOME/build-tools/(不同版本號)/dx
  • 34個Android常用adbshell命令匯總
    以下是常用adb命令adb常用命令大全顯示系統中全部Android平臺:android list targets顯示系統中全部AVD(模擬器):android list avd創建AVD(模擬器):android create avd –name 名稱 –target 平臺編號啟動模擬器:
  • 【Android基礎學習一】Android 常用 adb 命令總結
    設備上的文件或者文件夾複製到本地例如複製 Sdcard 下的 pull.txt 文件到 D 盤:adb pull sdcard/pull.txt d:\如果需要重命名為 rename.txt:adb pull sdcard/pull.txt d:\rename.txt注意權限,複製系統權限的目錄下的文件,需要
  • Android測試 常用adb 命令總結
    設備上的文件或者文件夾複製到本地例如複製 Sdcard 下的 pull.txt 文件到 D 盤:adb pull sdcard/pull.txt d:\如果需要重命名為 rename.txt:adb pull sdcard/pull.txt d:\rename.txt注意權限,複製系統權限的目錄下的文件,需要
  • android反編譯和防止反編譯的方法
    【IT168技術】android基於java的,而java反編譯工具很強悍,所以對正常apk應用程式基本上可以做到100%反編譯還原。  因此開發人員如果不準備開源自己的項目就需要知道怎樣防止反編譯和反編譯他人的項目來學習。
  • Android中的窗口——Activity
    > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  • Android安全幾道入門題目
    本文通過幾個題目可以讓你基本了解android中簡單的但比較經典的漏洞、以及簡單的android註冊機開發的思路。閱讀本文,你可能需要了解android逆向的基本知識和常用工具、非常簡單的java語言、smali的語法知識。本文適合android入門初學者,最基本的東西。大佬請無視!
  • 一次Android權限刪除經歷
    2.初步定位首先使用android studio查看了打包出來的apk中的Androidmanifest文件,發現其中確實存在RECEIVE_SMS權限,也就是說打包到apk中的Androidmanifest文件並不是app下的該文件,從android開發者官網中合併多個manifest文件的文檔來看,實際上打包到apk中的manifest文件是由多個menifest文件合併而來的,其合併順序如下
  • Android 通過adb shell am broadcast發送廣播
    1、安裝應用到模擬器:你可以使用adb從你的開發電腦上複製一個應用程式,並且將其安裝在一個模擬器/設備實例。像這樣做,使用install命令。這個install命令要求你必須指定你所要安裝的.apk文件的路徑: adb install <path_to_apk> 為了獲取更多的關於怎樣創建一個可以安裝在模擬器/設備實例上的.apk文件的信息,可參照Android Asset Packaging Tool (aapt).
  • Android 這些 Drawable 你都會用嗎?
    默認:disable;clamp:複製邊沿的顏色;repeat:水平和垂直方向重複繪製圖片;mirror:水平和垂直方向交替鏡像進行重複繪製下面以定義一個使用圖片作為背景的 Drawable 為例,展示 BitmapDrawable 的簡單實用方法。2.2 用法示例定義<?
  • Android構建過程分析
    下一步要進行的是通過javac命令將java源碼編譯成.class字節碼,用以編譯的classpath包含以下內容:android.jar,具體版本由targetSdkVersion指定;build.gradle中添加的第三方依賴;編譯後可對代碼進行混淆處理,主要包括刪除無用類、字節碼優化、重命名等操作,只需在build.gradle中配置混淆規則即可buildTypes
  • InjuredAndroid 1-5
    作者建議反編譯apk來解題,那就jadx-gui吧。前五題難度不大,適合初學者練手。註:FLAG已作打碼處理Github:https://github.com/B3nac/InjuredAndroid作者對這個項目的介紹:https://twitter.com/B3nac/status/1317185026677641218?
  • 通過ADB USB方法從計算機安裝Android應用APK
    如何從計算機(Windows / Mac OS X / Linux)使用ADB安裝Android應用–先決條件:啟用了開發人員選項和USB調試的Android手機–您的計算機或Mac必須安裝了Android ADB和Fastboot驅動程序–您打算安裝的Android應用的apk
  • apk瘦身;如何縮小體積呢?這篇文章來教你
    前言我們完成一個app後,都需要生成一個apk,然後上線,而apk的大小也一定程度的影響了用戶是否願意下載你的這個app,所以也就有了apk瘦身這門藝術。的結構既然要對一個apk瘦身,首先我們就得知道apk格式的文件內容。
  • 破解第一個Android程序
    /Library/apktool/framework/1.apk, -S, /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/res, -M, /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel
  • 開發總結:Android反編譯方法的總結
    文件:  將apk文件(為了方便起見放到tools目錄裡)用WinRAR等工具打開,將res/layout/main.xml解壓出來(也還是放在tools目錄裡哦)  打開main.xml文件,內容如下(一堆天文):