Android插件化原理(三)——加载插件资源

Android插件化框架原理与实战指南 插件化架构是现代移动应用开发的核心技术之一,其核心原理基于动态类加载和资源隔离机制。通过自定义ClassLoader突破Java双亲委派模型的限制,配合反射修改AssetManager实现资源动态加载,开发者可以构建高扩展性的应用架构。这种技术在Android平台上尤为重要,能有效解决应用体积膨胀、功能热更新等工程难题。以电商App为例,支付、社交分享等模块通过插件化实现独立部署,大幅提升开发效率和用户体验。当前主流方案如VirtualAPK采用字节码插桩技术,而RePlugin则通过独创的坑位管理机制优化 阅读详情


系列帖子:

Android插件化原理(一)—— 插件类加载、类加载原理、(双亲委托机制)
Android插件化原理(二)—启动插件Activity

应用

单作为一个技术点来讲,其实没有什么意义,还是要围绕需求展开的,加载插件资源最常见的应用就是插件化换肤了。我们直到,apk绝大部分体积都是资源,如果多套主题皮肤,那么包体积就会增加很多,
所以插件化换肤就很实用。可以实现多样化主题,个性化定制,提高了应用的灵活性和扩展性。

应用加载资源的原理

要看加载资源的原理,我们先看一下,在实际运行中如何获取资源的

如何获取资源

Activity

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getResources().getDrawable();
        getDrawable(0);
    }

Context

    @Nullable
    public final Drawable getDrawable(@DrawableRes int id) {
        return getResources().getDrawable(id, getTheme());
    }

实际上,不论是直接调用getDrawable或者Resource().getDrawable(),亦或者getContext().getDrawable()最终调用的都是Re
Resources的方法

Resources


    public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
            throws NotFoundException {
        return getDrawableForDensity(id, 0, theme);
    }

    @Nullable
    public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValueForDensity(id, density, value, true);
            return impl.loadDrawable(this, value, id, density, theme);
        } finally {
            releaseTempTypedValue(value);
        }
    }

    

ResourcesImpl

    void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
            boolean resolveRefs) throws NotFoundException {
        boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
        if (found) {
            return;
        }
        throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
    }

其中,最重要的就是 mAssets.getResourceValue(id, density, outValue, resolveRefs),而mAssets就是android.content.res.AssetManager,

AssetManager


    boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
            boolean resolveRefs) {
        Preconditions.checkNotNull(outValue, "outValue");
        synchronized (this) {
            ensureValidLocked();
            final int cookie = nativeGetResourceValue(
                    mObject, resId, (short) densityDpi, outValue, resolveRefs);
            if (cookie <= 0) {
                return false;
            }

            // Convert the changing configurations flags populated by native code.
            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
                    outValue.changingConfigurations);

            if (outValue.type == TypedValue.TYPE_STRING) {
                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
            }
            return true;
        }
    }

    private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
                                                     @NonNull TypedValue outValue, boolean resolveReferences);

最终呢,调用到了nativeGetResourceValue。

总结:查找资源从Context开始到Context内部的Resources,再到Resources的成员ResourcesImpl,再到ResourcesImpl的成员AssetManager,最终呢,到大native层,到这一步,我们可以说资源实际上是由AssetManager管理的。
那么,AssetManager又是如何来的呢?

继续往下看应用资源加载原理

android资源加载原理

我们从Activity的创建开始,一步步看他的资源是如何加载出来的

