Android笔记(二十五):两种sdk热更插件资源加载方案

news2024/11/27 2:20:16

背景

在研究sdk插件化热更新方式的过程中总结出了两套插件资源加载方案,在此记录下

资源热更方式

方式一:合并所有插件资源

需要解决资源id冲突问题

在这里插入图片描述

资源ID值一共4个字段,由三部分组成:PackageId+TypeId+EntryId

  • PackageId:是包的Id值,Android 中如果第三方应用的话,这个默认值是 0x7f,系统应用的话就是 0x01 ,插件的话那么就是给插件分配的id值,占用1个字节。
  • TypeId:是资源的类型Id值,一般 Android 中有这几个类型:attr,drawable,layout,anim,raw,dimen,string,bool,style,integer,array,color,id,menu 等。【应用程序所有模块中的资源类型名称,按照字母排序之后。值是从1开支逐渐递增的,而且顺序不能改变(每个模块下的R文件的相同资源类型id值相同)。比如:anim=0x01占用1个字节,那么在这个编译出的所有R文件中anim 的值都是 0x01】
  • EntryId:是在具体的类型下资源实例的id值,从0开始,依次递增,他占用2个字节。

两种解决方式

  1. gradle3.5以上可以动态配置aapt的package-id参数修改插件apk的packageId,可在插件模块的build.gradle配置如下:
android {
    compileSdkVersion 32

    defaultConfig {
        applicationId "xxx"
        minSdkVersion 21
        targetSdkVersion 32
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    //为模块定一个唯一的package-id,如0x80
    aaptOptions {
        additionalParameters '--allow-reserved-package-id','--package-id', '0x80'
    }
}
  1. gradle3.5以下,可以将打包出来的插件apk包进行解包,修改public.xml文件内资源id的package-id,再扫描每一个R$xxx.smali文件,纠正代码中R类的值,与public.xml中的对应,重新打包。

反射替换Application中的mResources字段

注意:在启动service和receiver的时候,会使用application的resource

fun mergePatchResources(application: Application, apkPaths : List<String>) {
	val newAssetManagerObj = AssetManager::class.java.newInstance()
	//todo 需要注意这里的addAssetPath被标志为废弃了
	val addAssetPath = AssetManager::class.java.getMethod("addAssetPath", String::class.java)
	// 插入宿主的资源
	addAssetPath.invoke(newAssetManagerObj, application.baseContext.packageResourcePath)
	// 插入插件的资源
	apkPaths.forEach {
		addAssetPath.invoke(newAssetManagerObj, it)
	}

	val newResourcesObj = Resources(
		newAssetManagerObj,
		application.baseContext.resources.displayMetrics,
		application.baseContext.resources.configuration
	)
	newHoldResources = newResourcesObj

	val resourcesField = application.baseContext.javaClass.getDeclaredField("mResources")
	resourcesField.isAccessible = true
    resourcesField[application.baseContext] = newResourcesObj

	val packageInfoField = application.baseContext.javaClass.getDeclaredField("mPackageInfo")
	packageInfoField.isAccessible = true
	val packageInfoObj = packageInfoField[application.baseContext]

	// 获取 mPackageInfo 变量对象中类的Resources类型的mResources 变量,并替换它的值为新的Resources对象
	// 注意:这是最主要的需要替换的,如果不需要支持插件运行时更新,只留这一个就可以了
	val resourcesField2 = packageInfoObj.javaClass.getDeclaredField("mResources")
	resourcesField2.isAccessible = true
	resourcesField2[packageInfoObj] = newResourcesObj

	// 获取 ContextImpl 中的 Resources.Theme 类型的 mTheme 变量,并至空它
	// 注意:清理mTheme对象,否则通过inflate方式加载资源会报错, 如果是activity动态加载插件,则需要把activity的mTheme对象也设置为null
	val themeField = application.baseContext.javaClass.getDeclaredField("mTheme")
	themeField.isAccessible = true
	themeField[application.baseContext] = null
}

更新Activity的资源

  1. 监听activity的onCreate回调
((Application)context).registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
                Log.d("zbm111", "doMonitorActivity---onCreate");
                Resources resources = "含宿主和所有插件资源的完整resource对象"
             			ResHookUtil.monkeyPatchExistingResources(activity, resources);
            }

            @Override
            public void onActivityStarted(@NonNull Activity activity) {

            }

            @Override
            public void onActivityResumed(@NonNull Activity activity) {

            }

            @Override
            public void onActivityPaused(@NonNull Activity activity) {

            }

            @Override
            public void onActivityStopped(@NonNull Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(@NonNull Activity activity) {

            }
        });
  1. 将要启动的activity反射替换mResources字段
