Android插件化(六): OpenAtlasの改寫aapt以防止資源ID衝突

Android插件化(六): OpenAtlasの改寫aapt以防止資源ID衝突

轉 https://www.300168.com/yidong/show-2791.htmlhtml

 
核心提示:引言Android應用程序的編譯中,負責資源打包的是aapt,若是不對打包後的資源ID進行控制,就會致使插件中的資源ID衝突。因此,咱們須要改寫aapt的源碼,以達到經過某種方式傳遞資源ID的Package ID,經過aapt打包時獲取到這個Package ID而且應用才插件資源的命名

引言

Android應用程序的編譯中,負責資源打包的是aapt,若是不對打包後的資源ID進行控制,就會致使插件中的資源ID衝突。因此,咱們須要改寫aapt的源碼,以達到經過某種方式傳遞資源ID的Package ID,經過aapt打包時獲取到這個Package ID而且應用才插件資源的命名中。java

1.改造aapt的目的:防止資源衝突

咱們前面知道了插件中的類的加載是經過DelegateClassLoader來進行的,那麼插件中資源的加載呢?node

其實也是經過DelegateResources進行的,只不過DelegateResources的更新是發生在每一個插件安裝完成後。在BundleLifecycleHandler的bundleChanged()方法中,監聽到BundleEvent.LOADED便開始加載,loaded()方法以下:android

//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()方法:git

  //加載宿主中的資源時,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()方法中:github

  /********將資源加入宿主程序中,最先調用這裏的是從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等)能夠由宿主提供便可,減少插件包的大小。app

可是,這樣的話,因爲不隔離,若是兩個插件的資源ID相同(可是卻對應不一樣的資源),就會形成資源ID的衝突。框架

首先了解一下Android應用程序的編譯和打包過程。用一張圖歸納以下:ide

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 看到。

相關文章
相關標籤/搜索