1. 程式人生 > >Android8.0 PackageManagerService相關 -- APK安裝和install 的變更和原始碼淺析

Android8.0 PackageManagerService相關 -- APK安裝和install 的變更和原始碼淺析

1.1           APK安裝和install

installd服務是用來執行程式包的安裝與解除安裝的

1.1.1          參考資料

//各版本支援的命令

//installer overview

//Android中installd程序存在的意義

http://blog.csdn.net/liqingxu2005/article/details/43447941

//install原始碼分析4.4

http://blog.csdn.net/yangwen123/article/details/11104397

////install 原始碼分析7.0

http://blog.csdn.net/gaugamela/article/details/52769139

//8.0 install原始碼有很大變化,概述見本文,詳細的需要自行分析程式碼

//apk安裝過程PMS的原始碼分析

//apk安裝過程overview

//4種安裝方法的程式碼分析,程式碼過時,可參考分析問題的方法

http://blog.csdn.net/wh_19910525/article/details/7909686

1.1.2          初始過程的變化

在android 7.0,installd的啟動設定不再放在init.rc裡面了,放在frameworks\native\cmds\installd\installd.rc,並frameworks\native\cmds\installd\Android.mk 裡設定這個rc檔案進行編譯,

LOCAL_STATIC_LIBRARIES := libdiskusage

LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk

LOCAL_INIT_RC :=installd.rc

LOCAL_CLANG := true

include $(BUILD_EXECUTABLE)

在android8.0,又發生了一些變化,install.rc裡建立了很多目錄,並將rc檔案放在frameworks/native/cmds/installd/Android.bp進行編譯,

 cc_binary {

    name:"installd",

    defaults:["installd_defaults"],

    srcs:["installd.cpp"],

    static_libs:["libdiskusage"],

    init_rc: ["installd.rc"],

}

1.1.3         IPC變化

在android7.0及之前,IPC通過socket完成;

在android8.0,IPC通過binder實現。

在下面檔案可看到變化

frameworks\base\core\java\com\android\internal\os\Installer.java

frameworks\native\cmds\installd\

 


1.1.4         Apk安裝

Apk安裝有幾種常見的場景:系統內建apk的安裝,adb install安裝,應用市場下載後點擊安裝或本地檔案點選安裝,靜默安裝等。

//開機PKMS掃描、解析  4.4

http://blog.csdn.net/luoshengyang/article/details/6766010

//安裝原始碼分析,開機安裝的java層 6.0

在7.0開始,安裝apk的函式createDataDirsLI()就沒有了,使用了新的邏輯,可從PackageManagerService.java的scanPackageDirtyLI繼續向下分析程式碼。

順便提下,6.0的scanPackageDirtyLI向下呼叫到installd的介面,操作檔案時使用了selinux_android_setfilecon,這裡和selinux的檔案訪問策略有關,有興趣的可以分析下。

//安裝原始碼分析,點選安裝的java層 6.0

在應用層,8.0的程式碼發生了一些變化,下面這個控制安裝介面的檔案沒有了,

/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java

,用InstallInstalling取代了它,

主要的檔案差異如下,針對不同裝置進行了相應處理,處理邏輯的變化就暫不分析了,



1.1.5          8.0 apk安裝

安裝應用的時候會發一個intent,

PMS解析intent,呼叫PackageInstallerActivity來啟動安裝介面,

會呼叫到PackageInstallerActivityonCreate—startInstallConfirm,在之後的onClick,執行startInstall,啟動android O新增加的安裝activity InstallInstalling

     private void startInstall() {

        // Startsubactivity to actually install the application

        Intent newIntent =new Intent();

       newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,

               mPkgInfo.applicationInfo);

       newIntent.setData(mPackageURI);

       newIntent.setClass(this, InstallInstalling.class);

        String installerPackageName =getIntent().getStringExtra(

               Intent.EXTRA_INSTALLER_PACKAGE_NAME);

        if(mOriginatingURI != null) {

           newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);

        }

        if (mReferrerURI!= null) {

           newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);

        }

        if(mOriginatingUid != VerificationParams.NO_UID) {

           newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);

        }

        if (installerPackageName != null) {

           newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,

                   installerPackageName);

        }

        if(getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {

            newIntent.putExtra(Intent.EXTRA_RETURN_RESULT,true);

           newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);

        }

        if(localLOGV)Log.i(TAG, "downloaded app uri="+mPackageURI);

       startActivity(newIntent);

        finish();

    }

