插件式换肤框架搭建-资源加载源码分析

HongChengDarren HongChengDarren     2022-08-24     297

关键词:

1. 概述


  大部分控件我们都会使用,但是我们未必知道其资源加载的原理,目前换肤的框架比较多我们可以随随便便拿过来用,但早在几年前这些资料是比较少的,如果想做一个换肤的框架那就只能自己一点一点啃源码。
  如果说我们现在不去用第三方的开源框架,要做一个换肤的功能,摆在我们面前的其实只有一个问题需要解决,那就是如何读取另外一个皮肤apk中的资源。
 
  所有分享大纲:2017Android进阶之路与你同行

  视频讲解地址:http://pan.baidu.com/s/1bC3lAQ

技术分享

2. 资源加载源码分析


2.1 我们先来看一下ImageView的scr属性到底是怎么加载图片资源的:

    <ImageView
        android:layout_width="wrap_content"
        android:src="@drawable/app_icon"
        android:layout_height="wrap_content" />

    // ImageView.java 解析属性
    final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
    // 通过TypedArray获取图片
    final Drawable d = a.getDrawable(R.styleable.ImageView_src);
    if (d != null) {
      setImageDrawable(d);
    }

    // TypedArray.getDrawable() 方法
    public Drawable getDrawable(@StyleableRes int index) {
       // 省略部分代码....
       // 加载资源其实是通过mResources去获取的
       return mResources.loadDrawable(value, value.resourceId, mTheme);
    }

2.2 Resource创建过程分析:
  
   我们在Activity中也经常这样使用context.getResources().getColor(R.id.title_color),那么这个Resources实例是怎么创建的呢?我们可以先从context的实现类ContextImpl入手

private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
       ......
       Resources resources = packageInfo.getResources(mainThread);

       if (resources != null) {
       // 不会走此分支,因为6.0中还不支持多屏显示,虽然已经有不少相关代码了,7.0以及正式支持多屏操作了
          if (displayId != Display.DEFAULT_DISPLAY
            || overrideConfiguration != null
            || (compatInfo != null && compatInfo.applicationScale
                    != resources.getCompatibilityInfo().applicationScale)) {
              ......
            }
       }
       ......
       mResources = resources;
}

// packageInfo.getResources 方法
public Resources getResources(ActivityThread mainThread) {
       // 缓存机制,如果LoadedApk中的mResources已经初始化则直接返回,
       // 否则通过ActivityThread创建resources对象
       if (mResources == null) {
           mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                   mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
       }
       return mResources;
}

