<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                PKMS構造函數第二階段的工作就是掃描系統中的APK了。由于需要逐個掃描文件,因此手機上裝的程序越多,PKMS的工作量越大,系統啟動速度也就越慢。 1. 系統庫的dex優化 接著對PKMS構造函數進行分析,代碼如下: **PackageManagerService.java** ~~~ ...... mRestoredSettings= mSettings.readLPw();//接第一段的結尾 longstartTime = SystemClock.uptimeMillis();//記錄掃描開始的時間 //定義掃描參數 intscanMode = SCAN_MONITOR | SCAN_NO_PATHS | SCAN_DEFER_DEX; if(mNoDexOpt) { scanMode|= SCAN_NO_DEX; //在控制掃描過程中是否對APK文件進行dex優化 } finalHashSet<String> libFiles = new HashSet<String>(); // mFrameworkDir指向/system/frameworks目錄 mFrameworkDir = newFile(Environment.getRootDirectory(),"framework"); // mDalvikCacheDir指向/data/dalvik-cache目錄 mDalvikCacheDir= new File(dataDir, "dalvik-cache"); booleandidDexOpt = false; /* 獲取Java啟動類庫的路徑,在init.rc文件中通過BOOTCLASSPATH環境變量輸出,該值如下 /system/framework/core.jar:/system/frameworks/core-junit.jar: /system/frameworks/bouncycastle.jar:/system/frameworks/ext.jar: /system/frameworks/framework.jar:/system/frameworks/android.policy.jar: /system/frameworks/services.jar:/system/frameworks/apache-xml.jar: /system/frameworks/filterfw.jar 該變量指明了framework所有核心庫及文件位置 */ StringbootClassPath = System.getProperty("java.boot.class.path"); if(bootClassPath != null) { String[] paths = splitString(bootClassPath, ':'); for(int i=0; i<paths.length; i++) { try{ //判斷該jar包是否需要重新做dex優化 if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) { /* 將該jar包文件路徑保存到libFiles中,然后通過mInstall對象發送 命令給installd,讓其對該jar包進行dex優化 */ libFiles.add(paths[i]); mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true); didDexOpt = true; } } ...... } } ...... /* 讀者還記得mSharedLibrarires的作用嗎?它保存的是platform.xml中聲明的系統庫的信息。 這里也要判斷系統庫是否需要做dex優化。處理方式同上 */ if (mSharedLibraries.size() > 0) { ...... } //將framework-res.apk添加到libFiles中。framework-res.apk定義了系統常用的 //資源,還有幾個重要的Activity,如長按Power鍵后彈出的選擇框 libFiles.add(mFrameworkDir.getPath() + "/framework-res.apk"); //列舉/system/frameworks目錄中的文件 String[] frameworkFiles = mFrameworkDir.list(); if(frameworkFiles != null) { ......//判斷該目錄下的apk或jar文件是否需要做dex優化。處理方式同上 } /* 上面代碼對系統庫(BOOTCLASSPATH指定,或 platform.xml定義,或 /system/frameworks目錄下的jar包與apk文件)進行一次仔細檢查,該優化的一定要優化。 如果發現期間對任何一個文件進行了優化,則設置didDexOpt為true */ if (didDexOpt) { String[] files = mDalvikCacheDir.list(); if (files != null) { /* 如果前面對任意一個系統庫重新做過dex優化,就需要刪除cache文件。原因和 dalvik虛擬機的運行機制有關。本書暫不探討dex及cache文件的作用。 從刪除cache文件這個操作來看,這些cache文件應該使用了dex優化后的系統庫 所以當系統庫重新做dex優化后,就需要刪除舊的cache文件。可簡單理解為緩存失效 */ for (int i=0; i<files.length; i++) { String fn = files[i]; if(fn.startsWith("data@app@") ||fn.startsWith("data@app-private@")) { (newFile(mDalvikCacheDir, fn)).delete(); ...... } ~~~ 2. 掃描系統Package 清空cache文件后,PKMS終于進入重點段了。接下來看PKMS第二階段工作的核心內容,即掃描Package,相關代碼如下: **PackageManagerService.java** ~~~ //創建文件夾監控對象,監視/system/frameworks目錄。利用了Linux平臺的inotify機制 mFrameworkInstallObserver = new AppDirObserver( mFrameworkDir.getPath(),OBSERVER_EVENTS, true); mFrameworkInstallObserver.startWatching(); /* 調用scanDirLI函數掃描/system/frameworks目錄,這個函數很重要,稍后會再分析。 注意,在第三個參數中設置了SCAN_NO_DEX標志,因為該目錄下的package在前面的流程 中已經過判斷并根據需要做過dex優化了 */ scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,scanMode | SCAN_NO_DEX, 0); //創建文件夾監控對象,監視/system/app目錄 mSystemAppDir = new File(Environment.getRootDirectory(),"app"); mSystemInstallObserver = new AppDirObserver( mSystemAppDir.getPath(), OBSERVER_EVENTS, true); mSystemInstallObserver.startWatching(); //掃描/system/app下的package scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); //監視并掃描/vendor/app目錄 mVendorAppDir = new File("/vendor/app"); mVendorInstallObserver = new AppDirObserver( mVendorAppDir.getPath(), OBSERVER_EVENTS, true); mVendorInstallObserver.startWatching(); //掃描/vendor/app下的package scanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); //和installd交互。以后單獨分析installd mInstaller.moveFiles(); ~~~ 由以上代碼可知,PKMS將掃描以下幾個目錄。 - /system/frameworks:該目錄中的文件都是系統庫,例如framework.jar、services.jar、framework-res.apk。不過scanDirLI只掃描APK文件,所以framework-res.apk是該目錄中唯一“受寵”的文件。 - /system/app:該目錄下全是默認的系統應用,例如Browser.apk、SettingsProvider.apk等。 - /vendor/app:該目錄中的文件由廠商提供,即廠商特定的APK文件,不過目前市面上的廠商都把自己的應用放在/system/app目錄下。 >[warning] **注意**:本書把這三個目錄稱為系統Package目錄,以區分后面的非系統Package目錄。 PKMS調用scanDirLI函數進行掃描,下面來分析此函數。 (1) scanDirLI函數分析 scanDirLI函數的代碼如下: **PackageManagerService.java** ~~~ private void scanDirLI(File dir, int flags, intscanMode, long currentTime) { String[] files = dir.list();//列舉該目錄下的文件 ...... inti; for(i=0; i<files.length; i++) { File file = new File(dir, files[i]); if (!isPackageFilename(files[i])) { continue; //根據文件名后綴,判斷是否為APK 文件。這里只掃描APK 文件 } /* 調用scanPackageLI函數掃描一個特定的文件,返回值是PackageParser的內部類 Package,該類的實例代表一個APK文件,所以它就是和APK文件對應的數據結構 */ PackageParser.Package pkg = scanPackageLI(file, flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime); if (pkg == null && (flags &PackageParser.PARSE_IS_SYSTEM) == 0 && mLastScanError ==PackageManager.INSTALL_FAILED_INVALID_APK) { //注意此處flags的作用,只有非系統Package掃描失敗,才會刪除該文件 file.delete(); } } } ~~~ 接著來分析scanPackageLI函數。PKMS中有兩個同名的scanPackageLI函數,后面會一一見到。先來看第一個也是最先碰到的scanPackageLI函數。 (2) 初會scanPackageLI函數 首次相遇的scanPackageLI函數的代碼如下: **PackageManagerService.java** ~~~ private PackageParser.Package scanPackageLI(FilescanFile, int parseFlags, int scanMode, long currentTime) { mLastScanError = PackageManager.INSTALL_SUCCEEDED; StringscanPath = scanFile.getPath(); parseFlags |= mDefParseFlags;//默認的掃描標志,正常情況下為0 //創建一個PackageParser對象 PackageParser pp = new PackageParser(scanPath); pp.setSeparateProcesses(mSeparateProcesses);// mSeparateProcesses為空 pp.setOnlyCoreApps(mOnlyCore);// mOnlyCore為false /* 調用PackageParser的parsePackage函數解析APK文件。注意,這里把代表屏幕 信息的mMetrics對象也傳了進去 */ finalPackageParser.Package pkg = pp.parsePackage(scanFile,scanPath, mMetrics, parseFlags); ...... PackageSetting ps = null; PackageSetting updatedPkg; ...... /* 這里略去一大段代碼,主要是關于Package升級方面的工作。讀者可能會比較好奇:既然是 升級,一定有新舊之分,如果這里剛解析后得到的Package信息是新,那么舊Package 的信息從何得來?還記得”readLPw的‘佐料’”這一小節提到的package.xml文件嗎?此 文件中存儲的就是上一次掃描得到的Package信息。對比這兩次的信息就知道是否需要做 升級了。這部分代碼比較繁瑣,但不影響我們正常分析。感興趣的讀者可自行研究 */ //收集簽名信息,這部分內容涉及signature,本書暫不擬討論(Signature和Android安全機制有關)。 if (!collectCertificatesLI(pp, ps, pkg,scanFile, parseFlags)) returnnull; //判斷是否需要設置PARSE_FORWARD_LOCK標志,這個標志針對資源文件和Class文件 //不在同一個目錄的情況。目前只有/vendor/app目錄下的掃描會使用該標志。這里不討論 //這種情況。 if (ps != null &&!ps.codePath.equals(ps.resourcePath)) parseFlags|= PackageParser.PARSE_FORWARD_LOCK; String codePath = null; String resPath = null; if((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) { ......//這里不考慮PARSE_FORWARD_LOCK的情況。 }else { resPath = pkg.mScanPath; } codePath = pkg.mScanPath;//mScanPath指向該APK文件所在位置 //設置文件路徑信息,codePath和resPath都指向APK文件所在位置 setApplicationInfoPaths(pkg, codePath, resPath); //調用第二個scanPackageLI函數 return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE, currentTime); } ~~~ scanPackageLI函數首先調用PackageParser對APK文件進行解析。根據前面的介紹可知,PackageParser完成了從物理文件到對應數據結構的轉換。下面來分析這個PackageParser。 (3) PackageParser分析 PackageParser主要負責APK文件的解析,即解析APK文件中的AndroidManifest.xml代碼如下: **PackageParser.java** ~~~ publicPackage parsePackage(File sourceFile, String destCodePath, DisplayMetrics metrics, int flags) { mParseError = PackageManager.INSTALL_SUCCEEDED; mArchiveSourcePath = sourceFile.getPath(); ......//檢查是否為APK文件 XmlResourceParser parser = null; AssetManager assmgr = null; Resources res = null; boolean assetError = true; try{ assmgr = new AssetManager(); int cookie = assmgr.addAssetPath(mArchiveSourcePath); if (cookie != 0) { res = new Resources(assmgr, metrics, null); assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,Build.VERSION.RESOURCES_SDK_INT); /* 獲得一個XML資源解析對象,該對象解析的是APK中的AndroidManifest.xml文件。 以后再討論AssetManager、Resource及相關的知識 */ parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); assetError = false; } ......//出錯處理 String[] errorText = new String[1]; Package pkg = null; Exception errorException = null; try { //調用另外一個parsePackage函數 pkg = parsePackage(res, parser, flags, errorText); } ...... ......//錯誤處理 parser.close(); assmgr.close(); //保存文件路徑,都指向APK文件所在的路徑 pkg.mPath = destCodePath; pkg.mScanPath = mArchiveSourcePath; pkg.mSignatures = null; return pkg; } ~~~ 以上代碼中調用了另一個同名的PackageParser函數,此函數內容較長,但功能單一,就是解析AndroidManifest.xml中的各種標簽,這里只提取其中相關的代碼: **PackageParser.java** ~~~ private Package parsePackage( Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { AttributeSet attrs = parser; mParseInstrumentationArgs = null; mParseActivityArgs = null; mParseServiceArgs= null; mParseProviderArgs = null; //得到Package的名字,其實就是得到AndroidManifest.xml中package屬性的值, //每個APK都必須定義該屬性 String pkgName = parsePackageName(parser, attrs, flags, outError); ...... inttype; ...... //以pkgName名字為參數,創建一個Package對象。后面的工作就是解析XML并填充 //該Package信息 finalPackage pkg = new Package(pkgName); boolean foundApp = false; ......//下面開始解析該文件中的標簽,由于這段代碼功能簡單,所以這里僅列舉相關函數 while(如果解析未完成){ ...... StringtagName = parser.getName(); //得到標簽名 if(tagName.equals("application")){ ......//解析application標簽 parseApplication(pkg,res, parser, attrs, flags, outError); } elseif (tagName.equals("permission-group")) { ......//解析permission-group標簽 parsePermissionGroup(pkg, res, parser, attrs, outError); } elseif (tagName.equals("permission")) { ......//解析permission標簽 parsePermission(pkg, res, parser, attrs, outError); } else if(tagName.equals("uses-permission")){ //從XML文件中獲取uses-permission標簽的屬性 sa= res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestUsesPermission); //取出屬性值,也就是對應的權限使用聲明 String name = sa.getNonResourceString(com.android.internal. R.styleable.AndroidManifestUsesPermission_name); //添加到Package的requestedPermissions數組 if(name != null && !pkg.requestedPermissions.contains(name)) { pkg.requestedPermissions.add(name.intern()); } }elseif (tagName.equals("uses-configuration")){ /* 該標簽用于指明本package對硬件的一些設置參數,目前主要針對輸入設備(觸摸屏、鍵盤 等)。游戲類的應用可能對此有特殊要求。 */ ConfigurationInfocPref = new ConfigurationInfo(); ......//解析該標簽所支持的各種屬性 pkg.configPreferences.add(cPref);//保存到Package的configPreferences數組 } ......//對其他標簽解析和處理 } ~~~ 上面代碼展示了AndroidManifest.xml解析的流程,其中比較重要的函數是parserApplication,它用于解析application標簽及其子標簽(Android的四大組件在application標簽中已聲明)。 圖4-5表示了PackageParser及其內部重要成員的信息。 :-: ![](http://img.blog.csdn.net/20150803110542379?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖4-5 PackageParser大家族 由圖4-5可知: - PackageParser定了相當多的內部類,這些內部類的作用就是保存對應的信息。解析AndroidManifest.xml文件得到的信息由Package保存。從該類的成員變量可看出,和Android四大組件相關的信息分別由activites、receivers、providers、services保存。由于一個APK可聲明多個組件,因此activites和receivers等均聲明為ArrayList。 - 以PackageParser.Activity為例,它從Component<ActivityIntentInfo>派生。Component是一個模板類,元素類型是ActivityIntentInfo,此類的頂層基類是IntentFilter。PackageParser.Activity內部有一個ActivityInfo類型的成員變量,該變量保存的就是四大組件中Activity的信息。細心的讀者可能會有疑問,為什么不直接使用ActivityInfo,而是通過IntentFilter構造出一個使用模板的復雜類型PackageParser.Activity呢?原來,Package除了保存信息外,還需要支持Intent匹配查詢。例如,設置Intent的Action為某個特定值,然后查找匹配該Intent的Activity。由于ActivityIntentInfo是從IntentFilter派生的,因此它它能判斷自己是否滿足該Intent的要求,如果滿足,則返回對應的ActivityInfo。在后續章節會詳細討論根據Intent查詢特定Activity的工作流程。 - PackageParser定了一個輕量級的數據結構PackageLite,該類僅存儲Package的一些簡單信息。我們在介紹Package安裝的時候,會遇到PackageLite。 >[warning] **注意**:讀者需要了解Java泛型類的相關知識。 (4) 與scanPackageLI再相遇 在PackageParser掃描完一個APK后,此時系統已經根據該APK中AndroidManifest.xm,創建了一個完整的Package對象,下一步就是將該Package加入到系統中。此時調用的函數就是另外一個scanPackageLI,其代碼如下: **PackageManagerService.java::scanPackageLI函數** ~~~ private PackageParser.PackagescanPackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime) { FilescanFile = new File(pkg.mScanPath); ...... mScanningPath = scanFile; //設置package對象中applicationInfo的flags標簽,用于標示該Package為系統 //Package if((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } //①下面這句if判斷極為重要,見下面的解釋 if(pkg.packageName.equals("android")) { synchronized (mPackages) { if (mAndroidApplication != null) { ...... mPlatformPackage = pkg; pkg.mVersionCode = mSdkVersion; mAndroidApplication = pkg.applicationInfo; mResolveActivity.applicationInfo = mAndroidApplication; mResolveActivity.name = ResolverActivity.class.getName(); mResolveActivity.packageName = mAndroidApplication.packageName; mResolveActivity.processName = mAndroidApplication.processName; mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert; mResolveActivity.exported = true; mResolveActivity.enabled = true; //mResoveInfo的activityInfo成員指向mResolveActivity mResolveInfo.activityInfo = mResolveActivity; mResolveInfo.priority = 0; mResolveInfo.preferredOrder = 0; mResolveInfo.match = 0; mResolveComponentName = new ComponentName( mAndroidApplication.packageName, mResolveActivity.name); } } ~~~ 剛進入scanPackageLI函數,我們就發現了一個極為重要的內容,即單獨判斷并處理packageName為“android”的Package。和該Package對應的APK是framework-res.apk,有圖為證,如圖4-6所示為該APK的AndroidManifest.xml中的相關內容。 :-: ![](http://img.blog.csdn.net/20150803110628247?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖4-6 framework-res.apk的AndroidManifest.xml 實際上,framework-res.apk還包含了以下幾個常用的Activity。 - ChooserActivity:當多個Activity符合某個Intent的時候,系統會彈出此Activity,由用戶選擇合適的應用來處理。 - RingtonePickerActivity:鈴聲選擇Activity。 - ShutdownActivity:關機前彈出的選擇對話框。 由前述知識可知,該Package和系統息息相關,因此它得到了PKMS的特別青睞,主要體現在以下幾點。 - mPlatformPackage成員用于保存該Package信息。 - mAndroidApplication用于保存此Package中的ApplicationInfo。 - mResolveActivity指向用于表示ChooserActivity信息的ActivityInfo。 - mResolveInfo為ResolveInfo類型,它用于存儲系統解析Intent(經IntentFilter的過濾)后得到的結果信息,例如滿足某個Intent的Activity的信息。由前面的代碼可知,mResolveInfo的activityInfo其實指向的就是mResolveActivity。 * * * * * **注意**:在從PKMS中查詢滿足某個Intent的Activity時,返回的就是ResolveInfo,再根據ResolveInfo的信息得到具體的Activity。 此處保存這些信息,主要是為了提高運行過程中的效率。Goolge工程師可能覺得ChooserActivity使用的地方比較多,所以這里單獨保存了此Activity的信息。 * * * * * 好,繼續對scanPackageLI函數的分析。 **PackageManagerService::scanPackageLI函數** ~~~ ......//mPackages用于保存系統內的所有Package,以packageName為key if(mPackages.containsKey(pkg.packageName) || mSharedLibraries.containsKey(pkg.packageName)) { return null; } File destCodeFile = newFile(pkg.applicationInfo.sourceDir); FiledestResourceFile = new File(pkg.applicationInfo.publicSourceDir); SharedUserSettingsuid = null;//代表該Package的SharedUserSetting對象 PackageSetting pkgSetting = null;//代表該Package的PackageSetting對象 synchronized(mPackages) { ......//此段代碼大約有300行左右,主要做了以下幾方面工作 /* ①如果該Packge聲明了” uses-librarie”話,那么系統要判斷該library是否 在mSharedLibraries中 ②如果package聲明了SharedUser,則需要處理SharedUserSettings相關內容, 由Settings的getSharedUserLPw函數處理 ③處理pkgSetting,通過調用Settings的getPackageLPw函數完成 ④調用verifySignaturesLP函數,檢查該Package的signature */ } finallong scanFileTime = scanFile.lastModified(); finalboolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0; //確定運行該package的進程的進程名,一般用packageName作為進程名 pkg.applicationInfo.processName = fixProcessName( pkg.applicationInfo.packageName, pkg.applicationInfo.processName, pkg.applicationInfo.uid); if(mPlatformPackage == pkg) { dataPath = new File (Environment.getDataDirectory(),"system"); pkg.applicationInfo.dataDir = dataPath.getPath(); }else { /* getDataPathForPackage函數返回該package的目錄 一般是/data/data/packageName/ */ dataPath = getDataPathForPackage(pkg.packageName, 0); if(dataPath.exists()){ ......//如果該目錄已經存在,則要處理uid的問題 } else { ......//向installd發送install命令,實際上就是在/data/data下 //建立packageName目錄。后續將分析installd相關知識 int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid); //為系統所有user安裝此程序 mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); if (dataPath.exists()) { pkg.applicationInfo.dataDir = dataPath.getPath(); } ...... if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir!= null) { ......//為該Package確定native library所在目錄 //一般是/data/data/packagename/lib } } //如果該APK包含了native動態庫,則需要將它們從APK文件中解壓并復制到對應目錄中 if(pkg.applicationInfo.nativeLibraryDir != null) { try { final File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); final String dataPathString = dataPath.getCanonicalPath(); //從2.3開始,系統package的native庫統一放在/system/lib下。所以 //系統不會提取系統Package目錄下APK包中的native庫 if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { NativeLibraryHelper.removeNativeBinariesFromDirLI( nativeLibraryDir)){ } else if (nativeLibraryDir.getParentFile().getCanonicalPath() .equals(dataPathString)) { boolean isSymLink; try { isSymLink = S_ISLNK(Libcore.os.lstat( nativeLibraryDir.getPath()).st_mode); } ......//判斷是否為鏈接,如果是,需要刪除該鏈接 if (isSymLink) { mInstaller.unlinkNativeLibraryDirectory(dataPathString); } //在lib下建立和CPU類型對應的目錄,例如ARM平臺的是arm/,MIPS平臺的是mips/ NativeLibraryHelper.copyNativeBinariesIfNeededLI(scanFile, nativeLibraryDir); } else { mInstaller.linkNativeLibraryDirectory(dataPathString, pkg.applicationInfo.nativeLibraryDir); } } ...... } pkg.mScanPath= path; if((scanMode&SCAN_NO_DEX) == 0) { ......//對該APK做dex優化 performDexOptLI(pkg,forceDex, (scanMode&SCAN_DEFER_DEX); } //如果該APK已經存在,要先殺掉運行該APK的進程 if((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { killApplication(pkg.applicationInfo.packageName, pkg.applicationInfo.uid); } ...... /* 在此之前,四大組件信息都屬于Package的私有財產,現在需要把它們登記注冊到PKMS內部的 財產管理對象中。這樣,PKMS就可對外提供統一的組件信息,而不必拘泥于具體的Package */ synchronized(mPackages) { if ((scanMode&SCAN_MONITOR) != 0) { mAppDirs.put(pkg.mPath, pkg); } mSettings.insertPackageSettingLPw(pkgSetting, pkg); mPackages.put(pkg.applicationInfo.packageName,pkg); //處理該Package中的Provider信息 int N =pkg.providers.size(); int i; for (i=0;i<N; i++) { PackageParser.Providerp = pkg.providers.get(i); p.info.processName=fixProcessName( pkg.applicationInfo.processName, p.info.processName, pkg.applicationInfo.uid); //mProvidersByComponent提供基于ComponentName的Provider信息查詢 mProvidersByComponent.put(new ComponentName( p.info.packageName,p.info.name), p); ...... } //處理該Package中的Service信息 N =pkg.services.size(); r = null; for (i=0;i<N; i++) { PackageParser.Service s =pkg.services.get(i); mServices.addService(s); } //處理該Package中的BroadcastReceiver信息 N =pkg.receivers.size(); r = null; for (i=0;i<N; i++) { PackageParser.Activity a =pkg.receivers.get(i); mReceivers.addActivity(a,"receiver"); ...... } //處理該Package中的Activity信息 N = pkg.activities.size(); r =null; for (i=0; i<N; i++) { PackageParser.Activity a =pkg.activities.get(i); mActivities.addActivity(a,"activity");//后續將詳細分析該調用 } //處理該Package中的PermissionGroups信息 N = pkg.permissionGroups.size(); ......//permissionGroups處理 N =pkg.permissions.size(); ......//permissions處理 N =pkg.instrumentation.size(); ......//instrumentation處理 if(pkg.protectedBroadcasts != null) { N = pkg.protectedBroadcasts.size(); for(i=0; i<N; i++) { mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i)); } } ......//Package的私有財產終于完成了公有化改造 return pkg; } ~~~ 到此這個長達800行的代碼就分析完了,下面總結一下Package掃描的流程。 (5) scanDirLI函數總結 scanDirLI用于對指定目錄下的APK文件進行掃描,如圖4-7所示為該函數的調用流程。 :-: ![](http://img.blog.csdn.net/20150803110657112?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖4-7 scanDirLI工作流程總結 圖4-7比較簡單,相關知識無須贅述。讀者在自行分析代碼時,只要注意區分這兩個同名scanPackageLI函數即可。 掃描完APK文件后,Package的私有財產就充公了。PKMS提供了好幾個重要數據結構來保存這些財產,這些數據結構的相關信息如圖4-8所示。 :-: ![](http://img.blog.csdn.net/20150803110726269?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖4-8 PKMS中重要的數據結構 圖4-8借用UML的類圖來表示PKMS中重要的數據結構。每個類圖的第一行為成員變量名,第二行為數據類型,第三行為注釋說明。 3. 掃描非系統Package 非系統Package就是指那些不存儲在系統目錄下的APK文件,這部分代碼如下: **PackageManagerService.java::構造函數第三部分** ~~~ if (!mOnlyCore) {//mOnlyCore用于控制是否掃描非系統Package Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator(); while (psit.hasNext()) { ......//刪除系統package中那些不存在的APK } mAppInstallDir = new File(dataDir,"app"); ......//刪除安裝不成功的文件及臨時文件 if (!mOnlyCore) { //在普通模式下,還需要掃描/data/app以及/data/app_private目錄 mAppInstallObserver = new AppDirObserver( mAppInstallDir.getPath(), OBSERVER_EVENTS, false); mAppInstallObserver.startWatching(); scanDirLI(mAppInstallDir, 0, scanMode, 0); mDrmAppInstallObserver = newAppDirObserver( mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false); mDrmAppInstallObserver.startWatching(); scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,scanMode,0); } else { mAppInstallObserver = null; mDrmAppInstallObserver = null; } ~~~ 結合前述代碼,這里總結幾個存放APK文件的目錄。 - 系統Package目錄包括:/system/frameworks、/system/app和/vendor/app。 - 非系統Package目錄包括:/data/app、/data/app-private。 4. 第二階段工作總結 PKMS構造函數第二階段的工作任務非常繁重,要創建比較多的對象,所以它是一個耗時耗內存的操作。在工作中,我們一直想優化該流程以加快啟動速度,例如延時掃描不重要的APK,或者保存Package信息到文件中,然后在啟動時從文件中恢復這些信息以減少APK文件讀取并解析XML的工作量。但是一直沒有一個比較完滿的解決方案,原因有很多。比如APK之間有著比較微妙的依賴關系,因此到底延時掃描哪些APK,尚不能確定。另外,筆者感到比較疑惑的一個問題是:對于多核CPU架構,PKMS可以啟動多個線程以掃描不同的目錄,但是目前代碼中還沒有尋找到相關的蛛絲馬跡。難道此處真的就不能優化了嗎?讀者如果有更好的解決方案,不妨和大家分享一下。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看