Atlas框架源码简要分析(下)--Bundle资源处理

Atlas框架源码简要分析(下)–Bundle资源处理

在上文中以启动一个在没有安装的Bundle中的Activity为例,整理了整个Bundle安装到该Activity对象实例化出来的过程,当该Bundle安装之后,对应的BundleClassLoader实例化完成之后,在<中>2.10.2处会对Bundle中的资源进行处理。我们下面就对资源的处理步骤详细展开

对于系统对资源的管理可以参考:http://blog.csdn.net/luoshengyang/article/details/8738877

1.1 重新贴出Bundle资源处理的入口代码如下:
1.1.1 此处为Bundle中资源处理的入口点,处理的根本思路就是想办法把该Bundle安装位置信息(即资源路径)添加到系统的资源查找路径列表中去

DelegateResources.addBundleResources()中第一个参数是,当前Bundle当前版本解压之后所在的位置,也就是资源文件所在的位置,第二个参数是Patch文件的位置。

@BundleLifecycleHandler.java
private void loaded(Bundle bundle) {//在BundleImpl建立之后,此时bundle解压的位置,及其中的ClassLoader已经初始化完毕,则会发送CMD=0的指令到达bundleChanged,进而调用此处开始:资源加载起始点
    BundleImpl b = (BundleImpl) bundle;

    try {
          DelegateResources.addBundleResources(b.getArchive().getArchiveFile().getAbsolutePath(),
                  b.getArchive().getCurrentRevision().getDebugPatchFilePath());//-------1.1.1 DelegateResources的addBundleResource(),添加当前bundle的资源到系统中去
    } catch (Exception e) {
        e.printStackTrace();
    }
}
.....//省略代码
}
1.2 调用DelegateResources添加当前bundle中的资源到系统中,具体代码和简要逻辑如下,(此处的debug资源也包括更新的patch资源)
@DelegateResource.java
//代理资源类,该方法相当于一个工具方法,此处的RuntimeVariables.delegateResources 是在Atlas启动之后即赋值的最初默认值为主bundle的Resource
public static void addBundleResources(String assetPath,String debugPath)  throws Exception{
    synchronized (DelegateResources.class) {
        if(debugPath!=null && !findResByAssetIndexDescending()){//------1.2.1此处主要兼容SDK20的查找方式不同,以确定debug包中的资源是否在实际资源之前加载
            updateResources(RuntimeVariables.delegateResources, debugPath, BUNDLE_RES);

        }
        updateResources(RuntimeVariables.delegateResources, assetPath, BUNDLE_RES);//------1.2.2更新一开始一同默认的resource,主要是把当前bundle的资源路径,通过反射添加到原有resource的路径上去,**各bundle在打包时,资源对应的id是打包为一个R文件的,里面记录的是对应资源的序列号,此处需要再看怎么根据这个号找到具体位置的**
        if(debugPath!=null && findResByAssetIndexDescending()){//------1.2.3 添加debug资源的路径
            updateResources(RuntimeVariables.delegateResources, debugPath, BUNDLE_RES);

        }
    }
}   
1.2.1 处主要兼容SDK20的资源查找方式不同,以确定debug包中的资源是否在实际资源之前加载

在SDK>20版本中资源查找的方式是按照资源路径列表降序查找的,也就是说后加入的路径会先去查找,而我们的debug资源目的是想要后查找,即先去正常资源中查找然后没有再到debug的资源中去查找,所以在SDK>20的版本中即findResByAssetIndexDescending()为true时就先执行跳过该步骤,先执行1.2.2添加正常的资源路径,否则就先执行此处代码添加debug的资源

  • 总之就是保证在查找资源时先从该Bundle的正常资源路径中找然后再到debug的资源路径中去查找
1.2.2 处理我们bundle的资源路径
  • 该处的RuntimeVariables.delegateResources 是在Atlas启动之后即赋值的最初默认值为主bundle的Resource,也就是系统生成的那一份resource,显然该使用该resource,我们是找不到bundle中的资源的,应为其默认的搜索路径是apk安装包位置

  • 该处的assetPath就是我们前面传入的b.getArchive().getArchiveFile().getAbsolutePath()即当前bundle解压之后的路径

