Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突

   2017-02-05 0
核心提示:引言Android应用程序的编译中,负责资源打包的是aapt,如果不对打包后的资源ID进行控制,就会导致插件中的资源ID冲突。所以,我们需要改写aapt的源码,以达到通过某种方式传递资源ID的Package ID,通过aapt打包时获取到这个Package ID并且应用才插件资源的命名

引言

Android应用程序的编译中,负责资源打包的是aapt,如果不对打包后的资源ID进行控制,就会导致插件中的资源ID冲突。所以,我们需要改写aapt的源码,以达到通过某种方式传递资源ID的Package ID,通过aapt打包时获取到这个Package ID并且应用才插件资源的命名中。

1.改造aapt的目的:防止资源冲突

我们前面知道了插件中的类的加载是通过DelegateClassLoader来进行的,那么插件中资源的加载呢?

其实也是通过DelegateResources进行的,只不过DelegateResources的更新是发生在每个插件安装完成后。在BundleLifecycleHandler的bundleChanged()方法中,监听到BundleEvent.LOADED便开始加载,loaded()方法如下:

//bundle是BundleImpl对象,该对象中的bundleDir是类似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test"这样的
    private void loaded(Bundle bundle) {
        long currentTimeMillis = System.currentTimeMillis();
        BundleImpl bundleImpl = (BundleImpl) bundle;
        try {
            DelegateResources.newDelegateResources(
                    RuntimeVariables.androidApplication,
                    RuntimeVariables.delegateResources, bundleImpl.getArchive().getArchiveFile().getAbsolutePath());
        } catch (Throwable e) {
            log.error("Could not load resource in bundle "
                            + bundleImpl.getLocation(), e);
        }
        if (DelegateComponent.getPackage(bundle.getLocation()) == null) {
            //注意:会在这里解析出PackageLite对象,其实就是解析Manifest文件中的内容,Application和4大组件等都会解析出来
            PackageLite parse = PackageLite.parse(bundleImpl.getArchive()
                    .getArchiveFile());
            log.info("Bundle installation info " + bundle.getLocation() + ":"
                    + parse.components);
            DelegateComponent.putPackage(bundle.getLocation(), parse);
        }
        log.info("loaded() spend "
                + (System.currentTimeMillis() - currentTimeMillis)
                + " milliseconds");
    }

显然是调用newDelegateResources()方法:

  //加载宿主中的资源时,newPath为空;加载插件中的资源时,newPath类似"data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so";
    public static void newDelegateResources(Application application, Resources resources, String newPath) throws Exception {
        if (Thread.currentThread().getId() == Looper.getMainLooper().getThread().getId()) {
            newDelegateResourcesInternal(application, resources, newPath);
            return;
        }
        synchronized (lock) {
            new Handler(Looper.getMainLooper()).post(new DelegateResourcesGetter(application, resources, newPath));
            lock.wait();
        }
    }