public static void monkeyPatchExistingResources(Activity activity, Resources newResources) {
        try {
            Class<?> contextThemeWrapperClass = null;
            try {
                contextThemeWrapperClass = Class.forName("android.view.ContextThemeWrapper");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

            // 反射获取 ContextThemeWrapper 类的 mResources 字段
            Field mResourcesField = null;
            try {
                mResourcesField = contextThemeWrapperClass.getDeclaredField("mResources");
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
            // 设置字段可见性
            mResourcesField.setAccessible(true);

            // 将插件资源设置到插件 Activity 中
            try {
                mResourcesField.set(activity, newResources);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            Method mTheme = contextThemeWrapperClass.getDeclaredMethod("setTheme", Resources.Theme.class);
            mTheme.setAccessible(true);
            mTheme.invoke(activity, (Object) null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

方式二:封装含插件资源的Resource对象

注意:插件apk的包名须与宿主的包名保持一致

需要解决资源id冲突问题

参考方式一,可以通过修改type id与宿主区分开

hook ActivityThread的handler设置callback

  1. 给ActivityThread中mH(Hander类型)对象的mCallback字段设置一个代理对象ProxyHandlerCallback
    public static void doHandlerHook(Context context) {
        try {
            HookUtils.context = context;
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
            Object activityThread = currentActivityThread.invoke(null);

            Field mHField = activityThreadClass.getDeclaredField("mH");
            mHField.setAccessible(true);
            Handler mH = (Handler) mHField.get(activityThread);

            Field mCallbackField = Handler.class.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);
            mCallbackField.set(mH, new ProxyHandlerCallback(mH));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  1. 系统启动service前,进行拦截,将ActivityThread的mPackages新增一个service的LoadedApk,该LoadedApk的mResources字段替换为含插件+宿主资源的SingleMixRes对象;
    系统启动receiver前,进行拦截,将application的mResources字段替换为含插件+宿主资源的SingleMixRes对象。
public class ProxyHandlerCallback implements Handler.Callback {
    private Handler mBaseHandler;

    private Map<String, String> mPathToPluginNameMap = new HashMap<>();

    public ProxyHandlerCallback(Handler mBaseHandler) {
        this.mBaseHandler = mBaseHandler;
    }

    @Override
    public boolean handleMessage(Message msg) {
        Log.d("zbm111", "接受到消息了msg:" + msg);
        if (msg.what == 113){
        	//启动receiver的时候走这里
            try {
                Object object = msg.obj;
                Field infoField = object.getClass().getDeclaredField("info");
                infoField.setAccessible(true);
                ActivityInfo activityInfo = (ActivityInfo) infoField.get(object);
                String hostReceiverName = activityInfo.name;
				Resources resources = "含插件+宿主资源的SingleMixRes对象";

                Field resourcesField = ((Application)HookUtils.getContext()).getBaseContext().getClass().getDeclaredField("mResources");
                resourcesField.setAccessible(true);
                resourcesField.set(((Application)HookUtils.getContext()).getBaseContext(), resources);
            }catch (Exception e){
                Log.e("zbm111", "handle create receiver failed");
            }
        } else if (msg.what == 114) {
        	//启动service的时候走这里
            try {
                Object object = msg.obj;
                Field infoField = object.getClass().getDeclaredField("info");
                infoField.setAccessible(true);
                ServiceInfo serviceInfo = (ServiceInfo) infoField.get(object);
                String hostServiceName = serviceInfo.name;
                String path = "插件apk本地存储路径"
                //todo dex热修复必须同时进行资源热更
                if (path != null) {
                    replaceLoadApk(hostServiceName, path, false);
                    Log.i("zbm111", "replaced to plugin service success");
                }

            } catch (Exception e) {
                Log.e("zbm111", "handle create service failed");
            }
        }

        mBaseHandler.handleMessage(msg);
        return true;
    }

    private void replaceLoadApk(String componentName, String path, boolean isUseActivity) throws Exception {
        Log.i("zbm111", "start replaceLoadApk");
        Field activityThreadField = Class.forName("android.app.ActivityThread").getDeclaredField("sCurrentActivityThread");
        activityThreadField.setAccessible(true);
        Object sCurrentActivityThread = activityThreadField.get(null);
        Field mPackagesField = sCurrentActivityThread.getClass().getDeclaredField("mPackages");
        mPackagesField.setAccessible(true);
        ArrayMap mPackages = (ArrayMap) mPackagesField.get(sCurrentActivityThread);
        if (null == mPackages) {
            Log.i("zbm111", "can not get mPackages");
            return;
        }

        ApplicationInfo applicationInfo = generateApplicationInfo(path);

        if (null != applicationInfo) {
            Field compatibilityInfoField = Class.forName("android.content.res.CompatibilityInfo").getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
            compatibilityInfoField.setAccessible(true);
            Object defaultCompatibilityInfo = compatibilityInfoField.get(null);
            Object loadedApk;
            if (isUseActivity){
                Method getPackageInfoMethod = sCurrentActivityThread.getClass().getDeclaredMethod("getPackageInfo", ApplicationInfo.class, Class.forName("android.content.res.CompatibilityInfo"), int.class);
                getPackageInfoMethod.setAccessible(true);
                loadedApk = getPackageInfoMethod.invoke(sCurrentActivityThread, applicationInfo, defaultCompatibilityInfo, Context.CONTEXT_INCLUDE_CODE);
            }else {
                Method getPackageInfoMethod = sCurrentActivityThread.getClass().getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, Class.forName("android.content.res.CompatibilityInfo"));
                getPackageInfoMethod.setAccessible(true);
                loadedApk = getPackageInfoMethod.invoke(sCurrentActivityThread, applicationInfo, defaultCompatibilityInfo);
            }

            String pluginName = applicationInfo.packageName;

            if (!TextUtils.isEmpty(pluginName)) {
                Log.i("zbm111", "plugin pkg name is " + pluginName);
                Resources resources = "含插件+宿主资源的SingleMixRes对象";
                setResource(loadedApk, resources);
                mPackages.put(pluginName, new WeakReference<>(loadedApk));
                mPackagesField.set(sCurrentActivityThread, mPackages);
                mPathToPluginNameMap.put(path, pluginName);
            } else {
                Log.i("zbm111", "get plugin pkg name failed");
            }
        } else {
            Log.i("zbm111", "can not get application info");
        }
    }

    private void setResource(Object loadedApk, Resources resources) throws Exception{
        Field mResourcesField = loadedApk.getClass().getDeclaredField("mResources");
        mResourcesField.setAccessible(true);
        mResourcesField.set(loadedApk, resources);
    }

    public ApplicationInfo generateApplicationInfo(String pluginPath) {
        try {
            ApplicationInfo applicationInfo = getApplicationInfoByPackageArchiveInfo(pluginPath);
            if (null == applicationInfo) {
                LogUtil.i("zbm111", "get applicationInfo failed");
                return null;
            }
            applicationInfo.sourceDir = pluginPath;
            applicationInfo.publicSourceDir = pluginPath;
            return applicationInfo;
        } catch (Exception e) {
            LogUtil.i("zbm111", "generateApplicationzInfo failed " + e.getMessage());
        }
        return null;
    }

    private ApplicationInfo getApplicationInfoByPackageArchiveInfo(String pluginPath) {
        PackageManager packageManager = HookUtils.getContext().getPackageManager();
        if (null == packageManager) {
            LogUtil.i("zbm111", "get PackageManager failed");
            return null;
        }
        PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginPath, 0);
        if (null == packageInfo) {
            LogUtil.i("zbm111", "get packageInfo failed");
            return null;
        }
        return packageInfo.applicationInfo;
    }
}
  1. 自定义ResourcesWrapper
public class ResourcesWrapper extends Resources {

    private Resources mBase;

    public ResourcesWrapper(Resources base){
        super(base.getAssets(),base.getDisplayMetrics(),base.getConfiguration());
        mBase = base;
    }

    @Override
    public CharSequence getText(int id) throws NotFoundException {
        return mBase.getText(id);
    }

    @TargetApi(Build.VERSION_CODES.O)
    @Override
    public Typeface getFont(int id) throws NotFoundException {
        return mBase.getFont(id);
    }

    @Override
    public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
        return mBase.getQuantityText(id, quantity);
    }

    @Override
    public String getString(int id) throws NotFoundException {
        return mBase.getString(id);
    }

    @Override
    public String getString(int id, Object... formatArgs) throws NotFoundException {
        return mBase.getString(id, formatArgs);
    }

    @Override
    public String getQuantityString(int id, int quantity, Object... formatArgs) throws NotFoundException {
        return mBase.getQuantityString(id, quantity, formatArgs);
    }

    @Override
    public String getQuantityString(int id, int quantity) throws NotFoundException {
        return mBase.getQuantityString(id, quantity);
    }

    @Override
    public CharSequence getText(int id, CharSequence def) {
        return mBase.getText(id, def);
    }

    @Override
    public CharSequence[] getTextArray(int id) throws NotFoundException {
        return mBase.getTextArray(id);
    }

    @Override
    public String[] getStringArray(int id) throws NotFoundException {
        return mBase.getStringArray(id);
    }

    @Override
    public int[] getIntArray(int id) throws NotFoundException {
        return mBase.getIntArray(id);
    }

    @Override
    public TypedArray obtainTypedArray(int id) throws NotFoundException {
        return mBase.obtainTypedArray(id);
    }

    @Override
    public float getDimension(int id) throws NotFoundException {
        return mBase.getDimension(id);
    }

    @Override
    public int getDimensionPixelOffset(int id) throws NotFoundException {
        return mBase.getDimensionPixelOffset(id);
    }

    @Override
    public int getDimensionPixelSize(int id) throws NotFoundException {
        return mBase.getDimensionPixelSize(id);
    }

    @Override
    public float getFraction(int id, int base, int pbase) {
        return mBase.getFraction(id, base, pbase);
    }

    @Override
    public Drawable getDrawable(int id) throws NotFoundException {
        return mBase.getDrawable(id);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
        return mBase.getDrawable(id, theme);
    }

    @Override
    public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
            return mBase.getDrawableForDensity(id, density);
        } else {
            return null;
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public Drawable getDrawableForDensity(int id, int density, Theme theme) {
        return mBase.getDrawableForDensity(id, density, theme);
    }

    @Override
    public Movie getMovie(int id) throws NotFoundException {
        return mBase.getMovie(id);
    }

    @Override
    public int getColor(int id) throws NotFoundException {
        return mBase.getColor(id);
    }

    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public int getColor(int id, Theme theme) throws NotFoundException {
        return mBase.getColor(id, theme);
    }

    @Override
    public ColorStateList getColorStateList(int id) throws NotFoundException {
        return mBase.getColorStateList(id);
    }

    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public ColorStateList getColorStateList(int id, Theme theme) throws NotFoundException {
        return mBase.getColorStateList(id, theme);
    }

    @Override
    public boolean getBoolean(int id) throws NotFoundException {
        return mBase.getBoolean(id);
    }

    @Override
    public int getInteger(int id) throws NotFoundException {
        return mBase.getInteger(id);
    }

    @Override
    public XmlResourceParser getLayout(int id) throws NotFoundException {
        return mBase.getLayout(id);
    }

    @Override
    public XmlResourceParser getAnimation(int id) throws NotFoundException {
        return mBase.getAnimation(id);
    }

    @Override
    public XmlResourceParser getXml(int id) throws NotFoundException {
        return mBase.getXml(id);
    }

    @Override
    public InputStream openRawResource(int id) throws NotFoundException {
        return mBase.openRawResource(id);
    }

    @Override
    public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
        return mBase.openRawResource(id, value);
    }

    @Override
    public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
        return mBase.openRawResourceFd(id);
    }

    @Override
    public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException {
        mBase.getValue(id, outValue, resolveRefs);
    }

    @Override
    public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs) throws NotFoundException {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
            mBase.getValueForDensity(id, density, outValue, resolveRefs);
        }
    }

    @Override
    public void getValue(String name, TypedValue outValue, boolean resolveRefs) throws NotFoundException {
        mBase.getValue(name, outValue, resolveRefs);
    }

    @Override
    public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
        return mBase.obtainAttributes(set, attrs);
    }

    @Override
    public DisplayMetrics getDisplayMetrics() {
        return mBase.getDisplayMetrics();
    }

    @Override
    public Configuration getConfiguration() {
        return mBase.getConfiguration();
    }

    @Override
    public int getIdentifier(String name, String defType, String defPackage) {
        return mBase.getIdentifier(name, defType, defPackage);
    }

    @Override
    public String getResourceName(int resid) throws NotFoundException {
        return mBase.getResourceName(resid);
    }

    @Override
    public String getResourcePackageName(int resid) throws NotFoundException {
        return mBase.getResourcePackageName(resid);
    }

    @Override
    public String getResourceTypeName(int resid) throws NotFoundException {
        return mBase.getResourceTypeName(resid);
    }

    @Override
    public String getResourceEntryName(int resid) throws NotFoundException {
        return mBase.getResourceEntryName(resid);
    }

    @Override
    public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle) throws XmlPullParserException, IOException {
        mBase.parseBundleExtras(parser, outBundle);
    }

    @Override
    public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle) throws XmlPullParserException {
        mBase.parseBundleExtra(tagName, attrs, outBundle);
    }
}
  1. SingleMixRes优先从插件加载资源,找不到则从宿主加载资源