在InstallInstalling的onCreate方法裡,有兩個主要步驟,一個是PackageParser.parsePackageLite,他解析apk裡面的部分資訊出來;另一個是getPackageInstaller().createSession,它負責建立一個會話,用於後續的安裝,會話是通過binder呼叫在PackageInstallerServices裡面建立的。

                     PackageParser.PackageLitepkg = PackageParser.parsePackageLite(file, 0);

                   mSessionId =getPackageManager().getPackageInstaller().createSession(params);

在InstallInstalling.java的onResume啟動一個非同步task InstallingAsyncTask,

     protected void onResume() {

        super.onResume();

        // This is thefirst onResume in a single life of the activity

        if(mInstallingTask == null) {

           PackageInstaller installer = getPackageManager().getPackageInstaller();

           PackageInstaller.SessionInfo sessionInfo =installer.getSessionInfo(mSessionId);

            if(sessionInfo != null && !sessionInfo.isActive()) {

               mInstallingTask = new InstallingAsyncTask();

               mInstallingTask.execute();

            } else {

                // we willreceive a broadcast when the install is finished

               mCancelButton.setEnabled(false);

               setFinishOnTouchOutside(false);

            }

        }

    }

InstallingAsyncTask主要是操作session,session也通過binder方式來操作,主要完成的工作是通過之前建立的session,建立資料夾,將檔案拷貝到安裝目錄下面,

         protected PackageInstaller.SessiondoInBackground(Void... params) {

           PackageInstaller.Session session;

            try {

                session =getPackageManager().getPackageInstaller().openSession(mSessionId);

            } catch (IOException e) {

                returnnull;

            }

           session.setStagingProgress(0);

            try {

                File file= new File(mPackageURI.getPath());

                try(InputStream in = new FileInputStream(file)) {

                    longsizeBytes = file.length();

                    try(OutputStream out = session

                           .openWrite("PackageInstaller", 0, sizeBytes)) {

                       byte[] buffer = new byte[4096];

                       while (true) {

                           int numRead = in.read(buffer);

                            if (numRead == -1) {

                               session.fsync(out);

                               break;

                           }

                           if (isCancelled()) {

                               session.close();

                                break;

                           }

                           out.write(buffer, 0, numRead);

     public IPackageInstallerSessionopenSession(int sessionId) {

        try {

            returnopenSessionInternal(sessionId);

        } catch(IOException e) {

            throwExceptionUtils.wrap(e);

        }

    }

通過prepareStageDir建立目錄,

     static void prepareStageDir(File stageDir)throws IOException {

        if(stageDir.exists()) {

            throw newIOException("Session dir already exists: " + stageDir);

        }

        try {

           Os.mkdir(stageDir.getAbsolutePath(), 0755);

           Os.chmod(stageDir.getAbsolutePath(), 0755);

        } catch(ErrnoException e) {

            // Thispurposefully throws if directory already exists

            throw newIOException("Failed to prepare session dir: " + stageDir, e);

        }

        if(!SELinux.restorecon(stageDir)) {

            throw newIOException("Failed to restorecon session dir: " + stageDir);

        }

    }

目前為止apk的安裝都只是在PackageInstaller apk和framework的PackageInstaller框架之間交換,還沒有涉及到PMS,這一點是怎麼做到的呢,如果沒有PMS參與,就沒有參考文件裡面我們瞭解的那些對manifest的解析、許可權處理、檔案拷貝和dex優化等相關流程,這裡就產生了割裂感。我看到的很多文件對這點都沒講透,所以這裡多寫一點。

PMS在上層的介面類是APM,通過PM類在PackageInstaller和PackageInstallerSession的引用就能找到相關的關聯關係,然後我們就會發現一個很重要的方法,就是session的commit方法,詳細說來,

在7.0系統,InstallAppProgress的doPackageStage直接呼叫了session.commit(前面還有session的建立和open等),

     private void doPackageStage(PackageManagerpm, PackageInstaller.SessionParams params) {

        finalPackageInstaller packageInstaller = pm.getPackageInstaller();

       PackageInstaller.Session session = null;

        try {

            final StringpackageLocation = mPackageURI.getPath();

            final File file = newFile(packageLocation);

            final intsessionId = packageInstaller.createSession(params);

            final byte[]buffer = new byte[65536];

            session =packageInstaller.openSession(sessionId);

            finalInputStream in = new FileInputStream(file);

            final longsizeBytes = file.length();

            finalOutputStream out = session.openWrite("PackageInstaller", 0,sizeBytes);

            try {

                int c;

                while ((c = in.read(buffer)) != -1) {

                   out.write(buffer, 0, c);

                    if(sizeBytes > 0) {

                       final float fraction = ((float) c / (float) sizeBytes);

                       session.addProgress(fraction);

                    }

                }

               session.fsync(out);

            } finally {

               IoUtils.closeQuietly(in);

               IoUtils.closeQuietly(out);

            }

            // Create aPendingIntent and use it to generate the IntentSender

            IntentbroadcastIntent = new Intent(BROADCAST_ACTION);

            PendingIntentpendingIntent = PendingIntent.getBroadcast(

                   InstallAppProgress.this /*context*/,

                   sessionId,

                    broadcastIntent,

                   PendingIntent.FLAG_UPDATE_CURRENT);

            session.commit(pendingIntent.getIntentSender());

        } catch(IOException e) {

           onPackageInstalled(PackageInstaller.STATUS_FAILURE);

        } finally {

           IoUtils.closeQuietly(session);

        }

    }

然後,通過binder,在PackageInstallerSession.java裡,呼叫commit,給handler發一個訊息MSG_COMMIT,

         final PackageInstallObserverAdapteradapter = new PackageInstallObserverAdapter(mContext,

               statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);

       mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();

然後Handler.CallbackmHandlerCallback再呼叫commitLocked(),這裡就開始呼叫到PMS的安裝介面了,然後就是PMS的流程,相關參考文件都有很詳盡的說明,我就不贅述了。

     private void commitLocked() throwsPackageManagerException {

        // Unpack nativelibraries

       extractNativeLibraries(mResolvedStageDir, params.abiOverride);

。。。

        mRelinquished =true;

        mPm.installStage(mPackageName,stageDir, stageCid, localObserver, params,

               installerPackageName, installerUid, user, mCertificates);

    }

在8.0上呢,沒有了InstallAppProgress檔案,改用InstallInstalling,但思路差不多,在非同步taskInstallingAsyncTask的方法中呼叫commit,之後的邏輯差不多。

         protected voidonPostExecute(PackageInstaller.Session session) {

            if (session !=null) {

                IntentbroadcastIntent = new Intent(BROADCAST_ACTION);

                broadcastIntent.setPackage(

                       getPackageManager().getPermissionControllerPackageName());

               broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);

               PendingIntent pendingIntent = PendingIntent.getBroadcast(

                       InstallInstalling.this,

                       mInstallId,

                       broadcastIntent,

                       PendingIntent.FLAG_UPDATE_CURRENT);

                session.commit(pendingIntent.getIntentSender());

               mCancelButton.setEnabled(false);

               setFinishOnTouchOutside(false);

            } else {

               getPackageManager().getPackageInstaller().abandonSession(mSessionId);

                if(!isCancelled()) {

                   launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);

                }

            }

        }