1.2.3 此处和1.2.1处对应如果1.2.1处不执行则在此处执行debug中的资源处理
1.3 bundle资源更新处理,接上面1.2.2处逻辑,该处及最终处理资源的逻辑,分别处理了Resource中的AssetManager,然后处理Resource。展开其逻辑及代码如下
@DelegateResource.java
private static void updateResources(Resources res,String assetPath,int assertType) throws Exception{//更新当前Resource中资源路径集合,把新加入的bundle资源路径添加进去
    if(sAssetManagerProcessor==null){
        sAssetManagerProcessor = new AssetManagerProcessor();//-------1.3.1DelegateResource 的内部类里面有自己保存的一份资源列表
    }
    AssetManager updatedAssetManager = sAssetManagerProcessor.updateAssetManager(res.getAssets(), assetPath, assertType);//-------1.3.2更新AssetManager,此处只是更新AssetManager,然后我们需要把代理过的新的Manager重新给放入到Resource中去

    if(sResourcesProcessor==null){
        sResourcesProcessor = getResourceProcessor();//------1.3.3拿取ResourceProcessor
    }
    sResourcesProcessor.updateResources(updatedAssetManager);//-------1.3.4更新Resource中的AssetsManager为我们代理的AssetsManager

    if(sResourcesFetcher==null){
        sResourcesFetcher = new ResourceIdFetcher();
    }
}
1.3.1 如果sAssetManagerProcessor为null则初始化AssetManagerProcessor,该类来处理系统当前的AssetManager,初始化代码如下:
@AssetManagerProcessor.java
private static class AssetManagerProcessor {

    private static HashMap<String,Boolean> sDefaultAssetPathList  ;
    static {
        try {
            AssetManager manager = AssetManager.class.newInstance();
            ArrayList<String> defaultPaths = getAssetPath(manager);
            if(defaultPaths!=null && defaultPaths.size()>0){
                sDefaultAssetPathList = new HashMap<String,Boolean>();
                for(String path : defaultPaths){
                    sDefaultAssetPathList.put(path,Boolean.FALSE);
                }
            }
        }catch (Throwable e){}finally {
            if(sDefaultAssetPathList==null){
                sDefaultAssetPathList = new HashMap<String,Boolean>(0);
            }
        }
    }
    private LinkedHashMap<String,Boolean> assetPathCache = null;
    private LinkedHashMap<String,Boolean> preAssetPathCache = null;

    public AssetManagerProcessor(){
        assetPathCache = new LinkedHashMap<String,Boolean>();
        preAssetPathCache = new LinkedHashMap<String,Boolean>();
        assetPathCache.put(RuntimeVariables.androidApplication.getApplicationInfo().sourceDir,Boolean.FALSE);
    }
}
1.3.2 调用AssetManagerProcessor的updateAssetManager(res.getAssets(), assetPath, assertType)函数,具体展开代码及逻辑如下:
  • 参数res.getAssets():res即为传入的系统Resource,res.getAssets()得到的即为当前使用的AsstManager
  • 参数assetPath:当前bundle解压之后的路径,即当前bundle的资源路径
  • 参数assertType:从1.2中我们可知此时传入的是DelegateResource.BUNDLE_RES

    @AssetManagerProcessor.java
    public AssetManager updateAssetManager(AssetManager manager,String newAssetPath,int assetType)throws Exception{
        AssetManager targetManager = null;
        if(assetType == BUNDLE_RES){
            if(supportExpandAssetManager()){//-----1.3.2.1是否支持直接扩展AssetManager API20以下及部分品牌的手机不支持
                try {
                    targetManager = updateAssetManagerWithAppend(manager, newAssetPath, BUNDLE_RES);//-----1.3.2.2直接更新此处传入的manager
                }catch(Throwable e){
                    e.printStackTrace();
                    Log.e("DelegateResources","walkround to createNewAssetmanager");
                    targetManager = createNewAssetManager(manager,newAssetPath,true,BUNDLE_RES);
                }
            }else{
                targetManager = createNewAssetManager(manager,newAssetPath,true,BUNDLE_RES);//-----1.3.2.3不支持直接添加则会先走走此方法
            }
            updateAssetPathList(newAssetPath,true);//-----1.3.2.4更新自己的存的一份
        }else{
          ....//省略只看对应BundleRes的情况
        }
    
        return targetManager;
    }
    
