淺談Android Apk安裝到解析
app 安裝的流程:
1 網路下載應用安裝――通過應用市場完成,沒有安裝介面
2 ADB工具安裝――沒有安裝介面。
3 第三方應用安裝――通過SD卡里的APK檔案安裝,有安裝介面,由 packageinstaller.apk應用處理安裝及解除安裝過程的介面。
安裝其實就是把apk檔案copy到了對應的目錄
1 system/app ——系統自帶的應用程式,獲得adb root許可權才能刪除 裡面儲存的都是系統的 app - apk 檔案
2 data/app —————使用者程式安裝的目錄。安裝時把 apk檔案複製到此目錄 可以將檔案取出並安裝,和我們本身的apk 是一樣的。
3 data/data —————開闢存放應用程式的資料的資料夾
包括我們應用的 so庫,快取檔案 等等。
PackageManagerService原始碼:
首先我們看下packageManagerService的 main
方法中的程式碼:
public static final PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
ServiceManager.addService("package", m);
return m;
}
這段程式碼呢,我們可以看到通過new 的方式建立了一個物件,並新增到了 ServiceManager
中,serviceManager 內部是一個 HashMap的集合,儲存了很多相關的 binder
服務,快取起來,我們在使用的時候, 會通過 getService(key)
的方式去 map
在建構函式中,是用同步的方式初始化了解析所需要的檔案目錄:
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
我們可以看到在 建構函式中呼叫了 scanDirLI
方法, 我們繼續跟進。
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();
for (File file : files) {
... // 進行校驗檔案 格式
try {
scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
... // 刪除了無效的檔案目錄
}
}
}
這裡遍歷的檔案目錄,就是我們上面的初始化的File, 比如我們 app的目錄 就是data/app
下。 進行了遍歷,下面我們進scanPackageLI
看看它都做了什麼?
在裡面做了兩件比較重要的事情:
1.建立了PackageParser
物件
PackageParser pp = new PackageParser();
2.呼叫了 parsePackage
方法 並返回了 PackageParser.Package
物件。
pkg = pp.parsePackage(scanFile, parseFlags);
我們要在這裡稍微停一下, 說說這個PackageParser.Package
. 為啥要停呢? 它有啥特別之處麼? 讓我們看一下它的類的資訊就清楚了:
public final static class Package {
public String packageName;
/** Names of any split APKs, ordered by parsed splitName */
public String[] splitNames;
// TODO: work towards making these paths invariant
/**
* Path where this package was found on disk. For monolithic packages
* this is path to single base APK file; for cluster packages this is
* path to the cluster directory.
*/
public String codePath;
/** Path of base APK */
public String baseCodePath;
/** Paths of any split APKs, ordered by parsed splitName */
public String[] splitCodePaths;
/** Flags of any split APKs; ordered by parsed splitName */
public int[] splitFlags;
public boolean baseHardwareAccelerated;
// For now we only support one application per package.
public final ApplicationInfo applicationInfo = new ApplicationInfo();
public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
public final ArrayList<Service> services = new ArrayList<Service>(0);
public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);
public final ArrayList<String> requestedPermissions = new ArrayList<String>();
public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>();
... // 部分程式碼。
}
Ok, 你也看到了, 裡面定義了 我們的packageName
、apk 路徑 baseCodePath
以及 各種 Arraylist 來儲存我們的 Activity
、Service
、許可權
等等。
好的,我們繼續往下面走。 下面我們的 主角從 PackageManagerService
切換到了 我們的 PackageParser
.
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
我們看到parsePakcage
轉呼叫了parseMonolithicPackage
方法,讓我們繼續。
@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
if (mOnlyCoreApps) {
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
}
}
final AssetManager assets = new AssetManager();
try {
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.codePath = apkFile.getAbsolutePath();
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}
不多說,直接進入我們的 parseBaseApk
方法。 這裡有兩點用說:
- 呼叫了
loadApkIntoAssetManager
方法呢,就是把我們app 的資源新增到了AssetManager
。
int cookie = assets.addAssetPath(apkPath);
2.呼叫了過載方法 解析AndroidManifest.xml
檔案
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {}
解析Xml的程式碼,我們看著個分支:
if (tagName.equals("application")) {
... // 省略部分程式碼
if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
return null;
}
}
直接來看 parseBaseApplication
中的操作:
1.解析了application 節點中的相關資訊 如 icon
、theme
等。
2.解析了application 節點下各個子節點的資訊。
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
owner.baseHardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.activities.add(a);
} else if (tagName.equals("receiver")) {
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.receivers.add(a);
} else if (tagName.equals("service")) {
Service s = parseService(owner, res, parser, attrs, flags, outError);
if (s == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.services.add(s);
}
我們只看其中的一個節點:
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
owner.baseHardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.activities.add(a);
}
parseActivity
中解析了 <activity>
節點下的相關資訊,比如 exported
、tag
、theme
、intent-filter
等,封裝成了一個類 即Activity
。 然後執行了
owner.activities.add(a);
也就是新增到了 PackageParser.Package
中。
總結
從整體的角度看,我們所做的這一系列操作,其實只是在做一件事,建立一個PackageParser.Package
物件,然後填充它,然後 return 回去。