public class SingleMixRes extends ResourcesWrapper{
    private Resources mHostResources;

    public SingleMixRes(Resources hostResources, Resources pluginResources) {
        super(pluginResources);
        mHostResources = hostResources;
    }

    @Override
    public CharSequence getText(int id) throws NotFoundException {
        try {
            return super.getText(id);
        } catch (NotFoundException e) {
            return mHostResources.getText(id);
        }
    }

    @Override
    public String getString(int id) throws NotFoundException {
        try {
            return super.getString(id);
        } catch (NotFoundException e) {
            return mHostResources.getString(id);
        }
    }

    @Override
    public String getString(int id, Object... formatArgs) throws NotFoundException {
        try {
            return super.getString(id,formatArgs);
        } catch (NotFoundException e) {
            return mHostResources.getString(id,formatArgs);
        }
    }

    @Override
    public float getDimension(int id) throws NotFoundException {
        try {
            return super.getDimension(id);
        } catch (NotFoundException e) {
            return mHostResources.getDimension(id);
        }
    }

    @Override
    public int getDimensionPixelOffset(int id) throws NotFoundException {
        try {
            return super.getDimensionPixelOffset(id);
        } catch (NotFoundException e) {
            return mHostResources.getDimensionPixelOffset(id);
        }
    }