最终会来到ResourcesManager的getResources方法

    public @NonNull Resources getResources(@Nullable IBinder activityToken,
            @Nullable String resDir, //app资源文件夹路径,实际上是apk文件的路径,如/data/app/包名/base.apk
            @Nullable String[] splitResDirs, //针对一个app由多个apk组成(将原本一个apk切片为若干apk)时,每个子apk中的资源文件夹
            @Nullable String[] overlayDirs,
            @Nullable String[] libDirs, // app依赖的共享jar/apk路径
            int displayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
            // 以apk路径为参数创建key
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    overlayDirs,
                    libDirs,
                    displayId,
                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                    compatInfo);
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
            return getOrCreateResources(activityToken, key, classLoader);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }


    private @NonNull Resources getOrCreateResources(@Nullable IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
        synchronized (this) {
            // .......

            if (activityToken != null) {
                // 根据key从缓存里面找找 ResourcesImpl 
                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
                if (resourcesImpl != null) {
                    if (DEBUG) {
                        Slog.d(TAG, "- using existing impl=" + resourcesImpl);
                    }
                    // 如果 resourcesImpl 有 那么根据resourcesImpl 和classLoader 从缓存找找 Resource
                    return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                            resourcesImpl);
                }

                // We will create the ResourcesImpl object outside of holding this lock.
            } else {
                // .......
            }
        }

        // If we‘re here, we didn‘t find a suitable ResourcesImpl to use, so create one now.
        // 这个比较重要  createResourcesImpl 通过 key
        ResourcesImpl resourcesImpl = createResourcesImpl(key);

        synchronized (this) {
            // .......
            final Resources resources;
            if (activityToken != null) {
                // 根据resourcesImpl和classLoader获取Resources 
                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                        resourcesImpl);
            } else {
                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
            }
            return resources;
        }
    }

    private @NonNull ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
        // 创建AssetManager 
        final AssetManager assets = createAssetManager(key);
        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        // 根据AssetManager 创建一个ResourcesImpl 其实找资源是: Resources -> ResourcesImpl -> AssetManager
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

    @VisibleForTesting
    protected @NonNull AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        // 创建一个AssetManager对象
        AssetManager assets = new AssetManager();

        // resDir can be null if the ‘android‘ package is creating a new Resources object.
        // This is fine, since each AssetManager automatically loads the ‘android‘ package
        // already.
        // 将app中的资源路径都加入到AssetManager对象中
        if (key.mResDir != null) {
            // 这个方法很重要,待会我们就是用它去加载皮肤的apk
            if (assets.addAssetPath(key.mResDir) == 0) {
                throw new Resources.NotFoundException("failed to add asset path " + key.mResDir);
            }
        }

        if (key.mLibDirs != null) {
            for (final String libDir : key.mLibDirs) {
                // 仅仅选择共享依赖中的apk,因为jar中不会有资源文件
                if (libDir.endsWith(".apk")) {
                    // Avoid opening files we know do not have resources,
                    // like code-only .jar files.
                    if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
                        Log.w(TAG, "Asset path ‘" + libDir +
                                "‘ does not exist or contains no resources.");
                    }
                }
            }
        }
        return assets;
    }

    /**
     * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
     * otherwise creates a new Resources object.
     */
    private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
            @NonNull ResourcesImpl impl) {
        // Find an existing Resources that has this ResourcesImpl set.
        final int refCount = mResourceReferences.size();
        for (int i = 0; i < refCount; i++) {
            WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
            // 从软引用缓存里面找一找
            Resources resources = weakResourceRef.get();
            if (resources != null &&
                    Objects.equals(resources.getClassLoader(), classLoader) &&
                    resources.getImpl() == impl) {
                if (DEBUG) {
                    Slog.d(TAG, "- using existing ref=" + resources);
                }
                return resources;
            }
        }

        // Create a new Resources reference and use the existing ResourcesImpl object.
        // 创建一个Resources ,Resource有好几个构造方法,每个版本之间有稍微的差别 
        // 有的版本是用的这一个构造方法 Resources(assets, dm, config, compatInfo)
        Resources resources = new Resources(classLoader);
        resources.setImpl(impl);
        // 加入缓存
        mResourceReferences.add(new WeakReference<>(resources));
        if (DEBUG) {
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
        }
        return resources;
    }

【看了这么多我们大致可以总结一下Resources的创建流程了:】
- packageInfo.getResources(mainThread) -> mainThread.getTopLevelResources() -> mResourcesManager.getResources() -> getOrCreateResources() 这里首先会找ResourcesImpl缓存如果有则会获取Resource缓存返回;
- 如果没有ResourcesImpl缓存,那么回去创建ResourcesImpl,ResourcesImpl的创建依赖于AssetManager ;
- AssetManager的创建是通过直接实例化对象调用了一个addAssetPath(path)方法把应用的apk路径添加到AssetManager,addAssetPath()方法请看源码解释。
- 创建好ResourcesImpl之后会再去缓存中找Resource如果没有,那么则会创建Resource并将其缓存,创建我们看到的源码是new Resources(classLoader),resources.setImpl(impl) 而不同的版本可能是 new Resources(assets, dm, config, compatInfo) 具体请看6.0源码。