1.3.2.1 判断是否支持直接调用相关方法扩展AssetManager中的资源路径信息列表,并能在添加之后直接生成该路径下resources.arsc对应的ResTable。SDK20以下及部分品牌的手机是不支持的

此处判定的依据如下:

  • 在API=20及以下:当调用addAssetPathNative()后在C层只是添加了保存了该路径,但是并没有去生成对应该Bundle资源的Asset并添加到ResTable中,并且当我们在getResource()的时候又由于在C层mResources已经初始化即mResources!=null会直接返回原有的mResources即ResTable,导致实际并没有对该新加的Bundle资源路径进行解析,所以此时需要重新新建一个AssetManager,并添加该路径到mAssetPaths,这样在调用getResources()的时候,此时mResources==null,就会根据当前的mAssetPaths去重新初始化该Resource对应的ResTable。对应返回为false,往下执行到【1.3.2.3】 createNewAssetManager()函数
  • 在API=20以上:我们在调用addAssetPathNative()之后,C层会直接处理当前新加入的资源路径,并更新其中的mResource,所以此时我们不需要在重新创建一个AssetManager,对应返回为true,往下执行到【1.3.2.2】 updateAssetManagerWithAppend()函数

  • 对于hasCreatedAssetsManager条件,我们可以简单的对应与代码第一次执行到此处时我们此处一定返回的是false,即第一次执行到此初时一定是往下执行了【1.3.2.3】 createNewAssetManager()函数

Atlas框架中简单的判定逻辑代码如下:

@AssetManagerProcessor.java
private synchronized boolean supportExpandAssetManager(){
        if(!hasCreatedAssetsManager || Build.VERSION.SDK_INT<=20 ||
                Build.BRAND.equalsIgnoreCase("sony") || Build.BRAND.equalsIgnoreCase("semc") ||
                (Build.BRAND.equalsIgnoreCase("xiaomi")&& (Build.MODEL.toLowerCase().startsWith("mibox")))){
            hasCreatedAssetsManager = true;
            return false;
        }else{
            return true;
        }
    }
下面也简单的贴出C层主要的代码,以了解为什么给不同的版本做不一样的处理逻辑:

版本:API<=20,C层代码AssetManager.cpp 如下:

@AssetManager.cpp API<=20

const ResTable& AssetManager::getResources(bool required) const//getResource()调用到此处
{
    const ResTable* rt = getResTable(required);
    return *rt;
}

const ResTable* AssetManager::getResTable(bool required) const
{
    ResTable* rt = mResources;//在API<=20中app启动起来之后该处的mResources已经被初始化,所以再次调用时直接return,会导致我们在addAssetPath()中添加的资源路径下的资源没有实际解析出来
    if (rt) {
        return rt;
            }
    .....//省略代码API<=20的,此处主要是循环遍历mAssetPaths并一次解析其中的Resource.arsc

}