    @Override
    public int getDimensionPixelSize(int id) throws NotFoundException {
        try {
            return super.getDimensionPixelSize(id);
        } catch (NotFoundException e) {
            return mHostResources.getDimensionPixelSize(id);
        }
    }

    @Override
    public Drawable getDrawable(int id) throws NotFoundException {
        try {
            return super.getDrawable(id);
        } catch (NotFoundException e) {
            return mHostResources.getDrawable(id);
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
        try {
            return super.getDrawable(id, theme);
        } catch (NotFoundException e) {
            return mHostResources.getDrawable(id,theme);
        }
    }

    @Override
    public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
        try {
            return super.getDrawableForDensity(id, density);
        } catch (NotFoundException e) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
                return mHostResources.getDrawableForDensity(id, density);
            } else {
                return null;
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public Drawable getDrawableForDensity(int id, int density, Theme theme) {
        try {
            return super.getDrawableForDensity(id, density, theme);
        } catch (Exception e) {
            return mHostResources.getDrawableForDensity(id,density,theme);
        }
    }

    @Override
    public int getColor(int id) throws NotFoundException {
        try {
            return super.getColor(id);
        } catch (NotFoundException e) {
            return mHostResources.getColor(id);
        }
    }
    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public int getColor(int id, Theme theme) throws NotFoundException {
        try {
            return super.getColor(id,theme);
        } catch (NotFoundException e) {
            return mHostResources.getColor(id,theme);
        }
    }

    @Override
    public ColorStateList getColorStateList(int id) throws NotFoundException {
        try {
            return super.getColorStateList(id);
        } catch (NotFoundException e) {
            return mHostResources.getColorStateList(id);
        }
    }
    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public ColorStateList getColorStateList(int id, Theme theme) throws NotFoundException {
        try {
            return super.getColorStateList(id,theme);
        } catch (NotFoundException e) {
            return mHostResources.getColorStateList(id,theme);
        }
    }

    @Override
    public boolean getBoolean(int id) throws NotFoundException {
        try {
            return super.getBoolean(id);
        } catch (NotFoundException e) {
            return mHostResources.getBoolean(id);
        }
    }

    @Override
    public XmlResourceParser getLayout(int id) throws NotFoundException {
        try {
            return super.getLayout(id);
        } catch (NotFoundException e) {
            return mHostResources.getLayout(id);
        }
    }

    @Override
    public String getResourceName(int resid) throws NotFoundException {
        try {
            return super.getResourceName(resid);
        } catch (NotFoundException e) {
            return mHostResources.getResourceName(resid);
        }
    }

    @Override
    public int getInteger(int id) throws NotFoundException {
        try {
            return super.getInteger(id);
        } catch (NotFoundException e) {
            return mHostResources.getInteger(id);
        }
    }

    @Override
    public CharSequence getText(int id, CharSequence def) {
        try {
            return super.getText(id,def);
        } catch (NotFoundException e) {
            return mHostResources.getText(id,def);
        }
    }

    @Override
    public InputStream openRawResource(int id) throws NotFoundException {
        try {
            return super.openRawResource(id);
        } catch (NotFoundException e) {
            return mHostResources.openRawResource(id);
        }

    }

    @Override
    public XmlResourceParser getXml(int id) throws NotFoundException {
        try {
            return super.getXml(id);
        } catch (NotFoundException e) {
            return mHostResources.getXml(id);
        }
    }

    @TargetApi(Build.VERSION_CODES.O)
    @Override
    public Typeface getFont(int id) throws NotFoundException {
        try {
            return super.getFont(id);
        } catch (NotFoundException e) {
            return mHostResources.getFont(id);
        }
    }

    @Override
    public Movie getMovie(int id) throws NotFoundException {
        try {
            return super.getMovie(id);
        } catch (NotFoundException e) {
            return mHostResources.getMovie(id);
        }
    }

    @Override
    public XmlResourceParser getAnimation(int id) throws NotFoundException {
        try {
            return super.getAnimation(id);
        } catch (NotFoundException e) {
            return mHostResources.getAnimation(id);
        }
    }

    @Override
    public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
        try {
            return super.openRawResource(id,value);
        } catch (NotFoundException e) {
            return mHostResources.openRawResource(id,value);
        }
    }