再进入到DelegateResources.newDelegateResourcesInternal()方法中:

  /********将资源加入宿主程序中,最早调用这里的是从FrameworkLifecycleHandler的frameworkEvent()中开始,是将宿主中的资源加入到宿主的AsssetManager中.newPath是类似"/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so"
     * @param newPath 新插件的路径
     * ******/
    private static void newDelegateResourcesInternal(Application application, Resources resources, String newPath) throws Exception {
        AssetManager assetManager;
        if (ignoreOpt || VERSION.SDK_INT <= 20 || assetPathsHistory == null) {
            Set<String> generateNewAssetPaths = generateNewAssetPaths(application, newPath);//generateNewAssetPaths中的对象类似["/data/app/cn.edu.zafu.atlasdemo-1.apk","/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so"]
            if(generateNewAssetPaths.size()>2){ //generateNewAssetPaths.size()>2时,其为["/data/app/cn.edu.zafu.atlasdemo-1.apk","/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so","/data/ddata/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_zxing.so"]
                Log.d(TAG,"generateNewAssetPaths.size()>2");
            }
            if (generateNewAssetPaths != null) {
                Resources delegateResources;
                //这个新建的assetManager即新的delegateResources的AssetManager对象,利用它生成delegateResources
                assetManager = AssetManager.class.newInstance();
                for (String assetPath : generateNewAssetPaths) { //但assetPath为"/data/app/cn.edu.zafu.atlasdemo-1.apk"时,即为宿主apk路径时,assetManager.addAssetPath()结果为0表示执行失败,当执行失败时,会在尝试3次
                    try {//一般OpentAtlasHacks.AssetManager_addAssetPath.invoke(assetManager,assetPath)能够执行成功,所以不需要更多尝试
                        if (Integer.parseInt(OpenAtlasHacks.AssetManager_addAssetPath.invoke(assetManager, assetPath).toString()) == 0) {
                            for (int i = 0; i < 3; i++) { //再尝试3次
                                if (Integer.parseInt(OpenAtlasHacks.AssetManager_addAssetPath.invoke(assetManager, assetPath).toString()) != 0) {
                                    break;
                                }
                                if (i == 3) { //如果尝试3次之后仍然失败,则打出log
                                    OpenAtlasMonitor.getInstance().trace(Integer.valueOf(-1), assetPath, "", "Add asset path failed");
                                }

                            }

                        }
                    } catch (NumberFormatException e) {
                        e.printStackTrace();
                    }
                }
                if (resources == null || !resources.getClass().getName().equals("android.content.res.MiuiResources")) {//如果是翔米UI需要使用MiuiResources
                    delegateResources = new DelegateResources(assetManager, resources);
                } else { //MiuiResources的话需要特殊处理
                    Constructor<?> declaredConstructor = Class.forName("android.content.res.MiuiResources").getDeclaredConstructor(AssetManager.class, DisplayMetrics.class, Configuration.class);
                    declaredConstructor.setAccessible(true);//新建MiuiResources作为delegateResources,并且其中的一个assetManager为刚刚建立的assetManager,包含了宿主和当前插件中的资源,所以通过delegateResources既可以引用插件中的资源,也可以引用宿主中的资源
                    delegateResources = (Resources) declaredConstructor.newInstance(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
                }
                RuntimeVariables.delegateResources = delegateResources;  //application是类似BootApp对象这样的宿主Application,delegateResources
                //AndroidHack.injectResources()中利用delegateResources替换LoadedApk中的mResources
                AndroidHack.injectResources(application, delegateResources);
                assetPathsHistory = generateNewAssetPaths;
                if (log.isDebugEnabled()) {
                    StringBuffer stringBuffer = new StringBuffer();
                    stringBuffer.append("newDelegateResources [");
                    for (String append : generateNewAssetPaths) {
                        stringBuffer.append(append).append(",");
                    }
                    stringBuffer.append("]");
                    if (newPath != null) {
                        stringBuffer.append("Add new path:" + newPath);
                    }
                    log.debug(stringBuffer.toString());
                    return;
                }
                return;
            }
            return;
        }
        assetManager = application.getAssets();
        if (!TextUtils.isEmpty(newPath) && !assetPathsHistory.contains(newPath)) {
            OpenAtlasHacks.AssetManager_addAssetPath.invoke(assetManager, newPath);
            assetPathsHistory.add(newPath);
        }
    }

这个方法主要做了以下事情:

  • 新建一个AssetManager对象
  • 通过反射调用AssetManager的addAssetPath将当前插件的资源路径添加进去,如果添加失败则尝试3次,3次之后还是失败则给出log
  • 如果添加成功,则根据该AssetManager对象生成delegateResources这个DelegateResources或Resources对象,其中对于Miui做了兼容
  • 将最新的delegateResources对象赋予RuntimVariables.delegateResources,并且将当前的插件路径赋值给assetPathsHistory

总结起来可以发现:OpenAtlas中管理资源的方式是每安装一个插件,就新建一个AssetManager,并且将之前的资源和插件中的资源都加入到唯这个AssetManager对象的的管理中,之后利用这个AssetManager对象生成新的delegate Resources对象,再利用反射将这个对象注入到LoadedApk中。这样统一管理的好处是一些基础资源(如主题,logo等)可以由宿主提供即可,减小插件包的大小。

但是,这样的话,由于不隔离,如果两个插件的资源ID相同(但是却对应不同的资源),就会造成资源ID的冲突。

首先了解一下Android应用程序的编译和打包过程。用一张图概括如下:

Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突

从图中可以清楚地看到Android的编译过程:先是利用aapt编译Manifest,Resources和Assets资源,生成R文件和打包好的资源文件,之后利用javac将java源码编译成字节码,利用NDK将native源码编译成.so库,之后利用dx将所有的字节码(jar包和之前编译的字节码)编译成dex文件(如果有混淆的话需要加上混淆规则),最后利用apkbuilder将dex文件、so库和打包好的资源文件一起编译成apk,如果需要签名的话,再利用签名程序(jarsigner)进行签名。

其中aapt称为Android Asset Package Tool,它的作用是将XML资源文件从文本格式编译成二进制格式,并且会执行以下两个额外的操作:

  • 赋予每个非assets资源一个ID值,这些ID值以常量的形式定义在一个R.java文件中
  • 生成一个resources.arsc文件,用来描述那些具有ID值的资源的配置信息,它的内容就相当于是一个资源索引表

有了资源ID以及资源索引表之后,Android资源管理框架就可以迅速将根据设备当前配置信息来定位最匹配的资源了。

如果大家对Android应用程序的编译和打包过程不熟悉,可以看老罗的这篇博客 Android应用程序资源的编译和打包过程分析 .

我们在编译一个Android应用程序的资源的时候,至少会涉及到两个包,其中一个是被引用的系统资源包,另外一个就跟当前正在编译的应用程序资源包。每个包都可以定义自己的资源,同时它也可以引用其他包的资源。

那么,一个包是通过什么方式来引用其它包的资源的呢?这就是我们熟悉的资源ID了。资源ID是一个4字节的无符号整数,其中,最高字节表示Package ID,次高字节表示Type ID,最低两字节表示Entry ID。

Package ID相当于是一个命名空间,限定资源的来源。Android系统当前定义了两个资源命令空间,其中一个系统资源命令空间,它的Package ID等于0x01,另外一个是应用程序资源命令空间,它的Package ID等于0x7f。所有位于[0x01, 0x7f]之间的Package ID都是合法的,而在这个范围之外的都是非法的Package ID。前面提到的系统资源包package-export.apk的Package ID就等于0x01,而我们在应用程序中定义的资源的Package ID的值都等于0x7f,这一点可以通过生成的R.java文件来验证。

Type ID是指资源的类型ID。资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。

Entry ID是指每一个资源在其所属的资源类型中所出现的次序。注意,不同类型的资源的Entry ID有可能是相同的,但是由于它们的类型不同,我们仍然可以通过其资源ID来区别开来。

显然,要使各插件的资源ID不冲突,可以通过控制各个插件的Package ID来达到,即使用0x02-0x7E之间的id,如下是一种插件id的架构:

Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突

那么如何达到控制package id的目的呢?

当然要通过修改aapt的源码来达到。

2.aapt的改写

aapt的源码在/frameworks/base/tools/aapt下,这里以Android API 22的appt源码为例进行分析。先看Main.cpp中的main()函数:

/*
 * Parse args.
 */
int main(int argc, char* const argv[])
{  isUpdatePkgId=0;
    char *prog = argv[0];
    Bundle bundle;
    bool wantUsage = false;
    int result = 1;    // pessimistically assume an error.
    int tolerance = 0;

    /* default to compression */
    bundle.setCompressionMethod(ZipEntry::kCompressDeflated);

    if (argc < 2) {
        wantUsage = true;
        goto bail;
    }

    if (argv[1][0] == 'v')
        bundle.setCommand(kCommandVersion);
    else if (argv[1][0] == 'd')
        bundle.setCommand(kCommandDump);
    else if (argv[1][0] == 'l')
        bundle.setCommand(kCommandList);
    else if (argv[1][0] == 'a')
        bundle.setCommand(kCommandAdd);
    else if (argv[1][0] == 'r')
        bundle.setCommand(kCommandRemove);
    else if (argv[1][0] == 'p')
        bundle.setCommand(kCommandPackage);
    else if (argv[1][0] == 'c')
        bundle.setCommand(kCommandCrunch);
    else if (argv[1][0] == 's')
        bundle.setCommand(kCommandSingleCrunch);
    else if (argv[1][0] == 'm')
        bundle.setCommand(kCommandDaemon);
    else {
        fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]);
        wantUsage = true;
        goto bail;
    }
    argc -= 2;
    argv += 2;

    /*
     * Pull out flags.  We support "-fv" and "-f -v".
     */
    while (argc && argv[0][0] == '-') {
        /* flag(s) found */
        const char* cp = argv[0] +1;

        while (*cp != '\0') {
            switch (*cp) {
            case 'v':
                bundle.setVerbose(true);
                break;
            case 'a':
                bundle.setAndroidList(true);
                break;
            ...

            default:
                fprintf(stderr, "ERROR: Unknown flag '-%c'\n", *cp);
                wantUsage = true;
                goto bail;
            }

            cp++;
        }
        argc--;
        argv++;
    }

    /*
     * We're past the flags.  The rest all goes straight in.
     */
    bundle.setFileSpec(argv, argc);

    result = handleCommand(&bundle);