bool AssetManager::addAssetPath(const String8& path, void** cookie)//调用的添加资源路径的方法
{
    AutoMutex _l(mLock);

    asset_path ap;

    String8 realPath(path);
    if (kAppZipName) {
        realPath.appendPath(kAppZipName);
    }
    ap.type = ::getFileType(realPath.string());
    if (ap.type == kFileTypeRegular) {
        ap.path = realPath;
    } else {
        ap.path = path;
        ap.type = ::getFileType(path.string());
        if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
            ALOGW("Asset path %s is neither a directory nor file (type=%d).",
                 path.string(), (int)ap.type);
            return false;
        }
    }

    // Skip if we have it already.
    for (size_t i=0; i<mAssetPaths.size(); i++) {
        if (mAssetPaths[i].path == ap.path) {
            if (cookie) {
                *cookie = (void*)(i+1);
            }
        }
    }

    ALOGV("In %p Asset %s path: %s", this,
         ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());

    mAssetPaths.add(ap);//在路径中添加新增的路径

    // new paths are always added at the end
    if (cookie) {
        *cookie = (void*)mAssetPaths.size();
    }

  ....

    return true;
}

版本:API>20,C层AssetManager.cpp 代码如下:

const ResTable& AssetManager::getResources(bool required) const//getResource()调用到此处
{
    const ResTable* rt = getResTable(required);
    return *rt;
}

const ResTable* AssetManager::getResTable(bool required) const
{
    ResTable* rt = mResources;//对于API>20的,mResources在addAssetPath()之后,已经调用appendPathToResTable(ap)解析过新路径下的资源文件了
    if (rt) {
        return rt;
            }
    .....//省略代码此处实现和API<20的有不同具体可以查看下源码

}

bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
   AutoMutex _l(mLock);

    asset_path ap;

    String8 realPath(path);
    if (kAppZipName) {
        realPath.appendPath(kAppZipName);
    }
    ap.type = ::getFileType(realPath.string());
    if (ap.type == kFileTypeRegular) {
        ap.path = realPath;
    } else {
        ap.path = path;
        ap.type = ::getFileType(path.string());
        if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
            ALOGW("Asset path %s is neither a directory nor file (type=%d).",
                 path.string(), (int)ap.type);
            return false;
        }
    }

    // Skip if we have it already.
    for (size_t i=0; i<mAssetPaths.size(); i++) {
        if (mAssetPaths[i].path == ap.path) {
            if (cookie) {
                *cookie = static_cast<int32_t>(i+1);
            }
            return true;
        }
    }

    ....

    if (mResources != NULL) {
        appendPathToResTable(ap);//------API>20时多了该方法,主要是当添加了新的路径后,会直接解析新添加的路径下的资源文件
    }

    return true;
}
1.3.2.2 如果是支持直接调用相关方法进行添加资源路径的则调用到此处updateAssetManagerWithAppend(manager, newAssetPath, BUNDLE_RES),具体展开代码及逻辑如下:

主要就是反射调用AssetManager中的addAssetPathNative()方法,添加资源路径,注意此处传入的AssetManager和返回的AssetManager是同一个对象

1.3.2.2.1 满足type != APK_RES,此时type=BUNDLE_RES,我们只关注对于bundleRes的条件
1.3.2.2.2 此时当前API版本支持直接直接反射调用addAssetPathNative(),则反射调用AssetsManager中的addAssetPathNative(),追加新的资源路径,注意此时我们直接调用了native层的方法添加了对应的新的资源路径,但是在AssetManager中的mStringBlocks字段并没有更新新加入的资源路径下的相关resources.arsc对应的字符资源池,所以下面需要手动更新该字段中对应的信息

此处所有反射的方法都是addAssetPathNative(),不同的主要是兼容不同品牌的参数信息,具体我们可以自己查看对应的代码处查看,AtlasHacks中的代码还是很漂亮的

