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

7系列ZYNQ硬件调试:Vitis无法识别ARM DAP的排查与解决

本文针对7系列ZYNQ开发中Vitis无法识别ARM DAP的典型硬件调试问题,提供了系统性的排查与解决方案。文章深入剖析了问题本质,指出ARM DAP无法访问的核心原因在于PS部分的电源、时钟或复位等硬件基础条件未满足,并重点强调了PS参考时钟(如33.333MHz晶振)的完整性是关键排查点。通过从电源完整性、时钟与复位信号到JTAG连接与焊接的逐步硬件排查,并结合软件配置交叉验证,为开发者提供了清晰的故障定位思路与实战指南。

weixin_29280657的博客 626

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

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

m0_64420071的博客 903

MLNX_OFED 5.4-3.7.5.0 驱动安装:银河麒麟V10SP2 ARM 内核升级后编译避坑 3 步

本文详细介绍了在银河麒麟V10SP2 ARM环境下安装MLNX_OFED 5.4-3.7.5.0驱动的完整流程,包括内核升级后的驱动兼容性挑战、编译环境准备、分步编译与安装流程以及性能优化与故障排查。特别针对国产化ARM服务器(如华为Atlas系列)提供了实用的解决方案和常见问题处理建议,帮助用户顺利完成mlnx网卡驱动的安装与配置。

weixin_29184075的博客 403

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

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

chuhe1989的博客 1万+

手把手教你用Node.js模拟大麦APP下单请求(含滑块验证处理)

本文详细解析了如何使用Node.js模拟大麦APP下单请求的全流程,包括接口逆向、滑块验证处理和签名算法逆向等关键技术。通过实战案例和代码示例,帮助开发者掌握移动端API调用的核心要素,如动态参数构造、安全验证突破等,提升自动化下单的成功率和效率。

weixin_42569329的博客 353

大智慧L2实时api接口的逐笔委托功能执行代码分享

用过l2行情接口的投资者应该都了解大部分的接口都是有逐笔委托功能的,那么大智慧L2实时api接口也有着很强大的逐笔委托功能,投资者可以通过这个逐笔委托功能可以看到一个股票的每天出现的全部委托指令,那么在这个逐笔委托功能里包含了委托价格、委托时间、委托量和委托方向四个要素!那么接下来小编就分享一下大智慧L2实时api接口的逐笔委托功能执行代码!那么在这里提个醒:投资者可以在大智慧L2实时api接口进行设置过滤,在逐笔委托列表里,就仅仅只会显示超过设置委托量或委托额的委托单,而不再显示小单。

Qq_2037696191的博客 3732

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

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

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

010Editor实战:5分钟破解Zip伪加密文件(附BUUCTF真题解析)

本文详细介绍了如何使用010Editor快速破解Zip伪加密文件,包括BUUCTF真题的完整解析。通过修改关键字节,轻松解除伪加密状态,适用于CTF竞赛和实际工作场景,提升文件处理效率。

weixin_29325007的博客 326

【Android】 插件化原理

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

1614

【运动规划算法项目实战】如何使用Pure Pursuit算法进行路径跟踪(附ROS C++代码)

在自动驾驶和机器人导航领域,路径跟踪是一项关键技术,它使车辆或机器人能够按照预定的路径规划进行移动。路径跟踪算法的选择对于系统性能和安全性至关重要。本文将介绍一种常用且有效的路径跟踪算法——Pure Pursuit算法。我们将详细讨论Pure Pursuit算法的原理、实现步骤以及如何在自动驾驶和机器人系统中应用该算法进行路径跟踪。Pure Pursuit是一种用于路径跟踪的控制算法。它通过计算角速度控制机器人从当前位置移到机器人前方的某个预瞄点。假定线速度是恒定的,当然可以随意更改机器人的线速度。

xiewf的博客 1万+

python实现马丁策略(股市) (附完整源码)

python实现马丁策略(股市) (附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 603

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

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

beefreesky的博客 1116

uniapp - 超详细 H5 网页引入百度地图教程,附地图示例 / 获取当前设备定位的省市区、详细地址、经纬度信息 / 用户当前 IP 属地定位地理位置等等(新手小白快速上手,详细配置及使用全流程)

uniapp百度地图,uniapp百度地图ip属地定位,uniapp百度地图获取省市区详细地址经纬度等,uniapp百度地图当前用户设备定位省份市区详细所在地ip属地等详细教程示例,uniapp网页h5引入百度地图详细教程,uniapp h5网站使用百度地图详细教程,uniappH5端如何接入使用百度地图api,uniapp H5网页端百度地图获取地理位置信息,uniapp h5页面引入百度地图,uniapp h5平台百度地图获取城市信息,uniapp百度地图获取经纬度,uniapp 百度地图拿省市区用什么

🏆 前端领域优质创作者 6217

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

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

分享+记录 2543

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

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

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

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

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

上一篇: 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、付费专栏及课程。

余额充值