bail:
    if (wantUsage) {
        usage();
        result = 2;
    }

    //printf("--> returning %d\n", result);
    return result;
}

这里省略了main()中对于aapt参数的处理,直接进入handleCommand()函数中:

/*
 * Dispatch the command.
 */
int handleCommand(Bundle* bundle)
{
    //printf("--- command %d (verbose=%d force=%d):\n",
    //    bundle->getCommand(), bundle->getVerbose(), bundle->getForce());
    //for (int i = 0; i < bundle->getFileSpecCount(); i++)
    //    printf("  %d: '%s'\n", i, bundle->getFileSpecEntry(i));

    switch (bundle->getCommand()) {
    case kCommandVersion:      return doVersion(bundle);
    case kCommandList:         return doList(bundle);
    case kCommandDump:         return doDump(bundle);
    case kCommandAdd:          return doAdd(bundle);
    case kCommandRemove:       return doRemove(bundle);
    case kCommandPackage:      return doPackage(bundle);
    case kCommandCrunch:       return doCrunch(bundle);
    case kCommandSingleCrunch: return doSingleCrunch(bundle);
    case kCommandDaemon:       return runInDaemonMode(bundle);
    default:
        fprintf(stderr, "%s: requested command not yet supported\n", gProgName);
        return 1;
    }
}