    @Override
    public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
        try {
            return super.openRawResourceFd(id);
        } catch (NotFoundException e) {
            return mHostResources.openRawResourceFd(id);
        }
    }
}

更新Activity的资源

参考方式一

两种方式注意事项

  1. 打包出的插件apk需要去除第三方smali代码,否则可能会报资源问题
  2. 代码更新与资源更新最好同步,预防id对应不上

附录

修改资源id冲突及去除第三方smali文件工具

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373076.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mysql 事务的隔离性(隔离级别)

Mysql 中的事务分为手动提交和自动提交&#xff0c;默认是自动提交&#xff0c;所以我们在Mysql每输入一条语句&#xff0c;其实就会被封装成一个事务提交给Mysql服务端。 手动提交需要先输入begin&#xff0c;表示要开始处理事务&#xff0c;然后就是常见的sql语句操作了&…

C++之入门之命名空间、缺省参数、函数重载

一、前言 我们知道c是对c语言的完善以及再发展&#xff0c;所以C中的很多东西是与C语言十分修饰的&#xff0c;并且C也是兼容C的&#xff0c;学习了C之后&#xff0c;相信学C也不在困难&#xff0c;对我们来说&#xff0c;唯一感到不解和陌生就只有 using namespace std; 这条…

【c++】STL1—STL初识