ActivityThread.handleLaunchActivity

    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {

        //...

        final Activity a = performLaunchActivity(r, customIntent);
        //...

    }


    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //...    
        ContextImpl appContext = createBaseContextForActivity(r);
        //...
    }

    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        final int displayId;
        try {
            displayId = ActivityManager.getService().getActivityDisplayId(r.token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
    
       //...
        return appContext;
    }   
    

上述代码是Activity启动时,为Activity创建了ContextImpl,下面是如何创建的

ContextImpl

    static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");

        String[] splitDirs = packageInfo.getSplitResDirs();
        //获取ClassLoader
        ClassLoader classLoader = packageInfo.getClassLoader();

        if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
            try {
                classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
                splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
            } catch (NameNotFoundException e) {
                // Nothing above us can handle a NameNotFoundException, better crash.
                throw new RuntimeException(e);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
            }
        }
        //创建实例
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
                activityToken, null, 0, classLoader);

        // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
        displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;

        final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
                ? packageInfo.getCompatibilityInfo()
                : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;

        final ResourcesManager resourcesManager = ResourcesManager.getInstance();

        // Create the base resources for which all configuration contexts for this Activity
        // will be rebased upon.
        //注入资源
        context.setResources(resourcesManager.createBaseActivityResources(activityToken,
                packageInfo.getResDir(),
                splitDirs,
                packageInfo.getOverlayDirs(),
                packageInfo.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfiguration,
                compatInfo,
                classLoader));
        context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
                context.getResources());
        return context;
    }

这里主要有3步骤:

  • 找到当前的ClassLoader
  • 构造ContextImpl实例
  • 通过ResourcesManager获取资源Resources,然后注入ContextImpl

那么ResourcesManager的Resources从何而来呢,我们继续往下看

ResourcesManager

    public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
            @Nullable String resDir,
            @Nullable String[] splitResDirs,
            @Nullable String[] overlayDirs,
            @Nullable String[] libDirs,
            int displayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader) {
        try {
            
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    overlayDirs,
                    libDirs,
                    displayId,
                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                    compatInfo);
     
            synchronized (this) {
                // Force the creation of an ActivityResourcesStruct.
                getOrCreateActivityResourcesStructLocked(activityToken);
            }

            // Update any existing Activity Resources references.
            updateResourcesForActivity(activityToken, overrideConfig, displayId,
                    false /* movedToDifferentDisplay */);

            // Now request an actual Resources object.
            return getOrCreateResources(activityToken, key, classLoader);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
                                                 @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
    //......

    // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
    ResourcesImpl resourcesImpl = createResourcesImpl(key);
    if (resourcesImpl == null) {
        return null;
    }

    // Add this ResourcesImpl to the cache.
    mResourceImpls.put(key, new WeakReference<>(resourcesImpl));

    final Resources resources;
    if (activityToken != null) {
        resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                resourcesImpl, key.mCompatInfo);
    } else {
        resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
    }
    return resources; 
   }
                                                 

这里省略大量代码,主要看createResourcesImpl:


    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);

        final AssetManager assets = createAssetManager(key);
        if (assets == null) {
            return null;
        }

        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);

        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

往下看AssetManager

    protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        final AssetManager.Builder builder = new AssetManager.Builder();

        // 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.
        if (key.mResDir != null) {
            try {
                builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
                        false /*overlay*/));
            } catch (IOException e) {
                Log.e(TAG, "failed to add asset path " + key.mResDir);
                return null;
            }
        }
        //省略类似代码

        return builder.build();
    }

这里我们先看loadApkAssets


    private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
            throws IOException {
        final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
        ApkAssets apkAssets = mLoadedApkAssets.get(newKey);
        if (apkAssets != null) {
            return apkAssets;
        }

        // Optimistically check if this ApkAssets exists somewhere else.
        //先看是否已经加载过了
        final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
        if (apkAssetsRef != null) {
            apkAssets = apkAssetsRef.get();
            if (apkAssets != null) {
                mLoadedApkAssets.put(newKey, apkAssets);
                return apkAssets;
            } else {
                // Clean up the reference.
                mCachedApkAssets.remove(newKey);
            }
        }
        //如果上面检查缓存没有,就直接按照path加载

        // We must load this from disk.
        if (overlay) {
            apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path),
                    false /*system*/);
        } else {
            apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
        }
        mLoadedApkAssets.put(newKey, apkAssets);
        mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
        return apkAssets;
    }

这段代码,首先会从缓存里面检查是否已经加载过了,如果没有,就按照path加载,核心代码就是apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);后面的代码到Native了,没有必要往下看了。