显然,handleCommand()是用于分发命令的,我们的是打包资源的命令,所以是调用doPackage(bundle);其中doPackage()方法如下:

/*
 * Package up an asset directory and associated application files.
 */
int doPackage(Bundle* bundle)
{
    const char* outputAPKFile;
    int retVal = 1;
    status_t err;
    sp<AaptAssets> assets;
    int N;
    FILE* fp;
    String8 dependencyFile;
    sp<ApkBuilder> builder;

    // -c en_XA or/and ar_XB means do pseudolocalization
    sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
    err = configFilter->parse(bundle->getConfigurations());
    if (err != NO_ERROR) {
        goto bail;
    }
    if (configFilter->containsPseudo()) {
        bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
    }
    if (configFilter->containsPseudoBidi()) {
        bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
    }

    N = bundle->getFileSpecCount();
    if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
            && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
        fprintf(stderr, "ERROR: no input files\n");
        goto bail;
    }

    outputAPKFile = bundle->getOutputAPKFile();

    // Make sure the filenames provided exist and are of the appropriate type.
    if (outputAPKFile) {
        FileType type;
        type = getFileType(outputAPKFile);
        if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
            fprintf(stderr,
                "ERROR: output file '%s' exists but is not regular file\n",
                outputAPKFile);
            goto bail;
        }
    }

    // Load the assets.
    assets = new AaptAssets();

    // Set up the resource gathering in assets if we're going to generate
    // dependency files. Every time we encounter a resource while slurping
    // the tree, we'll add it to these stores so we have full resource paths
    // to write to a dependency file.
    if (bundle->getGenDependencies()) {
        sp<FilePathStore> resPathStore = new FilePathStore;
        assets->setFullResPaths(resPathStore);
        sp<FilePathStore> assetPathStore = new FilePathStore;
        assets->setFullAssetPaths(assetPathStore);
    }

    err = assets->slurpFromArgs(bundle);
    if (err < 0) {
        goto bail;
    }

    if (bundle->getVerbose()) {
        assets->print(String8());
    }

    // Create the ApkBuilder, which will collect the compiled files
    // to write to the final APK (or sets of APKs if we are building
    // a Split APK.
    builder = new ApkBuilder(configFilter);

    // If we are generating a Split APK, find out which configurations to split on.
    if (bundle->getSplitConfigurations().size() > 0) {
        const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
        const size_t numSplits = splitStrs.size();
        for (size_t i = 0; i < numSplits; i++) {
            std::set<ConfigDescription> configs;
            if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
                fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
                goto bail;
            }

            err = builder->createSplitForConfigs(configs);
            if (err != NO_ERROR) {
                goto bail;
            }
        }
    }

    // If they asked for any fileAs that need to be compiled, do so.
    if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
        err = buildResources(bundle, assets, builder);
        if (err != 0) {
            goto bail;
        }
    }

   ...
}