文章目录STL的基本概念STL六大组件STL中容器、算法、迭代器容器算法迭代器容器算法迭代器初识vector存放内置数据类型vector存放自定义数据类型容器嵌套容器c的面向对象和泛型编程思想&#xff0c;目的就是复用性的提升。 为了建立数据结构和算法的一套标准&#xff0c;诞生了S…

并查集(13张图解)--擒贼先擒王

目录 前言 故事 &#x1f33c;思路 &#x1f33c;总结 &#x1f33c;代码 &#x1f44a;观察过程代码 &#x1f44a;正确代码 &#x1f44a;细节代码 来自《啊哈算法》 前言 刚学了树在优先队列中的应用--堆的实现 那么树还有哪些神奇的用法呢&#xff1f;我们从一…

前端卷算法系列(二)

前端卷算法系列&#xff08;二&#xff09; 回文数 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样…

zookeeper集群的搭建,菜鸟升级大神必看

一、下载安装zookeeperhttp://archive.apache.org/dist/zookeeper/下载最新版本2.8.1http://archive.apache.org/dist/zookeeper/zookeeper-3.8.1/二、上传安装包到服务器上并且解压&#xff0c;重命名tar -zxvf apache-zookeeper-3.8.1-bin.tar.gzmv apache-zookeeper-3.8.1-b…