再回到 builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/));

    public static class Builder {
        private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();

        public Builder addApkAssets(ApkAssets apkAssets) {
            mUserApkAssets.add(apkAssets);
            return this;
        }

        public AssetManager build() {
            // Retrieving the system ApkAssets forces their creation as well.
            final ApkAssets[] systemApkAssets = getSystem().getApkAssets();

            final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size();
            final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];

            System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);

            final int userApkAssetCount = mUserApkAssets.size();
            for (int i = 0; i < userApkAssetCount; i++) {
                apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
            }

            // Calling this constructor prevents creation of system ApkAssets, which we took care
            // of in this Builder.
            final AssetManager assetManager = new AssetManager(false /*sentinel*/);
            assetManager.mApkAssets = apkAssets;
            AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
                    false /*invalidateCaches*/);
            return assetManager;
        }
    }

ok,我们梳理一下流程:

![流程图][1]

资源最终是在AssetManager被加载进来的,而内部是通过ApkAssets.loadFromPath跟局path加载为ApkAssets,然后添加到AssetManager,AssetManager在ResourcesImpl中,ResourcesImpl是Resources的成员…


ok,了解了系统是如何为Activity(Context)加载资源的,我们接下来开始正题——加载插件资源

加载插件资源

在上一节我们了解了系统加载资源的方法,核心就是

  • 1.调用ApkAssets.loadFromPath加载ApkAssets
  • 2.将ApkAssets添加到AssetManager.Builder内
  • 3.调Build.build得到AssetManager

但实际上不论AssetManager还是AssetManager.Builder都对开发者不可见的,我们有更简单的办法,查看AssetManager源码发现一个方法:

    public int addAssetPath(String path) {
        return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
    }

    private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
        Preconditions.checkNotNull(path, "path");
        synchronized (this) {
            ensureOpenLocked();
            final int count = mApkAssets.length;
    
            // See if we already have it loaded.
            for (int i = 0; i < count; i++) {
                if (mApkAssets[i].getAssetPath().equals(path)) {
                    return i + 1;
                }
            }
    
            final ApkAssets assets;
            try {
                if (overlay) {
                    // TODO(b/70343104): This hardcoded path will be removed once
                    // addAssetPathInternal is deleted.
                    final String idmapPath = "/data/resource-cache/"
                            + path.substring(1).replace('/', '@')
                            + "@idmap";
                    assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/);
                } else {
                    assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib);
                }
            } catch (IOException e) {
                return 0;
            }
    
            mApkAssets = Arrays.copyOf(mApkAssets, count + 1);
            mApkAssets[count] = assets;
            nativeSetApkAssets(mObject, mApkAssets, true);
            invalidateCachesLocked(-1);
            return count + 1;
        }
}

看addAssetPathInternal是不是感觉很熟悉,其实我们只需要反射构造AssetManager实例,然后反射addAssetPath即可。

加载插件资源的核心代码:


    /**
     * 加载插件的Resources
     *
     * @param context
     * @return
     */
    public static Resources loadResources(Context context, String pluginAbsolutePath) {
        try {
            //创建加载插件的AssetManager
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
            //加载插件的资源
            addAssetPathMethod.invoke(assetManager, pluginAbsolutePath);
            //获取宿主的资源,获取其资源configuration等信息
            Resources resources = context.getResources();
            return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());

        } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
            Log.e(TAG, "loadResources: ", e);
        }

        return null;
    }

这段代码是不是很直观

  • 通过反射获取AssetManager实例
  • 反射调用addAssetPath方法把插件apk的资源加载到其实例
  • 根据宿主app的屏幕配置、主题配置等信息构造属于插件的Resources实例

是不是很简单,就是这么简单。

关于插件化换肤的一些看法

