Android动态部署二:APK安装及AndroidManifest.xml解析流程分析

Posted ximsfei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android动态部署二:APK安装及AndroidManifest.xml解析流程分析相关的知识,希望对你有一定的参考价值。

转载请注明出处:http://blog.csdn.net/ximsfei/article/details/50886134

github地址:https://github.com/ximsfei/DynamicDeploymentApk
在上一篇文章:Android动态部署:Google原生Split APK浅析中,简单描述了Google实现SplitApk的机制。
接下来我们就开始一步步的实践,自己手动实现非安装apk的动态加载。
首先来了解一下APK解析安装的源码:
我们可以通过adb命令来安装apk到android手机中:

1. adb push xxx /data/app
   此处/data/app第三方应用的安装目录,还可以为/system/app:系统应用,/vendor/app:方案商应用等。
2. adb install xxx
3. adb shell pm install yyy

其中adb push命令需要重启手机才能安装成功,xxx为宿主电脑中的apk路径,yyy则为Android手机中的路径

使用adb push 安装apk

源码解析:通过adb push命令将APK放到/data/app目录,手机重启后,会在PackageManagerService的构造方法中解析该apk,至于PackageManagerService这个类的构造方法何时调用,大家可以看看Android framework的启动源码,这里只需要知道,在Android启动后,SystemServer类中会调用PackageManagerService中的main方法。
SystemServer.java

private void startBootstrapServices() 
    ...
    mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
    ...

PackageManagerService.java

public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) 
    PackageManagerService m = new PackageManagerService(context, installer,
            factoryTest, onlyCore);
    ServiceManager.addService("package", m);
    return m;

调用ServiceManager的addService方法可能会引起PERMISSION DENIED问题:浅谈android add_service PERMISSION DENIED问题

下图为手机启动后PackageManagerService app解析的时序图,其中红色标注的为我们在研究过程需要关注的点:

  1. PackageParser.java中的new AssetManager & new Resources
    我们可以根据APK的绝对路径,及Host APK的Resources初始化一个插件APK的Resources对象
AssetManager assets = AssetManager.class.newInstance();
Reflect.create().setClass(AssetManager.class)
        .setMethod("addAssetPath", String.class).invoke(assets, apkPath);
Resources res = new Resources(assets, context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration());

其中Reflect.java为自己封装的Java反射类,简单的说,就是通过反射调用AssetManager中的hide方法:addAssetPath

  1. PackageParser.java中的parseBaseApk & parseBaseApplication
    parseBaseApk方法会解析AndroidManifest.xml中包含的所有信息,最重要的部分为其调用的parseBaseApplication方法,在parseBaseApplication方法中会解析该APK所包含的四大组件的所有信息
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
        String[] outError) throws XmlPullParserException, IOException 
    AttributeSet attrs = parser;
    ...

    final Package pkg = new Package(pkgName);
    TypedArray sa = res.obtainAttributes(attrs,
            com.android.internal.R.styleable.AndroidManifest);

    ...

    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) 

        ...

        String tagName = parser.getName();
        if (tagName.equals("application")) 
            ...

            if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) 
                return null;
            
         else if (tagName.equals("overlay")) 
            ...
            XmlUtils.skipCurrentTag(parser);
         else if (tagName.equals("key-sets")) 
            if (!parseKeySets(pkg, res, parser, attrs, outError)) 
                return null;
            
         else if (tagName.equals("permission-group")) 
            if (parsePermissionGroup(pkg, flags, res, parser, attrs, outError) == null) 
                return null;
            
         else if (tagName.equals("permission")) 
            if (parsePermission(pkg, res, parser, attrs, outError) == null) 
                return null;
            
         else if (tagName.equals("permission-tree")) 
            if (parsePermissionTree(pkg, res, parser, attrs, outError) == null) 
                return null;
            
         else if (tagName.equals("uses-permission")) 
            if (!parseUsesPermission(pkg, res, parser, attrs)) 
                return null;
            
         else if (tagName.equals("uses-permission-sdk-m")
                || tagName.equals("uses-permission-sdk-23")) 
            if (!parseUsesPermission(pkg, res, parser, attrs)) 
                return null;
            
         
        ...
    
    ...

    return pkg;

