1. 程式人生 > >淺談Android Apk安裝到解析

淺談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 來儲存我們的 ActivityService許可權 等等。

好的,我們繼續往下面走。 下面我們的 主角從 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 方法。 這裡有兩點用說:

  1. 呼叫了 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 節點中的相關資訊 如 icontheme等。

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> 節點下的相關資訊,比如 exportedtagthemeintent-filter 等,封裝成了一個類 即Activity。 然後執行了

owner.activities.add(a);

也就是新增到了 PackageParser.Package 中。

總結

從整體的角度看,我們所做的這一系列操作,其實只是在做一件事,建立一個PackageParser.Package 物件,然後填充它,然後 return 回去。