设计环形队列

文章目录1.思路分析1.1队列空满分析1.2出队分析2.循环队列设计1.思路分析 1.1队列空满分析 首先我们假设一个长度为4的环形队列 队头front 队尾rear 当队列为空时 frontrear 当队列满时 frontrear 所以我们无法判断队列是满的或者空的 因此我们多加入一个空间使队列长度为5&am…

什么是自适应平台服务?

总目录链接==>> AutoSAR入门和实战系列总目录 文章目录 什么是自适应平台服务?1.1 自适应平台服务包含哪些功能簇呢?1.1.1 ara::sm 状态管理 (SM)1.1.2 ara::diag 诊断管理 (DM)1.1.3 ara::s2s 信号到服务映射1.1.4 ara::nm 网络管理 (NM)1.1.5 ara::ucm 更新和配置管…

数据结构期末复习总结(前章)

作者的话 作为一名计算机类的学生&#xff0c;我深知数据结构的重要性。在期末复习前&#xff0c;我希望通过这篇博客给大家一些复习建议。希望能帮助大家夯实数据结构的基础知识&#xff0c;并能够更好地掌握数据结构和算法的应用。 一、绪论 数据&#xff1a;信息的载体&am…

【测试】loadrunner安装

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录备注一、下载安装包二、安装loadrunner三、修改浏览器配置今天搬砖不努力&#xff0c;明天地位不稳定&#xff01; 备注 电脑最好有IE浏览器&#xff0c;但是没有也没事儿。&#xff08;注意&#xff1a;IE浏览器不…