private boolean parseBaseApplication(Package owner, Resources res,
        XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
    throws XmlPullParserException, IOException 
    final ApplicationInfo ai = owner.applicationInfo;
    final String pkgName = owner.applicationInfo.packageName;

    TypedArray sa = res.obtainAttributes(attrs,
            com.android.internal.R.styleable.AndroidManifestApplication);

    ...    //application标签解析

    int type;
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) 
        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) 
            continue;
        

        String tagName = parser.getName();
        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);

         else if (tagName.equals("provider")) 
            Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
            if (p == null) 
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            

            owner.providers.add(p);

         else if (tagName.equals("activity-alias")) 
            Activity a = parseActivityAlias(owner, res, parser, attrs, flags, outError);
            if (a == null) 
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            

            owner.activities.add(a);

        
        ...
    

    ...
    return true;
  1. PackageManagerService.java中的scanPackageDirtyLI方法
    在该方法中,会扫描1,2中解析出来的信息,并且保存到本地变量中,方便后续调用queryIntent,resolveIntent等方法,检查该组件是否存在,并返回
// Currently known shared libraries.
final ArrayMap<String, SharedLibraryEntry> mSharedLibraries =
        new ArrayMap<String, SharedLibraryEntry>();
// All available activities, for your resolving pleasure.
final ActivityIntentResolver mActivities =
        new ActivityIntentResolver();
// All available receivers, for your resolving pleasure.
final ActivityIntentResolver mReceivers =
        new ActivityIntentResolver();
// All available services, for your resolving pleasure.
final ServiceIntentResolver mServices = new ServiceIntentResolver();

// All available providers, for your resolving pleasure.
final ProviderIntentResolver mProviders = new ProviderIntentResolver();
......
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
        int scanFlags, long currentTime, UserHandle user) throws PackageManagerException 
    final File scanFile = new File(pkg.codePath);

    ...
    // writer
    synchronized (mPackages) 
        // Add the new setting to mSettings
        mSettings.insertPackageSettingLPw(pkgSetting, pkg);
        // Add the new setting to mPackages
        mPackages.put(pkg.applicationInfo.packageName, pkg);
        ...

        int N = pkg.providers.size();
        StringBuilder r = null;
        int i;
        for (i=0; i<N; i++) 
            PackageParser.Provider p = pkg.providers.get(i);
            p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    p.info.processName, pkg.applicationInfo.uid);
            mProviders.addProvider(p);

            ...
        
        ...

        N = pkg.services.size();
        r = null;
        for (i=0; i<N; i++) 
            PackageParser.Service s = pkg.services.get(i);
            s.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    s.info.processName, pkg.applicationInfo.uid);
            mServices.addService(s);

            ...
        
        ...

        N = pkg.receivers.size();
        r = null;
        for (i=0; i<N; i++) 
            PackageParser.Activity a = pkg.receivers.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName, pkg.applicationInfo.uid);
            mReceivers.addActivity(a, "receiver");
            ...
        
        ...

        N = pkg.activities.size();
        r = null;
        for (i=0; i<N; i++) 
            PackageParser.Activity a = pkg.activities.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName, pkg.applicationInfo.uid);
            mActivities.addActivity(a, "activity");
            ...
        
        ...

        N = pkg.permissionGroups.size();
        r = null;
        for (i=0; i<N; i++) 
            PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
            PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
            if (cur == null) 
                mPermissionGroups.put(pg.info.name, pg);
                ...
            
        

        ...
    

    return pkg;

使用adb install xxx & adb shell pm install yyy安装apk

下图为adb install安装APK的时序图,最终流程和使用adb push安装相同:

总结

这篇文章主要介绍了源码中APK安装以及AndroidManifest.xml解析的部分源码,在做Android 动态部署时,我们都会去自己解析AndroidManifest.xml文件,从中获取该插件APK中的信息,有哪些Activity,Receiver,Service,Provider等,我们自己写代码去解析固然好,不过我较为推荐的是porting源码中的PackageParser.java以及PackageManager.java, PackageManagerService.java中的部分代码,毕竟源码对AndroidManifest.xml的解析较为充分,不会有遗漏,而且porting过程中可以对源码有更深的理解,为我们后续的实现打好基础。

以上是关于Android动态部署二:APK安装及AndroidManifest.xml解析流程分析的主要内容,如果未能解决你的问题,请参考以下文章

Android动态部署五:如何从插件apk中启动Service

Android动态部署五:如何从插件apk中启动Service

Android动态部署三:如何从插件apk中启动Activity(-)

MobSF 框架安装使用部署

Android动态部署一:Google原生Split APK浅析

Android动态部署四:如何从插件apk中启动Activity