3. 加载皮肤资源


  如果大致知道了资源的加载流程以及Resource的创建过程,现在我们要去加载另外一个apk中的资源就好办了,只需要自己创建一个Resource对象,下面这段代码网上找一大堆,如果分析过源码相信你会有更深的认识:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            Resources superRes = getResources();
            // 创建AssetManager,但是不能直接new所以只能通过反射
            AssetManager assetManager = AssetManager.class.newInstance();
            // 反射获取addAssetPath方法
            Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
            // 皮肤包的路径:  本地sdcard/plugin.skin
            String skinPath = Environment.getExternalStorageDirectory().getAbsoluteFile()+ File.separator+"plugin.skin";
            // 反射调用addAssetPath方法
            addAssetPathMethod.invoke(assetManager, skinPath);
            // 创建皮肤的Resources对象
            Resources skinResources = new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());
            // 通过资源名称,类型,包名获取Id
            int bgId = skinResources.getIdentifier("main_bg","drawable","com.hc.skin");
            Drawable bgDrawable = skinResources.getDrawable(bgId);
            // 设置背景
            findViewById(R.id.activity_main).setBackgroundDrawable(bgDrawable);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. AssetManager创建过程分析

  下面的分析希望不要有强迫症,看不懂其实也不打紧因为涉及到JNI。通过前面的分析可知,Android系统中实际对资源的管理是AssetManager类.每个Resources对象都会关联一个AssetManager对象,Resources将对资源的操作大多数委托给了AssetManager。当然有些源码还有一层 ResourcesImpl 刚刚我们也看到了。
  另外还会存在一个native层的AssetManager对象与java层的这个AssetManager对象相对应,而这个native层AssetManager对象在内存的地址存储在java层的AssetManager.mObject中。所以在java层AssetManager的jni方法中可以快速找到它对应的native层的AssetManager对象。

4.1 AssetManager的init()

     /**
     * Create a new AssetManager containing only the basic system assets.
     * Applications will not generally use this method, instead retrieving the
     * appropriate asset manager with {@link Resources#getAssets}.    Not for
     * use by applications.
     * {@hide}
     */
    public AssetManager() {
        synchronized (this) {
            if (DEBUG_REFS) {
                mNumRefs = 0;
                incRefsLocked(this.hashCode());
            }
            init(false);
            if (localLOGV) Log.v(TAG, "New asset manager: " + this);
            ensureSystemAssets();
        }
    }

   // ndk的源码路径
   // frameworks/base/core/jni/android_util_AssetManager.cpp
   // frameworks/base/libs/androidfw/AssetManager.cpp
   private native final void init(boolean isSystem);
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
    if (isSystem) {
        verifySystemIdmaps();
    }
    //  AssetManager.cpp
    AssetManager* am = new AssetManager();
    if (am == NULL) {
        jniThrowException(env, "java/lang/OutOfMemoryError", "");
        return;
    }

    am->addDefaultAssets();

    ALOGV("Created AssetManager %p for Java object %p
", am, clazz);
    env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}

bool AssetManager::addDefaultAssets()
{
    const char* root = getenv("ANDROID_ROOT");
    LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");

    String8 path(root);
    // framework/framework-res.apk  
    // 初始化的时候会去加载系统的framework-res.apk资源
    // 也就是说我们为什么能加载系统的资源如颜色、图片、文字等等
    path.appendPath(kSystemAssets);

    return addAssetPath(path, NULL);
}

4.2 AssetManager的addAssetPath(String path)方法

bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
    asset_path ap;

    // 省略一些校验代码

    // 判断是否已经加载过了
    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;
        }
    }

    // 检查路径是否有一个androidmanifest . xml
    Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
            kAndroidManifest, Asset::ACCESS_BUFFER, ap);
    if (manifestAsset == NULL) {
        // 如果不包含任何资源
        delete manifestAsset;
        return false;
    }
    delete manifestAsset;
    // 添加 
    mAssetPaths.add(ap);

    // 新路径总是补充到最后
    if (cookie) {
        *cookie = static_cast<int32_t>(mAssetPaths.size());
    }

    if (mResources != NULL) {
        appendPathToResTable(ap);
    }

    return true;
}