我看有的人会把插件的资源加载进来后像是我们加载插件Activity一样,合并到宿主中,但是我不建议这么做,主要问题如下:

  • 一来是还需要进行一系列的反射(尽管上面我特意说到资源相关类的从属关系,这就是反射的关键)
  • 二来还会碰到资源冲突的问题,一通操作下来我觉得浪费时间得不偿失
  • 另外我觉得不如将插件资源包装到个人的资源管理类,全局用资源管理类加载资源,而不是用默认的context加载,这样配合换肤管理类使用更加灵活方便。

好了,插件化这一系列终于完成了,🤦‍♂️🤦‍♂️🤦‍♂️

附:Github所有代码

Android插件化完美实现代码资源加载及原理讲解 附可运行demo *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 。 我们通过前4篇的分解,分别将插件化设计到的知识点全部梳理了一遍,如果没有看过的,建议先看前面4篇 1. Binder机制 2. 插件化知识详细分解及原理 之代理,hook,反射, 3. 类加载及dex加载 4. 应用启动过程及类加载过程 5. Android插件化完美实现代码资源加载及原理讲解 附可运行demo 阅读详情

相关推荐

Android插件化原理与方案详解

Android插件化是一种开发模式,它允许我们动态地加载和卸载APK,从而实现模块化开发,热更新等功能。本文将详细介绍Android插件化的原理,以及几种主流的Android插件化方案。

陆业聪 6716

Android插件化原理(一)—— 插件类加载、类加载原理、(双亲委托机制)

Acitivity启动原理(一),pauseActivity原理

blankmargin的博客 2402

Android插件化资源的使用及动态加载 附demo

上一篇我们已经完成了一个真正可运行的插件化demo,而且demo中也解决了插件中不可以使用资源的问题,但是由于篇幅的问题我们并没有对原理讲解,所以这一篇是对上一篇的一个收尾,如果没有看过上一篇建议先看Android插件化完美实现代码资源加载及原理讲解 附可运行demo.demo地址 : https://github.com/ljqloveyou123/LiujiaqiAndroid我们的宿主应用调用

yulong0809的专栏 7566

Android开发:深入源码剖析图片加载过程,面试官再也不能为难我了!

*** 关注2:getValue()*/// 查找资源Id & 相关信息存储在 outValue -> 关注3/*** 关注3:AssetManager.getResourceValue()*/// 从Native文件中查找 -> 关注4/*** 关注4:AssetManager.NativeGetResourceValue()*/clazz// ->关注5/*** 关注5:AssetManager.CopyValue()*/if (config!

2401_84010744的博客 688

android插件化

概述 插件化是一种动态升级app功能的解决方案,不同于热修复(仅仅是修复功能),类似于RN、Weex(目的类似)。都是为了在不发版本的情况下,可以让用户用上最新的功能。不过RN、Weex还额外支持跨平台。相对于RN和Weex,插件化有以下的优缺点: 优点: 对于业务方,无额外的学习成本,基本无感知 性能等同于原生、可以做任何原生可以做的事情 天然代码隔离、使得插件化的代码更加的“高内聚、低耦合” 插件并发开发,开发之间互不影响 缺点: 稳定性差,使用了大量大反射...

long8313002的博客 4985

android插件化原理

最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接。随着公司业务快速发展,项目增多,开发资源却有限,如何能在有限资源内满足需求和项目的增长,同时又能快速响应问题和迭代新需求,这就是一个矛盾点。此时,插件化技术正好风生水起,去了解各个主流框架实现思路,看看能对目前工作是否有帮助,是很有必要的。 主要分为以下几个部分 ...

pgg_cold的博客 1175

Android 资源文件匹配规则

Android 资源文件匹配规则 Android 资源文件,可以加各种限定标注,类似我们常用的layout-hdpi ,layout-xhpdi,value-zh-rCN,layout-400x800等等。官方支持的请参考: 应用资源概览 那么Android是怎么去判断使用哪个一个呢? 资源查找 我们知道,不管定义多少个限制符的资源文件,ID只有一个,所以到底取哪个文件夹下面的呢? 以Resource#getDrawable开启。主要流程是:Resource#getDrawable-》Resource#ge

qunimaode的博客 878

Service插件化实战:从类加载到生命周期注入的完整方案