1.3.2.2.3 此处拿取当前Java层AssetManager中mStringBlocks中数组,该数组每一个元素都对应每一个被加载的资源表所提取出的全部字符串资源池,上面我们通过反射调用native方法新加了一个路径进去,该过程在native层会解析该路径下对应的resources.arsc,并整理成相关的数据结构,同时会生成一个对应的字符串资源池,但是该Java层的字段还没有改变
1.3.2.2.4 此时通过反射调用getStringBlockCount()最终会得到native层当前对应的StringBlock数量
1.3.2.2.5 我们在java层构建一个对应和native层数量相同的类型为android.content.res.StringBlock的数组,用来替换当前AssetManager中的mStringBlocks
1.3.2.2.6 依次添加java层原有的StringBlock到该数组,同时通过调用getNativeStringBlock()方法拿取native层新加入的StringBlock,并放入新构建的数组中
1.3.2.2.7 此处就是直接替换Java层AssetManager中mStringBlocks字段的值,至此则对应Bundle中的资源在nativeceng和AssetManager中都替换完毕
    @AssetManagerProcessor.java 该类是DelegateResource的内部类
    private AssetManager updateAssetManagerWithAppend(AssetManager manager,String newAssetPath,int type) throws Exception{
        synchronized (manager) {
            if(type == APK_RES) {//如果是APK资源
               .....
            }else{//------1.3.2.2.1 bundle资源会走到此处
                int retryCount = 2;
                int cookie = 0;
                do {
                    retryCount--;
                    //1.3.2.2.2 add native path  反射调用AssetsManager中的addAssetPathNative(),追加新的资源路径,此处方法不同主要是兼容不同品牌的参数信息
                    if (AtlasHacks.AssetManager_addAssetPathNative!=null && AtlasHacks.AssetManager_addAssetPathNative.getMethod()!=null) {
                        cookie = (int)AtlasHacks.AssetManager_addAssetPathNative.invoke(manager, newAssetPath);
                    } else if(AtlasHacks.AssetManager_addAssetPathNative24!=null && AtlasHacks.AssetManager_addAssetPathNative24.getMethod()!=null){
                        cookie = (int)AtlasHacks.AssetManager_addAssetPathNative24.invoke(manager, newAssetPath, false);
                    } else if(AtlasHacks.AssetManager_addAssetPathNativeSamSung!=null && AtlasHacks.AssetManager_addAssetPathNativeSamSung.getMethod()!=null){
                        cookie = (int)AtlasHacks.AssetManager_addAssetPathNativeSamSung.invoke(manager, newAssetPath, 0);
                    } else{
                        throw new RuntimeException("no valid addassetpathnative method");
                    }
                    if(cookie>0){
                        break;
                    }
                }while(retryCount>0);
                if(cookie>0) {//添加完成后-需要修改 AssetsManager中的mStringBlocks,为什么不直接hack addAssetPath()方法???
                    //2. getSeedNum
                    //-----1.3.2.2.3 此处拿取当前Java层AssetManager中mStringBlocks中数组,该数组每一个元素都对应每一个被加载的资源表,上面我们通过反射调用native方法新加了一个路径进去,则此时在native层会解析该路径下对应的resources.arsc,并整理成相关的数据结构,同时会生成一个对应的字符串资源池,但是该Java层的字段还没有改变
                    Object[] mStringBlocks = (Object[]) AtlasHacks.AssetManager_mStringBlocks.get(manager);
                    int seedNum = mStringBlocks.length;

                    //3. getStringBlockCount
                    //-----1.3.2.2.4 在前面我们已经通过反射调用addAssetPathNative()方法把当前Bundle的资源路径加入,该过程在native层会解析该路径下对应的resources.arsc,并整理成相关的数据结构,同时会生成一个对应的字符串资源池。此时通过getStringBlockCount()最终会得到native层当前对应的StringBlock数量
                    int num = (int) AtlasHacks.AssetManager_getStringBlockCount.invoke(manager);

                    //4. init newStringBlockList
                    //-----1.3.2.2.5 我们在java层构建一个对应和native层数量相同的类型为android.content.res.StringBlock的数组
                    Object newStringBlockList = Array.newInstance(AtlasHacks.StringBlock.getmClass(), num);
                    //-----1.3.2.2.6 依次添加java层原有的StringBlock到该数组,同时通过调用getNativeStringBlock()方法拿取native层新加入的StringBlock,并放入新构建的数组中
                    for (int i = 0; i < num; i++) {
                        if (i < seedNum) {
                            Array.set(newStringBlockList, i, mStringBlocks[i]);
                        } else {
                            Array.set(newStringBlockList, i, AtlasHacks.StringBlock_constructor.getInstance(
                                    AtlasHacks.AssetManager_getNativeStringBlock.invoke(manager, i), true
                            ));
                        }
                    }
                    //5. replace AssetManager.mStringBlocks
                    //-----1.3.2.2.7 此处就是直接替换Java层AssetManager中mStringBlocks字段的值,至此则对应Bundle中的资源在nativeceng和AssetManager中都替换完毕
                    AtlasHacks.AssetManager_mStringBlocks.set(manager, newStringBlockList);
                }else{
                    sFailedAsssetPath.add(newAssetPath);
                    Map<String, Object> detail = new HashMap<>();
                    detail.put("appendAssetPath", newAssetPath);
                    AtlasMonitor.getInstance().report(AtlasMonitor.CONTAINER_APPEND_ASSETPATH_FAIL, detail, new RuntimeException());
                }
            }
        }

        return manager;
    }