这里省略了编译资源之后输出R.java文件等代码,可以看出编译资源的代码是buildResources():

status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
{
    // First, look for a package file to parse.  This is required to
    // be able to generate the resource information.
    sp<AaptGroup> androidManifestFile =
            assets->getFiles().valueFor(String8("AndroidManifest.xml"));
    if (androidManifestFile == NULL) {
        fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
        return UNKNOWN_ERROR;
    }

    status_t err = parsePackage(bundle, assets, androidManifestFile);
    if (err != NO_ERROR) {
        return err;
    }

    NOISY(printf("Creating resources for package %s\n",
                 assets->getPackage().string()));

    ResourceTable::PackageType packageType = ResourceTable::App;
    if (bundle->getBuildSharedLibrary()) {
        packageType = ResourceTable::SharedLibrary;
    } else if (bundle->getExtending()) {
        packageType = ResourceTable::System;
    } else if (!bundle->getFeatureOfPackage().isEmpty()) {
        packageType = ResourceTable::AppFeature;
    }

    ResourceTable table(bundle, String16(assets->getPackage()), packageType);
    err = table.addIncludedResources(bundle, assets);
    if (err != NO_ERROR) {
        return err;
    }

    ...

    return err;
}

这里省略了很多无关的代码,其中的PackageType就是与Package ID有关的,它的定义如下:

enum PackageType{
  App,
  System,
  SharedLibrary,
  AppFeature
}

显然,分为普通应用类型,系统类型,共享库类型和AppFeature类型。其中ResourceTable类的构造函数如下:

ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
    : mAssetsPackage(assetsPackage)
    , mPackageType(type)
    , mTypeIdOffset(0)
    , mNumLocal(0)
    , mBundle(bundle)
{
    ssize_t packageId = -1;
    switch (mPackageType) {
        case App:
        case AppFeature:
            packageId = 0x7f;
            break;

        case System:
            packageId = 0x01;
            break;

        case SharedLibrary:
            packageId = 0x00;
            break;

        default:
            assert(0);
            break;
    }
    sp<Package> package = new Package(mAssetsPackage, packageId);
    mPackages.add(assetsPackage, package);
    mOrderedPackages.add(package);

    // Every resource table always has one first entry, the bag attributes.
    const SourcePos unknown(String8("????"), 0);
    getType(mAssetsPackage, String16("attr"), unknown);
}

