permission
文件的處理在PMS
的構造方法中初始化了PermissionManagerService
,經過PermissionManagerService.create()
方法,相關調用以下:java
public static PermissionManagerInternal create(......) {
......
new PermissionManagerService(context, defaultGrantCallback, externalLock);
......
}
PermissionManagerService(......) {
......
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
Watchdog.getInstance().addThread(mHandler);
......
SystemConfig systemConfig = SystemConfig.getInstance();
mSystemPermissions = systemConfig.getSystemPermissions();
......
}
複製代碼
上面列出來PermissionManagerService
初始化的過程當中兩個操做:android
Watchdog
監聽,此處列出它來主要是爲了呼應前面的章節。哈哈哈,重點是第二點SystemConfig
的getSystemPermissions
方法來獲取系統的Permission
列表 public SparseArray<ArraySet<String>> getSystemPermissions() {
return mSystemPermissions;
}
複製代碼
而對於mSystemPermissions
,是在SystemConfig
的構造方法中完成的,相關代碼以下:api
//單例模式
SystemConfig() {
// 讀取 /system/etc/sysconfig 目錄下的 permission 文件
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
// 讀取 /system/etc/permissions 目錄下的 permission 文件
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
......
// 讀取 /vendor/etc/sysconfig 目錄下的 permission 文件
readPermissions(Environment.buildPath(
Environment.getVendorDirectory(), "etc", "sysconfig"), vendorPermissionFlag);
// 讀取 /vendor/etc/permissions 目錄下的 permission 文件
readPermissions(Environment.buildPath(
Environment.getVendorDirectory(), "etc", "permissions"), vendorPermissionFlag);
...// 省略 /oem、/odm、/product對應分區目錄下 readPermissions()
}
複製代碼
不用說,核心是這個readPermissions()
方法,看看:markdown
void readPermissions(File libraryDir, int permissionFlag) {
// Read permissions from given directory.
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
return;// 若是目錄不存在或者libraryDir不是目錄,直接返回
}
if (!libraryDir.canRead()) {
return;// 若是目錄不可讀,直接返回
}
File platformFile = null;
// 循環能處理目錄文件
for (File f : libraryDir.listFiles()) {
if (f.getPath().endsWith("etc/permissions/platform.xml")) {
// 檢測到platform.xml文件,先記錄,循環中不作處理
platformFile = f;
continue;
}
if (!f.getPath().endsWith(".xml")) {
// 跳過非xml文件
continue;
}
if (!f.canRead()) {
// 跳過不可讀文件
continue;
}
readPermissionsFromXml(f, permissionFlag);// 解析xml
}
// 最後,解析platform.xml文件
if (platformFile != null) {
readPermissionsFromXml(platformFile, permissionFlag);
}
}
複製代碼
readPermissions()
方法先檢測指定目錄下的xml
文件,而後調用readPermissionsFromXml
方法來解析文件並將解析結果填充到SystemConfig
對應的數據結構中。數據結構
readPermissionsFromXml()
方法內容以下:app
private void readPermissionsFromXml(File permFile, int permissionFlag) {
FileReader permReader = new FileReader(permFile);
......
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(permReader);
int type;
// 查找 START_TAG 或 END_DOCUMENT
while ((type=parser.next()) != parser.START_TAG
&& type != parser.END_DOCUMENT) {
;
}
// 從 START_TAG 開始解析
if (type != parser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
// 只針對<permissions/>和<config/>標籤進行解析
if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
throw new XmlPullParserException(...);
}
......
while (true) {
......
String name = parser.getName();
// 可以識別的標籤以下:
if ("group".equals(name) && allowAll) {
} else if ("permission".equals(name) && allowPermissions) {
} else if ("assign-permission".equals(name) && allowPermissions) {
} else if ("library".equals(name) && allowLibs) {
} else if ("feature".equals(name) && allowFeatures) {
} else if ("unavailable-feature".equals(name) && allowFeatures) {
} else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
} else if ("allow-in-power-save".equals(name) && allowAll) {
} else if ("allow-in-data-usage-save".equals(name) && allowAll) {
} else if ("allow-unthrottled-location".equals(name) && allowAll) {
} else if ("allow-implicit-broadcast".equals(name) && allowAll) {
} else if ("app-link".equals(name) && allowAppConfigs) {
} else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
} else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) {
} else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) {
} else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) {
} else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name) && allowAppConfigs) {
} else if ("disabled-until-used-preinstalled-carrier-app".equals(name) && allowAppConfigs) {
} else if ("privapp-permissions".equals(name) && allowPrivappPermissions) {
} else if ("oem-permissions".equals(name) && allowOemPermissions) {
} else if ("hidden-api-whitelisted-app".equals(name) && allowApiWhitelisting) {
} else {
}
}
}
......
}
複製代碼
if else
中的字符串就是xml
的關鍵標籤,解析時會分別保存到SystemConfig
對應的final
成員變量中,這部分就不貼源碼了哈ide
從整個解析過程來看,這些xml
文件分紅了<permissions/>
和<config/>
兩個大標籤,而後又包含permission
、library
等子標籤。函數
以platform.xml
爲例,咱們來看下相關內容結構:oop
<permissions>
<permission name="android.permission.BLUETOOTH_ADMIN" >
<group gid="net_bt_admin" />
</permission>
<permission name="android.permission.BLUETOOTH" >
<group gid="net_bt" />
</permission>
......
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
<assign-permission name="android.permission.WAKE_LOCK" uid="media" />
......
<library name="android.test.base" file="/system/framework/android.test.base.jar" />
<library name="android.test.mock" file="/system/framework/android.test.mock.jar" />
......
<permissions/>
複製代碼
platform.xml
上面主要由3塊內容:測試
<permission/>
:將屬性name
中字符串表示的權限賦予<group/>
標籤中經過gid
指定的用戶組<assign-permission/>
:將屬性name
中字符串表示的權限賦予經過屬性uid
指定的用戶<library/>
:指定系統自動加載的動態庫
Android
的不少功能都是經過所謂的permission
字符串來保護的,應用中若是須要使用某項受保護的功能,須要在它的AndroidManifest
文件中顯示聲明該功能對應的permission
字符串。可是,有些功能只能由特定的用戶組使用,而在platform.xml
文件中定義的就是這種規則
Android 5.0
引入了Split APK
機制,主要是爲了解決65536
上限以及APK
安裝包愈來愈大等問題。
Split APK
機制能夠將一個APK
拆分紅多個獨立APK
。 在引入了Split APK
機制後,APK
有兩種分類:
Single APK
:安裝文件爲一個完整的APK
,即base APK
。Android
稱其爲Monolithic
。Mutiple APK
:安裝文件在一個文件目錄中,其內部有多個被拆分的APK
,這些APK
由一個base APK
和一個或多個split APK
組成。Android
稱其爲Cluster
。scanDirLI
上面兩種格式的掃描邏輯,是在PMS
的構造方法中,經過scanDirLI
來實現。
scanDirLI
代碼以下:
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = scanDir.listFiles();
......
// 建立 ParallelPackageParser apk 解析幫助類
// ParallelPackageParser核心實際上是一個 ExecutorService
// 當前目錄下全部的 apk 解析任務經過ExecutorService.submit來建立
try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(...)) {
// 遍歷查找指定目錄下的apk文件
int fileCount = 0;
for (File file : files) {
// 判讀是否爲apk文件
// 或者不是smdl2tmp打頭的文件夾
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
continue;
}
// 建立提交解析任務,解析符合條件的文件
parallelPackageParser.submit(file, parseFlags);
// 記錄任務數量
fileCount++;
}
// 根據建立的任務數量,查看任務結果
for (; fileCount > 0; fileCount--) {
// 此方法是阻塞的,直到隊列中有結果添加進來
ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
// 對解析結果進行判斷
Throwable throwable = parseResult.throwable;
int errorCode = PackageManager.INSTALL_SUCCEEDED;
if (throwable == null) {
// 若是是靜態共享庫,進行rename操做
......
// 若是應用正常,執行進一步的掃描
if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
currentTime, null);
}
}
...// 省略異常狀況處理
// Delete invalid userdata apps
if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
errorCode != PackageManager.INSTALL_SUCCEEDED) {
.......
removeCodePathLI(parseResult.scanFile);
}
}
}
}
複製代碼
根據上面的代碼咱們能夠劃分爲兩個階段:
scanDirLI
之parsePackage
APK
解析階段APK
的解析是經過ParallelPackageParser
類的submit()
方法提交解析任務來實現的scanDirLI
之scanPackageChildLI
APK
添加到PMS
的mPackages
中咱們逐一來分析
scanDirLI
之parsePackage
ParallelPackageParser
類是Android
封裝的一個線程池,核心實現是ExecutorService
類。主要是爲了方便收集並行解析的結果。
解析任務的建立都是經過此類的submit()
方法來完成的。
ParallelPackageParser
的submit()
方法咱們來簡單看下:
public void submit(File scanFile, int parseFlags) {
mService.submit(() -> {
ParseResult pr = new ParseResult();
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
pp.setCacheDir(mCacheDir);
pp.setCallback(mPackageParserCallback);
pr.scanFile = scanFile;
pr.pkg = parsePackage(pp, scanFile, parseFlags);
......
mQueue.put(pr);
......
});
}
protected PackageParser.Package parsePackage(......) throws PackageParser.PackageParserException {
return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
}
複製代碼
函數中的submit()
新建了一個解析線程,線程中建立了一個PackageParser
對象,而後調用它的parsePackage
方法來進行解析。
PackageParser
的parsePackage()
方法PackageParser
的parsePackage()
方法以下:
public Package parsePackage(File packageFile, int flags, boolean useCaches) throws PackageParserException {
......
// 根據是否爲directory執行不一樣的解析邏輯
if (packageFile.isDirectory()) {
// 解析一組apk
parsed = parseClusterPackage(packageFile, flags);
} else {
// 解析單個apk
parsed = parseMonolithicPackage(packageFile, flags);
}
......
}
複製代碼
到這裏咱們能夠發現,真正的APK
解析流程就在PackageParser
中,你們能夠跟蹤下上面兩種狀況下的解析,以parseClusterPackage()
爲例,咱們看下
parseClusterPackage()
解析split
應用private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
// 獲取應用目錄的 PackageLite 對象,這個對象中分開保存了目錄下的
// base應用和其餘非核心應用
final PackageLite lite = parseClusterPackageLite(packageDir, 0);
......
try {
// 先裝載base應用資源
final AssetManager assets = assetLoader.getBaseAssetManager();
final File baseApk = new File(lite.baseCodePath);
final Package pkg = parseBaseApk(baseApk, assets, flags);
......
// 再裝載split應用資源
if (!ArrayUtils.isEmpty(lite.splitNames)) {
final int num = lite.splitNames.length;
pkg.splitNames = lite.splitNames;
pkg.splitCodePaths = lite.splitCodePaths;
......
for (int i = 0; i < num; i++) {
final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
parseSplitApk(pkg, i, splitAssets, flags);
}
}
pkg.setCodePath(packageDir.getCanonicalPath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
}
......
}
複製代碼
parseClusterPackage()
方法中首先調用parseClusterPackageLite()
方法對目錄下的APK
進行初步分析
base
和split
應用:base
應用只有一個。split
應用能夠沒有或者多個split
應用的做用是用來保存資源和代碼parseClusterPackageLite()
方法會將分析結果經過PackageLite
對象返回parseClusterPackage()
方法接下來:
base
應用經過parseBaseApk()
來進行分析split
應用經過parseSplitApk()
來進行分析Package
對象中對於parseMonolithicPackage()
方法省去了parseSplitApk
的過程。但在解析時兩者都會執行到parseBaseApk()
方法,而parseBaseApk()
最後會執行到parseBaseApkCommon()
方法中。
parseBaseApkCommon()
解析AndroidManifest.xml
parseBaseApkCommon()
其實主要是對AndroidManifest.xml
的解析,簡要方法以下:
private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException {
......
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifest);
// 讀取 AndroidManifest 的sharedUserId信息
String str = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
......
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
......
String tagName = parser.getName();
......
if (tagName.equals(TAG_APPLICATION)) {
......
// 解析 <application/> 標籤信息
if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
return null;
}
}
// 省略各類 AndroidManifest 的標籤解析
......
else if (tagName.equals(TAG_PERMISSION)) {
// 解析 permission 標籤
if (!parsePermission(pkg, res, parser, outError)) {
return null;
}
}
......
}
......
return pkg;
}
複製代碼
這個方法對AndroidManifest.xml
的標籤進行解析,把解析結果存放到到Package
對象中,並返回。
咱們在while
循環中省略了不少AndroidManifest.xml
的標籤,簡單介紹下應用常見的一些標籤:
<manifest/>
:AndroidManifest.xml
的根元素。
<application/>
元素xlmns:android
屬性:android
的XML
命名空間package
屬性:應用的包名versionCode
:指定應用版本號,整數類型versionName
:顯示給用戶的版本號,字符串類型<uses-permission/>
:聲明某種權限。應用必須聲明可權限纔可使用對應的功能<permission-group/>
:定義權限組<permission-tree/>
:定義一顆權限樹,能夠經過PackageManager.addPermission
插入Permission
<path-permission/>
:爲ContentProvider
下的數據目錄定義訪問權限<application/>
:應用配置的根元素,包含全部與應用有關的屬性配置<activity/>
:Activity
的聲明標籤<activity-alias/>
:Activity
的別名定義標籤<service/>
:Service
組件的聲明標籤<provider/>
:ContentProvider
的聲明標籤<receiver/>
:Broadcast Receiver
的聲明標籤<intent-filter/>
:<intent-filter/>
表示Intent
過濾的聲明
<intent-filter/>
能夠放在<activity/>
、<activity-alias/>
、<service/>
、<receiver/>
等標籤下<intent-filter/>
必須包含有<action/>
,用於描述Intent
的名稱<category/>
,用於描述Intent
的類別<data/>
,用於描述Intent
須要處理的數據格式<meta-data/>
:用於儲存預約義的數據。
<meta-data/>
能夠放在<activity/>
、<activity-alias/>
、<application/>
、<service/>
、<receiver/>
等標籤下<meta-data/>
數據通常以鍵值對的形式出現,可使用ActivityInfo
、ServiceInfo
、ApplicationInfo
隊形的metaData
變量來訪問它們<instrumentation/>
:用來聲明Instrumentation
測試類來監視應用的行爲<uses-sdk/>
:指定應用所需使用的Android SDK
的最高版本、最低版本、目標版本。<uses-configration/>
:聲明應用須要的一些系統配置,一共有五種定義 <uses-configuration android:reqFiveWayNav=["true" | "false"] android:reqHardKeyboard=["true" | "false"] android:reqKeyboardType=["undefined" | "nokeys" | "qwerty" | "twelvekey"] android:reqNavigation=["undefined" | "nonav" | "dpad" | "trackball" | "wheel"] android:reqTouchScreen=["undefined" | "notouch" | "stylus" | "finger"] />
複製代碼
<uses-feture/>
:聲明應用使用的軟件或硬件feature
<uses-library/>
:指定應用須要使用的用戶庫,系統將會在應用運行時裝載這些庫<support-screens/>
:指定應用支持的屏幕種類到這裏,
Android
已經把APK
解析完成,並建立了一個完整的Package
對象。那麼接下來就是經過scanPackageChildLI
方法將解析好的Package
對象添加到系統中的mPackages
中了
scanDirLI
之scanPackageChildLI
前面已經講過,對於掃描成功的應用,會循環執行scanPackageChildLI()
將其添加到系統中。
代碼以下:
private PackageParser.Package scanPackageChildLI(......){
......
// 掃描當前應用
PackageParser.Package scannedPkg = addForInitLI(pkg, parseFlags,
scanFlags, currentTime, user);
// 循環掃描子應用
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPackage = pkg.childPackages.get(i);
addForInitLI(childPackage, parseFlags, scanFlags,
currentTime, user);
}
......// 省略一個遞納入口
return scannedPkg;
}
複製代碼
scanPackageChildLI()
方法最重要的操做就是經過addForInitLI()
方法來對當前應用作進一步的解析,主要流程以下:
private PackageParser.Package addForInitLI(PackageParser.Package pkg, @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, @Nullable UserHandle user) throws PackageManagerException {
......
// 設置應用資源、代碼等相關的路徑
pkg.setApplicationVolumeUuid(pkg.volumeUuid);
pkg.setApplicationInfoCodePath(pkg.codePath);
pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
pkg.setApplicationInfoResourcePath(pkg.codePath);
pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
// 判斷系統應用是否須要升級
synchronized (mPackages) {
......
// 檢查應用是否屬於rename標籤
renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
......
// 檢查是否屬於update標籤
disabledPkgSetting = mSettings.getDisabledSystemPkgLPr(disabledPkgName);
......
}
// 判斷升級的重點是將當前package與mSettings中的進行比對
// 咱們知道mSettings中的package數據基本上都是從platform.xml文件中解析出來的
// 這種對比至關於當前掃描到的應用和其歷史信息的比對,這裏主要是檢查<renamed-package/>和<updated-package/>標籤
......
// 掃描應用文件的簽名
collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify);
......
// 處理應用包名有衝突的狀況
boolean shouldHideSystemApp = false;
if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
&& !pkgSetting.isSystem()) {
// 當前掃描到的應用是系統應用,可是也存在一個同名的普通應用
// 先比較簽名
if (...) {
......
// 簽名不匹配,刪除掃描到系統的應用
deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);
pkgSetting = null;
} else if (newPkgVersionGreater) {
......
// 簽名相同,當前系統應用版本高,移除普通應用
InstallArgs args = createInstallArgsForExisting(...);
synchronized (mInstallLock) {
args.cleanUpResourcesLI();
}
} else {
// 簽名相同,當前系統應用版本低,須要記錄升級關係
// 先把系統應用隱藏
shouldHideSystemApp = true;
......
}
}
// 調用 scanPackageNewLI 繼續掃描
final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
if (shouldHideSystemApp) {
synchronized (mPackages) {
mSettings.disableSystemPackageLPw(pkg.packageName, true);
}
}
return scannedPkg;
}
複製代碼
又繼續調用了scanPackageNewLI()
來掃描,咱們簡單看看作了什麼:
private PackageParser.Package scanPackageNewLI(......) throws PackageManagerException {
......
synchronized (mPackages) {
......
SharedUserSetting sharedUserSetting = null;
if (pkg.mSharedUserId != null) {
// 若是當前應用的sharedUserId不爲空的話
// 將該sharedUserId添加到Settings的集合中
sharedUserSetting = mSettings.getSharedUserLPw(
pkg.mSharedUserId, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
......
}
......
try {
......
// 繼續調用 scanPackageOnlyLI 進行掃描操做
// scanPackageOnlyLI 基本上就是組裝PackageSetting對象的過程
// ScanResult 中會包含掃描解析出的PackageSetting對象
final ScanResult result = scanPackageOnlyLI(request, mFactoryTest, currentTime);
if (result.success) {
// 掃描成功後,將應用相關數據組織到系統中
commitScanResultsLocked(request, result);
}
}
......
}
return pkg;
}
複製代碼
代碼中會先對應用的sharedUserId
進行檢查,不爲空的話添加到Settings
的mSharedUsers
集合中。
接下來,調用scanPackageOnlyLI
作進一步的掃描,掃描成功後會調用commitScanResultsLocked()
把package
添加到PMS
的mPackages
中。
scanPackageOnlyLI
和commitScanResultsLocked()
就不細講了哈,對這兩個方法留有印象就好啦。
咱們下面重點了解下PMS
啓動後應用安裝的過程。下一篇深刻Android系統(十)PMS-3-應用安裝過程