1.3.2.3 相对1.3.2.2如果是不支持直接调用相关方法进行添加资源路径,则调用到此处createNewAssetManager(),即重新新建一个AssetManager(manager,newAssetPath,true,BUNDLE_RES),这样能保证我们在调用getResource的时候会重新初始化所有mAssetsPath路径的下的资源,具体展开代码及逻辑如下:

注意此处理逻辑中传入的AssetManager和返回的AssetManager不是同一个对象

@AssetManagerProcessor.java 该类是DelegateResource的内部类
 private static String sWebviewPath = null;
    private AssetManager createNewAssetManager(AssetManager srcManager,String newAssetPath,boolean append,int type) throws Exception{
        AssetManager newAssetManager = AssetManager.class.newInstance();//----1.3.2.3.1直接新建一个AssetManager
        List<String> runtimeAdditionalAssets = new ArrayList<String>();
        List<String> currentPaths = getAssetPath(srcManager);//------1.3.2.3.2拿取原有的资源路径集合,主要用来筛选webView相关的路径,至于为什么要筛选出关于webview和chrome并最后再添加进去??
        for(String currentPath : currentPaths){
            if(!sDefaultAssetPathList.containsKey(currentPath) && !assetPathCache.containsKey(currentPath)
                    && !preAssetPathCache.containsKey(currentPath) && !currentPath.equals(newAssetPath)){
                if(currentPath.toLowerCase().contains("webview") || currentPath.toLowerCase().contains("chrome")) {
                    runtimeAdditionalAssets.add(currentPath);
                }
            }
        }
        if(Build.VERSION.SDK_INT>=24) {
            //7.0版本 webivew 特殊path下的兜底策略
            ....//省略代码
        }
        sFailedAsssetPath.clear();
        if(!append){//------1.3.2.3.3append 为true标识为追加,即新添加的放在后面,否则放在前面,即先添加,此处Bundle资源加载的时候默认给的是true,所以此处跳过
            appendAssetPath(newAssetManager,newAssetPath,false);
        }
        //逆序加入assetmanager
        if(preAssetPathCache!=null){//------1.3.2.3.4 添加该bundle之前已经安装了的Bundle的资源路径,根据其Bundle添加是传入的append值缓存到preAssetPathCache或者是assetPathCache
            if(preAssetPathCache.size()==1){
                Iterator<Map.Entry<String, Boolean>> iterator = preAssetPathCache.entrySet().iterator();
                appendAssetPath(newAssetManager, iterator.next().getKey(),false);
            }else {
                ListIterator<Map.Entry<String,Boolean>> i=new ArrayList<Map.Entry<String,Boolean>>(preAssetPathCache.entrySet()).listIterator(preAssetPathCache.size());
                while(i.hasPrevious()) {
                    Map.Entry<String, Boolean> entry=i.previous();
                    appendAssetPath(newAssetManager, entry.getKey(),false);
                }
            }
        }

        if(assetPathCache!=null){//------1.3.2.3.5 对应已经安装的Bundle,此时都会缓存到到assetPathCache中,此时依次在重新添加到新实例化出来的AssetManager
            Iterator<Map.Entry<String,Boolean>> iterator= assetPathCache.entrySet().iterator();
            while(iterator.hasNext()){
                Map.Entry<String,Boolean> entry = iterator.next();
                if(!sDefaultAssetPathList.containsKey(entry.getKey())) {
                    appendAssetPath(newAssetManager,entry.getKey(),false);
                }
            }
        }

        if(append){//-----1.3.2.3.6 append==true 则会后添加新的Assets路径
            appendAssetPath(newAssetManager,newAssetPath,false);
        }

        //add additional assets
        if(!runtimeAdditionalAssets.isEmpty()){
            for(String additional : runtimeAdditionalAssets){
                if(Build.VERSION.SDK_INT<24) {
                    appendAssetPath(newAssetManager, additional, false);
                }else{
                    appendAssetPath(newAssetManager, additional, true);
                }
            }
        }

        /**
         * 追加主apk新的assets内容
         */
        if(sAssetsPatchDir!=null){
            appendAssetPath(newAssetManager,sAssetsPatchDir,false);//
        }


        return newAssetManager;
    }