Bootstrap系列之栅格系统

Bootstrap栅格系统 bootatrap提供了一套响应式&#xff0c;移动设备优先的流式网格系统&#xff0c;随着屏幕或者视口尺寸的增加&#xff0c;系统会自动分为最多12列&#xff0c;多出12列的将不再此行显示&#xff08;换行显示&#xff09; bootstrap网格系统有以下六个类 重点…

华为OD机试用Python实现 -【云短信平台优惠活动】(2023-Q1 新题)

华为OD机试题 华为OD机试300题大纲云短信平台优惠活动题目描述输入描述输出描述示例一输入输出说明示例二输入输出说明Python 代码实现代码编写思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看…

【Java基础】操作系统原理

一、进程 进程是指一段程序的执行过程&#xff0c;会消耗系统资源如CPU、内存、网络等。 一个进程包含静态代码段&#xff0c;数据&#xff0c;寄存器地址等 进程的特点 动态性&#xff08;可动态地创建、结束进程&#xff09; 并发性&#xff08;进程被独立调度并占用处理…

服务器部署—若依【vue】如何部署到nginx里面?nginx刷新页面404怎么办?【完美解决建议收藏】

服务器部署项目我们大家都会遇到&#xff0c;但是有些铁子会遇到很多的问题&#xff0c;比如前端部署nginx如何操作&#xff1f; 前端有单纯的静态页面、还有前后端分离的项目&#xff1b;这里博主直接分享最牛最到位的前后端分离项目的前端部署到nginx上面&#xff0c;以若依项…

C语言之习题练习集

&#x1f497; &#x1f497; 博客:小怡同学 &#x1f497; &#x1f497; 个人简介:编程小萌新 &#x1f497; &#x1f497; 如果博客对大家有用的话&#xff0c;请点赞关注再收藏 &#x1f31e; 文章目录牛客网题号&#xff1a; JZ17 打印从1到最大的n位数牛客网题号&#x…

Laravel框架03:DB类操作数据库

Laravel框架03&#xff1a;DB类操作数据库一、概述二、数据表的创建与配置三、增删改操作1. 增加信息2. 修改数据3. 删除数据四、查询操作1. 取出基本数据2. 取出单行数据3. 获取一个字段的值4. 获取多个字段的值5. 排序6. 分页五、执行任意的SQL语句一、概述 按照MVC的架构&a…

详讲函数知识

目录 1. 函数是什么&#xff1f; 2. C语言中函数的分类&#xff1a; 2.1 库函数&#xff1a; 2.2 自定义函数 函数的基本组成&#xff1a; 3. 函数的参数 3.1 实际参数&#xff08;实参&#xff09;&#xff1a; 3.2 形式参数&#xff08;形参&#xff09;&#xff1a; …

我那点浅薄的MOS模拟集成电路基础

记录研究生课程模拟集成电路设计所学到的一些知识&#xff0c;这门课是由刘老师和周老师一起上的&#xff0c;刘老师讲模拟集成部分这个模集跟模电还是有很大的区别的&#xff0c;模拟集成主要是针对MOS器件的集成&#xff0c;学得更专业也更深&#xff1b;而周老师讲的是信号检…

华为OD机试题,用 Java 解【图片整理】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

Yolov3,v4,v5区别

网络区别就不说了&#xff0c;ipad笔记记录了&#xff0c;这里只说其他的区别1 输入区别1.1 yolov3没什么特别的数据增强方式1.2 yolov4Mosaic数据增强Yolov4中使用的Mosaic是参考2019年底提出的CutMix数据增强的方式&#xff0c;但CutMix只使用了两张图片进行拼接&#xff0c;…