在这里就可以很明显的看出PackageType与packageId的关系.

看到这里,就可以知道,需要控制插件的packageId,就需要修改ResourceTable的构造函数,在其中传入对应插件的packageId。

这里有两个思路:第一种是将在Bundle中增加字段,将这个参数放在bundle中(因为bundle是从main()函数中一路传递下来的);第二种是通过全局变量来引用。

而bunnyblue采用的是第二种方法(其实这种方法不优美).

bunnyblue具体的实现方法是:在插件的build.gradle中的versionName中同时声明versionName和packageId,如下:

 defaultConfig {
        applicationId "com.lizhangqu.test"
        minSdkVersion 10
        targetSdkVersion 22
        versionCode 1
        versionName "1.00x20"
    }

到了aapt中在进行处理,分离为"1.0"这个versionName和0x20这个插件的packageId.

具体是如何分离的呢?

在Main.cpp的main()方法中,有这么一行:

  int main(int argc, char* const argv[]){

    ...

  case 'M':
                argc--;
                argv++;
                if (!argc) {
                    fprintf(stderr, "ERROR: No argument supplied for '-M' option\n");
                    wantUsage = true;
                    goto bail;
                }
                convertPath(argv[0]);
                bundle.setAndroidManifestFile(argv[0]);
                hack_getVersionName(&bundle);
                break;

    ...

}

注意其中的hack_getVersionName(&bundle);该方法在Resourcehack.cpp中,如下:

void hack_getVersionName(Bundle* bundle){
//  return ;
  fprintf(stderr, "hack version ibundle->getAndroidManifestFile()X%s,  \n",bundle->getAndroidManifestFile());
  String8 srcFile(bundle->getAndroidManifestFile());
  fprintf(stderr, "hack version ibundle->getAndroidManifestFile() %s , %s 1\n",srcFile.getPathLeaf().string(),  srcFile.getPathDir().string());
  AaptFile *mAaptFile=new AaptFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir());
  fprintf(stderr, "hack version ibundle->getAndroidManifestFile()2\n");
  const sp<AaptFile> manifestFile(mAaptFile);
  fprintf(stderr, "hack version ibundle->getAndroidManifestFile()3\n");
  String8 manifestPath(bundle->getAndroidManifestFile());
  fprintf(stderr, "hack version dump info ..get default versionName%s\n",manifestPath.string());
  fprintf(stderr, "hack version ibundle->getAndroidManifestFile()4\n");
  // Generate final compiled manifest file.
  //manifestFile->clearData();
  sp<XMLNode> root = XMLNode::parse(bundle->getAndroidManifestFile());
  fprintf(stderr, "hack version ibundle->getAndroidManifestFile()6\n");
  if (root == NULL) {
    if(!access(bundle->getAndroidManifestFile(),0)){}else{
      fprintf(stderr, "no  found 7\n");
    }
    fprintf(stderr, "no node 7\n");
      return ;
  }
 hack_massageManifest(root);

// root = root->searchElement(String16(), String16("manifest"));
//
// const XMLNode::attribute_entry* attrlocal = root->getAttribute(
//         String16(RESOURCES_ANDROID_NAMESPACE), String16("versionName"));
// if (attrlocal != NULL) {
//   fprintf(stderr, "hack version dump info ..get default versionName%s\n",strdup(String8(attrlocal->string).string()));
//   char * versionNameMisc=strdup(String8(attrlocal->string).string());//bunny
//   if(strlen(versionNameMisc)>5){
//     char resOffset[64]={0};
//     strncpy(resOffset,versionNameMisc+strlen(versionNameMisc)-4,4);
//     if(resOffset[0]=='0'&&resOffset[1]=='x'){
//       pkgIdOffset=strtol(resOffset,NULL,16);
//     }
//     fprintf(stderr, "hack version is ok,found new version packageID %s \n",resOffset);
//   }else{
//     fprintf(stderr, "hack version is failed,versionName should endwith 0xXX  \n");
//
//   }
//
//
//
// }
  //delete(mAaptFile);
  fprintf(stderr, "hack version ibundle->getAndroidManifestFile()7\n");

}