1.3.2.3.1 直接新建一个AssetManager,关于为什么要新建一个AssetManager的原因,在1.3.2.1处已经大致简单的解释过了
1.3.2.3.2 拿取原有的资源路径集合。后面加上新的路径后重新设置进去
1.3.2.3.3 此处append 为true标识为追加,即新添加的资源路径放在后面,否则放在前面,即先添加,此处Bundle资源加载的时候默认给的是true,所以此处跳过
1.3.2.3.4 此处在新建的AssetManager中倒序添加之前缓存的preAssetPathCache中的资源路径
1.3.2.3.5 此处在新建的AssetManager中依次添加之前已经安装的所有bundle的资源路径,其路径在安装各bundle时都会缓存到到assetPathCache中
1.3.2.3.6 在安装Bundle时,传入的append==true 所以此处逻辑被执行,当前正在被安装的bundle的资源路径会被顺序添加到当前新建的AssetManager中
1.3.2.3.7 关于添加路径到AssetManager中调用的方法appendAssetPath(newAssetManager, iterator.next().getKey(),false),最终会调用到addAssetPathInternal(),代码如下:

该处代码比较简单就是直接反射调用AssetManager的addAssetPath()方法添加资源路径

 @AssetManagerProcessor.java 该类是DelegateResource的内部类
 private int addAssetPathInternal(AssetManager asset,String path,boolean shared) throws Exception{
        if(shared){
            //7.0
            return (int)AtlasHacks.AssetManager_addAssetPathAsSharedLibrary.invoke(asset,path);
        }else {
            return (int) AtlasHacks.AssetManager_addAssetPath.invoke(asset, path);
        }
    }
1.3.2.4 更新当前DelegateResource中的缓存数据。展开代码如下

我们安装bundle时,更新资源传入的append=true,所以会把该bundle对应的资源路径缓存到assetPathCache中,我们在1.3.2.3中使用的assetPathCache和preAssetPathCache的数据来源正是此处

private void updateAssetPathList(String newAssetPath,boolean append){
        if(append) {
            assetPathCache.put(newAssetPath,Boolean.FALSE);
        }else{
            //顺序添加,逆序加入assetmanager
            preAssetPathCache.put(newAssetPath,Boolean.FALSE);
        }
    ....
}
1.3.3 拿取ResourceProcessor,当sResourcesProcessor为null时会重新初始化该ResourceProcessor,该类用来处理对应的Resource,对应的getResourceProcessor()方法如下
@DelegateResource
private static ResourcesProcessor getResourceProcessor(){
    if(RuntimeVariables.delegateResources.getClass().getName().equals("android.content.res.MiuiResources")){//应该是小米手机的兼容
        return new MiuiResourcesProcessor();
    }else{
        return new ResourcesProcessor();
    }

}
1.3.4 更新处理当前使用中的Resource中的AssetsManager和当前使用的Resource,我们看展开代码