bool AssetManager::appendPathToResTable(const asset_path& ap) const {
    // skip those ap‘s that correspond to system overlays
    if (ap.isSystemOverlay) {
        return true;
    }

    Asset* ass = NULL;
    ResTable* sharedRes = NULL;
    bool shared = true;
    bool onlyEmptyResources = true;
    MY_TRACE_BEGIN(ap.path.string());
    // 资源覆盖机制,暂不考虑
    Asset* idmap = openIdmapLocked(ap);
    size_t nextEntryIdx = mResources->getTableCount();
    ALOGV("Looking for resource asset in ‘%s‘
", ap.path.string());
    // 资源包路径不是一个文件夹,那就是一个apk文件了
    if (ap.type != kFileTypeDirectory) {
        // 对于app来说,第一次执行时,肯定为0,因为mResources刚创建,还没对其操作
        // 下面的分支 指挥在参数是系统资源包路径时,才执行,
        // 而且系统资源包路径是首次被解析的
        // 第二次执行appendPathToResTable,nextEntryIdx就不会为0了
        if (nextEntryIdx == 0) {
            // mAssetPaths中存储的第一个资源包路径是系统资源的路径,
            // 即framework-res.apk的路径,它在zygote启动时已经加载了
            // 可以通过mZipSet.getZipResourceTable获得其ResTable对象
            sharedRes = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTable(ap.path);
            // 对于APP来说,肯定不为NULL
            if (sharedRes != NULL) {
                // 得到系统资源包路径中resources.arsc个数
                nextEntryIdx = sharedRes->getTableCount();
            }
        }
        // 当参数是mAssetPaths中除第一个以外的其他资源资源包路径,
        // 比如app自己的资源包路径时,走下面的逻辑
        if (sharedRes == NULL) {
            // 检查该资源包是否被其他进程加载了,这与ZipSet数据结构有关,后面在详细介绍
            ass = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTableAsset(ap.path);
            // 对于app自己的资源包来说,一般都会都下面的逻辑
            if (ass == NULL) {
                ALOGV("loading resource table %s
", ap.path.string());
                // 创建Asset对象,就是打开resources.arsc
                ass = const_cast<AssetManager*>(this)->
                    openNonAssetInPathLocked("resources.arsc",
                                             Asset::ACCESS_BUFFER,
                                             ap);
                if (ass != NULL && ass != kExcludedAsset) {
                    ass = const_cast<AssetManager*>(this)->
                        mZipSet.setZipResourceTableAsset(ap.path, ass);
                }
            }
            // 只有在zygote启动时,才会执行下面的逻辑
            // 为系统资源创建 ResTable,并加入到mZipSet里。
            if (nextEntryIdx == 0 && ass != NULL) {
                // If this is the first resource table in the asset
                // manager, then we are going to cache it so that we
                // can quickly copy it out for others.
                ALOGV("Creating shared resources for %s", ap.path.string());
                // 创建ResTable对象,并把前面与resources.arsc关联的Asset对象,加入到这个ResTabl中
                sharedRes = new ResTable();
                sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
                sharedRes = const_cast<AssetManager*>(this)->
                    mZipSet.setZipResourceTable(ap.path, sharedRes);
            }
        }
    } else {
        ALOGV("loading resource table %s
", ap.path.string());
        ass = const_cast<AssetManager*>(this)->
            openNonAssetInPathLocked("resources.arsc",
                                     Asset::ACCESS_BUFFER,
                                     ap);
        shared = false;
    }

    if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
        ALOGV("Installing resource asset %p in to table %p
", ass, mResources);
        // 系统资源包时
        if (sharedRes != NULL) {
            ALOGV("Copying existing resources for %s", ap.path.string());
            mResources->add(sharedRes);
        } else {
            // 非系统资源包时,将与resources.arsc关联的Asset对象加入到Restable中
            // 此过程会解析resources.arsc文件。
            ALOGV("Parsing resources for %s", ap.path.string());
            mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
        }
        onlyEmptyResources = false;

        if (!shared) {
            delete ass;
        }
    } else {
        mResources->addEmpty(nextEntryIdx + 1);
    }

    if (idmap != NULL) {
        delete idmap;
    }
    MY_TRACE_END();

    return onlyEmptyResources;
}

  大家应该之前了解过这个文件resources.arsc, 如果没了解过可以在网上找篇文章看一下。apk在打包的时候会生成它,我们解压apk就应该能够看到他。这里面基本都是存放的资源的索引,之所以不同的分辨率可以加载不同的图片它可是个大功臣。

5. 资源的查找过程

  现在我们回到最开始的loadDrawable()方法,drawable资源是有实际资源文件的。这类资源索引的过程大体上分为两个步骤,解析资源ID代表的资源的路径;装载资源文件并缓存。
  drawable是缓存到Resources.mDrawableCache中。加载drawable的时候,要先检查下这个缓存中是否有,有的话,直接返回,就不需要加载了。没有缓存的话,说明还没加载该资源文件,所以要先加载加载之后在缓存到mDrawableCache中。而loadDrawable()方法中又是通过loadDrawableForCookie()来加载drawable的:

    private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
        // drawable资源项的值是一个字符串,代表文件的路径
        if (value.string == null) {
            throw new NotFoundException("Resource "" + getResourceName(id) + "" ("
                    + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
        }

        final String file = value.string.toString();

       .
        final Drawable dr;

        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
        try {
            if (file.endsWith(".xml")) {
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXml(this, rp, theme);
                rp.close();
            } else {
                // 如果drawable是图片文件的话,打开它
                // assetCookie-1就是图片所在的资源包路径在native层AssetManager.mAssetPaths数组中的索引
                // 下面这个方法就是打开这个文件了
                final InputStream is = mAssets.openNonAsset(
                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                dr = Drawable.createFromResourceStream(this, value, is, file, null);
                is.close();
            }
        } catch (Exception e) {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
            final NotFoundException rnf = new NotFoundException(
                    "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
            rnf.initCause(e);
            throw rnf;
        }
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

        return dr;
    }

  到这里为止就彻底搞清楚资源的查找与加载过程了:索引+加载+缓存。

  所有分享大纲:2017Android进阶之路与你同行

  视频讲解地址:http://pan.baidu.com/s/1bC3lAQ












android换肤框架搭建及使用(3完结篇)(代码片段)

本系列计划3篇:Android换肤之资源(Resources)加载(一)setContentView()/LayoutInflater源码分析(二)换肤框架搭建(三)—本篇tips:本篇只说实现思路,以及使用,具体细节请下载代码查看!本篇实现效果:fragment换肤recyclerView换肤自定义view属性换肤打... 查看详情

android插件化hook插件化框架(从源码角度分析加载资源流程|hook点选择|资源冲突解决方案)(代码片段)

Android插件化系列文章目录【Android插件化】插件化简介(组件化与插件化)【Android插件化】插件化原理(JVM内存数据|类加载流程)【Android插件化】插件化原理(类加载器)【Android插件化】“插桩式“插件化框架(原理与实现思路)【Android... 查看详情

android皮肤包换肤之resources加载(代码片段)

Android换肤之资源(Resources)加载(一)本系列计划3篇:Android换肤之资源(Resources)加载(一)—本篇setContentView()/LayoutInflater源码分析(二)换肤框架搭建(三)看完本篇你可以学会什么?Resources在什么时候被解析并加载的Application#ResourcesActivity#Res... 查看详情

activity布局流程+资源加载过程+插件化换肤思路(代码片段)

Activity布局流程Activity框架1.Activity里有一个window,在初始化的时候是空的,然后在activity.attach()里赋值为PhoneWindow2.PhoneWindow里有一个DecorView,这个DecorView就是一个FrameLayout,是整个Activity的根布局3.当新建Activity时选择不... 查看详情

03布局原理与xml原理分析二(代码片段)

(1)使用插件化的方案为App换肤(2)不需要重启App就能够换肤(3)市场上所有的APP都可以当成自己的皮肤包来用。(4)无闪烁(5)便于扩展与维护,入侵性很小。(6)只需要... 查看详情

android手写实现插件化换肤框架兼容android10android11(代码片段)

...对代码动态设置颜色、背景的业务场景进行单独处理实现插件化换肤,有以下几个关键问题要处理收集所有需要换肤的view及相关属性统一处理所有Activity的换肤工作ÿ 查看详情

android插件化hook插件化框架(hookactivity启动流程|hook点分析)(代码片段)

Android插件化系列文章目录【Android插件化】插件化简介(组件化与插件化)【Android插件化】插件化原理(JVM内存数据|类加载流程)【Android插件化】插件化原理(类加载器)【Android插件化】“插桩式“插件化框架(原理与实现思路)【Android... 查看详情

android插件化hook插件化框架(加载插件包资源)(代码片段)

Android插件化系列文章目录【Android插件化】插件化简介(组件化与插件化)【Android插件化】插件化原理(JVM内存数据|类加载流程)【Android插件化】插件化原理(类加载器)【Android插件化】“插桩式“插件化框架(原理与实现思路)【Android... 查看详情

ssh框架总结(框架分析+环境搭建+实例源码下载)《转》

这篇文章比较易懂,易理解: 首先,SSH不是一个框架,而是多个框架(struts+spring+hibernate)的集成,是目前较流行的一种Web应用程序开源集成框架,用于构建灵活、易于扩展的多层Web应用程序。 集成SSH框架的系统从职责... 查看详情

android插件化hook插件化框架总结(插件包管理|hookactivity启动流程|hook插件包资源加载)★★★(代码片段)

Android插件化系列文章目录【Android插件化】插件化简介(组件化与插件化)【Android插件化】插件化原理(JVM内存数据|类加载流程)【Android插件化】插件化原理(类加载器)【Android插件化】“插桩式“插件化框架(原理与实现思路)【Android... 查看详情

android手写实现插件化换肤兼容android10android11(代码片段)

...对代码动态设置颜色、背景的业务场景进行单独处理实现插件化换肤,有以下几个关键问题要处理收集所有需要换肤的view及相关属性统一处理所有Activity的换肤工作ÿ 查看详情

干货分享腾讯出品android插件化开发指南+项目实战(附源码)

何为插件化?插件化即将一个完整的工程,按业务划分为不同的插件,都是分治法的一种体现。化整为零,相互配合。越小的模块越容易维护。Android插件化开发和组件化略有不同,插件化开发是将整个app拆分... 查看详情

唯一插件化replugin源码及原理深度剖析--插件的安装加载原理(代码片段)

上一篇唯一插件化Replugin源码及原理深度剖析–唯一Hook点原理在Replugin的初始化过程中,我将他们分成了比较重要3个模块,整体框架的初始化、hook系统ClassLoader、插件的加载,3个模块已经说了两个,在第一篇的最... 查看详情

插件化换肤方案

参考技术A插件化换肤,需要考虑两个核心的问题。第一、如何收集到所有需要换肤的View,因为我们需要在换肤的时机中调用这些View的setBackGround、setColor等方法,所以我们需要再收集到这些需要换肤的属性,一个View可能对应多... 查看详情

glide4.12图片框架之多级缓存源码设计分析(代码片段)

...de缓存初识在上两篇文章中,我们从源码角度分析Glide框架加载图片的流程、以及Glide图片通过巧妙的空view的Fragment的设计实现的Glide的图片加载的三大生命周期函数onStart、onStop、onDestroy。Glide的框架的源码量确实比较大,... 查看详情

glide4.12图片框架之多级缓存源码设计分析(代码片段)

...de缓存初识在上两篇文章中,我们从源码角度分析Glide框架加载图片的流程、以及Glide图片通过巧妙的空view的Fragment的设计实现的Glide的图片加载的三大生命周期函数onStart、onStop、onDestroy。Glide的框架的源码量确实比较大,... 查看详情

最新源码glide4.12框架之加载图片流程源码分析(代码片段)

一、前言Android图片加载框架,在android应用开发中是一个常见的话题。在12、13年的时候我记得可能用的最多的是XUtils的一套框架(更早之前叫aFinal框架),这个框架中提供imageUtils用于在android应用的开发中完成远... 查看详情

glide4.12框架源码中的生命周期设计(代码片段)

在上一篇文章《最新源码Glide4.12框架之加载图片流程源码分析》中,我们主要做了对于通过Glide.with(this).load(url).into(target)的调用图片加载流程的的源码分析,以及对加载流程图的梳理。本篇文章主要对Glide图片框架的生命... 查看详情