显然,由于bundle.gradle中的versionName会写入到Manifest文件中,所以这里通过解析Manifest文件来获取插件的packageId,在hack_messageManifest()中:

void hack_massageManifest( sp<XMLNode> root)
{
    root = root->searchElement(String16(), String16("manifest"));

    const XMLNode::attribute_entry* attrlocal = root->getAttribute(
            String16(RESOURCES_ANDROID_NAMESPACE), String16("versionName"));
    if (attrlocal != NULL) {
      fprintf(stderr, "hack version dump info ..get default versionName%s\n",strdup(String8(attrlocal->string).string()));
      char * versionNameMisc=strdup(String8(attrlocal->string).string());//bunny
      if(strlen(versionNameMisc)>5){
        char resOffset[64]={0};
        strncpy(resOffset,versionNameMisc+strlen(versionNameMisc)-4,4);
        if(resOffset[0]=='0'&&resOffset[1]=='x'){
          pkgIdOffset=strtol(resOffset,NULL,16);
          isUpdatePkgId=1;
        }
        fprintf(stderr, "hack version is ok,found new version packageID %s \n",resOffset);
      }else{
        fprintf(stderr, "hack version is failed,versionName should endwith 0xXX  \n");

      }



    }
    // if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionName",
    //         "0x7f", errorOnFailedInsert, true)) {
    //     return UNKNOWN_ERROR;
    // } else {
    //     const XMLNode::attribute_entry* attr = root->getAttribute(
    //             String16(RESOURCES_ANDROID_NAMESPACE), String16("versionName"));
    //     if (attr != NULL) {
    //       fprintf(stderr, "hack version dump info... %s\n",strdup(String8(attr->string).string()));
    //         bundle->setVersionName(strdup(String8(attr->string).string()));
    //     }
    // }



}

显然,在这里分离出了插件的packageId并赋值给了全局变量pkgIdOffset,而这个pkgIdOffset是在Main.cpp中定义的:

int pkgIdOffset=0x7f;

显然,默认值为0x7f;而pkgIdOffset的使用当然是在ResourceTable的构造函数中:

ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
    : mAssetsPackage(assetsPackage)
    , mPackageType(type)
    , mTypeIdOffset(0)
    , mNumLocal(0)
    , mBundle(bundle)
{
    ssize_t packageId = -1;
    switch (mPackageType) {
        case App:
        case AppFeature:
            packageId = pkgIdOffset;
            break;

        case System:
            packageId = 0x01;
            break;

        case SharedLibrary:
            packageId = 0x00;
            break;

        default:
            assert(0);
            break;
    }
    sp<Package> package = new Package(mAssetsPackage, packageId);
    mPackages.add(assetsPackage, package);
    mOrderedPackages.add(package);

    // Every resource table always has one first entry, the bag attributes.
    const SourcePos unknown(String8("????"), 0);
    getType(mAssetsPackage, String16("attr"), unknown);
}

显然,对于mPackageType为App和AppFeature的,packageId=pkgIdOffset.这样就获取到了我们写在插件项目的build.gradle中的packageId值。

不过,我自己觉得最好的处理方案是在Manifest中增加一个packageId的attr,之后在aapt的main()中解析出这个结果,并且放入bundle的字段中,最终在ResoureTable中对于App和AppFeature,去bundle中的该字段作为packageId.