在1.3.2中我们已经根据相应场景的不同或新建了一个AssetManager或者在原有的AssetManager中追加了当前安装Bundle的路径,并且都能已经能够保证在资源获取时正确的获取的到

@ResourcesProcessor.java 该类为DelegateResource的内部类
public static class ResourcesProcessor{

    public void updateResources(AssetManager assetManager) throws Exception{
        if(RuntimeVariables.delegateResources.getAssets()!=assetManager ||
                RuntimeVariables.delegateResources== null ||
                !(RuntimeVariables.delegateResources instanceof DelegateResources)){//------1.3.4.1更新Resource的条件判断
            RuntimeVariables.delegateResources = createNewResources(assetManager);//------1.3.4.2此处用我们包含了新bundle路径的AssetManager 生成一个DelegateResource
            walkroundActionMenuTextColor(RuntimeVariables.delegateResources);//??????
            AndroidHack.injectResources(RuntimeVariables.androidApplication, RuntimeVariables.delegateResources);//-----1.3.4.3注入DelegateResource,把loadApk中和ContextImpl中的Resource 替换掉

        }
    }

    //-----1.3.4.5创建新的DelegateResources
    Resources createNewResources(AssetManager manager) throws Exception{
        return new DelegateResources(manager, RuntimeVariables.delegateResources);
    }
}
1.3.4.1 我们首先根据代码逻辑看什么情况下需要更新替换掉当前的Resource

对应的判定条件如下

  • 条件1:RuntimeVariables.delegateResources.getAssets()!=assetManager,此时对应我们的AssetManager为新建出来的情况。我们以一个简单的场景为例,当app初次启动且不涉及升级时,当第一个bundle安装过程走到此处时RuntimeVariables.delegateResources当前的值依然是系统自动创建的Resource其中的AssetManager则还是系统原有的AsstManager。但是我们此时传入的AssetsManager从1.3.2.1处的逻辑可知第一次更新AssetManager时一定是走的createNewAssetManager(),所以此处肯定满足RuntimeVariables.delegateResources.getAssets()!=assetManager,并且此时RuntimeVariables.delegateResources 还不是DelegateResource
  • 条件2:RuntimeVariables.delegateResources== null,该条件默认为false,因为初始化时会赋值为当前的系统帮创建的Resource
  • 条件3:!(RuntimeVariables.delegateResources instanceof DelegateResources),初次在系统的Resource被替换为DelegateResource之前该条件成立

综上判定逻辑如下:

  • 1.当我们第一次执行到此处时一定会执行到1.3.4.2->1.3.4.3

  • 2.当在更新AssetManager时满足执行1.3.2.3处代码,即是调用的createNewAssetManager(),此时传入的AssetManager是新实例化出来的,则条件1一定满足,也会执行到1.3.4.2->1.3.4.3

  • 3.当不是第一次执行到此处即当前的Resource已经被替换为了DelegateResources时,且在更新AssetManager时满足执行的是1.3.2.2处即调用的是updateAssetManagerWithAppend()时,此时条件1不成立,条件3不成立,条件2也不成立,则该方法执行完毕

1.3.4.2 此处用我们包含了新bundle路径的AssetManager 生成一个DelegateResource
1.3.4.3 注入DelegateResource,把loadApk中和ContextImpl中的Resource 替换掉,一样是通过反射替换
至此Atlas框架中Bundle安装过程中的资源处理逻辑就结束了,总的来说资源处理的思路是简单的就是找到AssetManager并在其中插入当前Bundle对应的资源路径即可,只是中间做了一些相应的兼容处理。
以上关于Atlas框架的整体代码实现就大致走了一遍,中间肯定有不妥的地方,还请参看的同学做为一个参考,不一致的地方,不妨再去源码中的地方去找一下答案
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值