在Android组件化与动态化架构中,插件化技术常被用于模块解耦与功能热更新。相比Activity插件化,Service组件因完全依赖系统调度,其插件化实现需要更底层的能力补偿,核心难点在于绕过AMS校验后,仍要模拟完整的生命周期管理。原理上主要涉及三层机制:通过DexClassLoader解决插件类的加载与隔离,借助ProxyService作为占坑代理接收系统回调,再通过反射注入Context并同步系统缓存,让插件实例获得真实存活能力。该技术能显著提升宿主App的扩展性与迭代效率,广泛应用于应用商店、广告

weixin_34194829的博客 202

插件化之APK动态加载

插件化相关概念: 根据组件化与插件化的区别来了解一下概念 组件化和插件化的区别 组件化:是将一个APP分成多个模块,每个模块都是一个组件(module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件,但是最终发布的时候将这些组件合并成一个统一的APK。 插件化:是将整个APP拆分成很多模块,每个模块都是一个APK(组件化的每个模块是一个lib),最终打包的时候将宿主APK和插件APK分开打包,插件APK通过动态下发到宿主APK。 插件化的优点 减小安装APK的体积,按需下载模块

u013773608的博客 2050

Android插件化之资源动态加载

Android插件化之资源动态加载 一.概述 Android插件化的一个重要问题就是插件资源访问问题,先列出会面对的问题 1.如何加载插件资源 2.如何处理插件资源与宿主资源的处突:插件化资源问题要做到的效果是,如果我们要获取的资源在插件中找得到,则加载优先加载插件的,如果找不到,则到宿主资源中找。这样能做到动态更新的效果。 3.如何确保插件和宿主使用到的是被修改过的资源。 二.原理分析 在做一件事之前必须先弄清楚原理,所以,这里先要弄清楚Android的资源体系原理。 1.资源链   Context:一个apk里面其context的个数为application+Activit

手把手用ROS实现Ego-Planner动态避障:无人机撞树问题终结方案

本文详细介绍了如何在ROS和Gazebo仿真环境中,通过配置和调优Ego-Planner算法,实现无人机的动态避障功能。文章深入解析了其基于局部梯度优化的核心思想,提供了从环境搭建、参数详解到代码集成的完整实践指南,并分享了实战排错经验,旨在彻底解决无人机在复杂动态场景中的碰撞问题。

weixin_29031161的博客 1630

Android进阶宝典 -- 插件化1(加载插件中类)

什么是插件化?插件化对于Android应用能起到什么好处?可能对于插件化不熟悉的伙伴们都会有这个疑问,或许你在项目中已经遇到过这个问题,只不过是不知道需要采用什么样的方式去解决,我们看下面这个场景。

m0_64420071的博客 903

存储关键技术·RAID技术·RAID2.0·华为RAID2.0+·华为服务器·云计算

一.RAID基本概念 常见的RAID级别组合RAID级别 二.RAID的数据组织形式 三.RAID的数据保护功能 方法一:镜像方法二:奇偶校验算法(XOR) 四.RAID热备和重构 1.热备(Hot Spare)的定义2.热备的分类3.热备的工作原理 五.RAID 2.0技术 1.RAID 2.02.RAID 2.0+3.华为RAID 2.0+ 六.RAID 2.0+块虚拟化技术 1.什么会出现RAID 2.0+技术?2.什么是RAID 2.0?3.RAID 2.0+的原理是什么?

qq_47480932的博客 3320

Android插件化主流框架和实现原理

写在前面; 这几年移动开发业界兴起的「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接。随着公司业务快速发展,项目增多,开发资源却有限,如何能在有限资源内满足需求和项目的增长,同时又能快速响应问题和迭代新需求,这就是一个矛盾点。此时,插件化技术正好风生水起,去了解各个主流框架实现思路,看看能对目前工作是否有帮助,是很有必要的。 主要分为以...

chuhe1989的博客 1万+

ADS1220 STM32硬件SPI驱动程序

前一段时间温故了下标准库,使用的是F1系列芯片,测试程序。

随风飘零翼的博客 5767

【Android 插件化】插件化原理 ( 类加载器 )

一、" 插件化 " 中的 dex 文件、 二、类加载器分析、 三、获取类加载器、 四、双亲委派机制、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 3477

高性能网站设计之缓存更新的套路

看到好些人在写更新缓存数据代码时,先删除缓存,然后再更新数据库,而后续的操作会把数据再装载的缓存中。然而,这个是逻辑是错误的。试想,两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。 我不知道为什么这么多人用的都是这

代码技巧 1万+

【Android】 插件化原理

也叫动态加载技术,应用在不发布新版本的情况下更新,增加或者移除某些功能模块的技术;对Android来说插件化通常是指将App拆分成多个功能模块,包括一个宿主和多个插件,宿主的形式是APK,插件的形式可以是APK(未安装), zip,Jar, dex;;区别于组件化:组件化是将一个App分成多个模块,每个模块都是一个组件(Module),开发的过程中让这些组件相互依赖,并且可以单独开发和调试单个组件,但最终发布的是所有模块合并打包的APK。

1614

AWS DevOps Agent 完全指南

AI 驱动的 SRE 队友,7×24 自主调查告警、定位根因、生成修复方案、预防未来故障。

探索云原生与智能化驱动下的安全运维新范式。关注DevSecOps、可观测性、AIOps等前沿领域,与您共赴技术前沿。 381

5G无线关键技术 — 新型传输波形技术和先进编码与调制技术

目录 1、OFDM传输波形技术 2、滤波器组多载波 (Filterbank multicarrier:FBMC) 3、先进编码与调制技术 4、空间调制(Spatial Modulation SM) 5、频率正交幅度调制 1、OFDM传输波形技术 OFDM是当前Wi-Fi和LTE标准中的高速无线通信的主要传信模式。 LTECP配置 子载波间隔 CP长度Tcp 有用符号长度Tu CP比例..

Fanjufei的博客 2128

安卓漏洞学习(八):Android 插件化技术

android插件的基本原理和实现方法

beefreesky的博客 1116

PHPStorm携手ThinkPHP8:开启高效开发之旅

本文围绕使用 PHPStorm 进行 ThinkPHP8 开发展开,先介绍开发环境搭建与 Xdebug 配置,再阐述 PHPStorm 集成 ThinkPHP8 的步骤,包括项目导入、解释器和服务器配置。还讲解了项目开发基础、数据库操作、高级技巧、调试处理及部署上线等内容,为开发者提供了从环境搭建到项目上线的完整指引,助力高效开展 ThinkPHP8 开发工作。

邓邓子的博客 1544

安卓插件化系列课程-一步步带你实现插件化

前言: 目前安卓领域,插件化十分的流行,本以为这一类的文章会有很多,但是百度一搜,基本上讲的插件化都是皮毛,没有涉及到核心。 所以就想写一系列的文章来一步一步深入的讲解插件化,通过实现逐渐增加难度的需求,最终实现插件化方案。 章节: 插件化系列文章主要包含以下几个篇章: 1.加载插件apk中的类 2.启动插件apk中的activity 3.使用插桩的方式启动apk中的activity 4.使用插件apk中的资源 5.使用插件apk中的so文件 6.插件化加载移动apk。 7.插件化目前所遇到的

分享+记录 2543

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

【Android 插件化】Hook 插件化框架 ( 加载插件包资源 )

Android 插件化系列文章目录、 前言、 一、加载插件包资源文件、 1、反射 AssetManager 类并创建实例对象、 2、反射 AssetManager 的 addAssetPath 方法并调用、 3、创建 Resources、 二、自定义 Application 中设置插件资源、 三、插件 Activity 组件中设置插件资源、 四、博客资源、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 1928

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

上一篇: Gradle系列(2)——如何自定以Gradle 插件
下一篇: Gradle系列(3)——Gradle extension(gradle扩展),如何自定义gradle扩展,AppPlugin,AppExtension原理
cry kid
博客等级 码龄10年 144粉丝 24原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值