改造后的源码可以在 OpenAtlasExtension 看到。

 
标签: 安卓开发
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 安卓中通知功能的具体实现
    安卓中通知功能的具体实现
    通知[Notification]是Android中比较有特色的功能,当某个应用程序希望给用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知实现。使用通知的步骤1、需要一个NotificationManager来获得NotificationManager manager = (NotificationManager
    02-05 安卓开发
  • Android view系统分析-setContentView
    Android view系统分析-setContentView
    第一天上班,列了一下今年要学习的东西。主要就是深入学习Android相关的系统源代码,夯实基础。对于学习Android系统源代码,也没什么大概,就从我们平常使用最基础的东西学起,也就是从view这个切入点开始学习Android的源码,在没分析源码之前,我们有的时候
    02-05 安卓开发
  • 如何进行网络视频截图/获取视频的缩略图
    如何进行网络视频截图/获取视频的缩略图
    小编导读:获取视频的缩略图,截图正在播放的视频某一帧,是在音视频开发中,常遇到的问题。本文是主要用于点播中截图视频,同时还可以获取点播视频的缩略图进行显示,留下一个问题,如下图所示, 如果要获取直播中节目视频缩略图,该怎么做呢?(ps:直播是直
  • Android NDK 层发起 HTTP 请求的问题及解决
    Android NDK 层发起 HTTP 请求的问题及解决
    前言新的一年,大家新年快乐~~鸡年大吉!本次给大家带来何老师的最新文章~虽然何老师还在过节,但依然放心不下广大开发者,在此佳节还未结束之际,给大家带来最新的技术分享~ 事件的起因不说了,总之是需要实现一个 NDK 层的网络请求。为了多端适用,还是选择
  • Android架构(一)MVP架构在Android中的实践
    Android架构(一)MVP架构在Android中的实践
    为什么要重视程序的架构设计 对程序进行架构设计的原因,归根结底是为了 提高生产力 。通过设计是程序模块化,做到模块内部的 高聚合 和模块之间的 低耦合 (如依赖注入就是低耦合的集中体现)。 这样做的好处是使得程序开发过程中,开发人员主需要专注于一点,
    02-05 安卓开发
  • 安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件 作者: 飞龙 这个教程中我们要分析一个锁机软件。像这种软件都比较简单,完全可以顺着入口看下去,但我这里还是用关键点来定位。首先这个软件的截图是这样,进入这个界面之后,除非退出模拟器,否则没办法回到桌面。上面那
    02-05 安卓开发
  • Android插件化(二):OpenAtlas插件安装过程分析
    Android插件化(二):OpenAtlas插件安装过程分析
    在前一篇博客 Android插件化(一):OpenAtlas架构以及实现原理概要 中,我们对应Android插件化存在的问题,实现原理,以及目前的实现方案进行了简单的叙述。从这篇开始,我们要深入到OpenAtlas的源码中进行插件安装过程的分析。 插件的安装分为3种:宿主启动时立
    02-05 安卓开发
  • [译] Android API 指南
    [译] Android API 指南
    众所周知,Android开发者有中文网站了,API 指南一眼看去最左侧的菜单都是中文,然而点进去内容还是很多是英文,并没有全部翻译,我这里整理了API 指南的目录,便于查看,如果之前还没有通读,现在可以好好看一遍。注意,如果标题带有英文,说明官方还没有翻
  • 使用FileProvider解决file:// URI引起的FileUriExposedException
    使用FileProvider解决file:// URI引起的FileUri
    问题以下是一段简单的代码,它调用系统的相机app来拍摄照片:void takePhoto(String cameraPhotoPath) {File cameraPhoto = new File(cameraPhotoPath);Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);takePhotoIntent.putExtra(Medi
    02-05 安卓开发
  • Supporting Multiple Screens
    术语和概念Screen size 屏幕尺寸又称「屏幕大小」,是屏幕对角线的物理尺寸。单位英寸 inch,比如 Samsung Note4 是 5.7 英寸。Resolution 屏幕分辨率屏幕纵横方向上物理像素的总数,比如 Samsung Note4 是 2560x1440,表示纵向有 2560 个像素,横向有 1440
    02-05 安卓开发
点击排行