Android实现多渠道打包,动态替换包名Icon图片等资源,解决因applicationId和BuildConfig路径不匹配的问题
Posted 胖子爱你520
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android实现多渠道打包,动态替换包名Icon图片等资源,解决因applicationId和BuildConfig路径不匹配的问题相关的知识,希望对你有一定的参考价值。
前言:android实现多渠道打包,这个问题并不新鲜,解决方案是固定的那么几种,网上的博客也有很多,我这里只是针对近期开发中遇到的坑进行整理,方便自己方便他人。
一、初识productFlavors
无疑要实现一个壳工程打出不同样式的包,这个技术解决方案Android已经替我们考虑到了,也就是使用Gradle中的productFlavors,在做定制或适配的时候,不需要建立多个工程、来回切换项目分支、逐个编译apk,使用productFlavors可以帮我们简化这一步操作,快速打包所有项目版本的apk。
productFlavors用处
- 创建不同的产品并为不同产品分配专有属性
- 设置不同代码引用
- 先在src目录下建立对应的文件夹,比如java代码则建立product/java,res文件夹则建立product/res(product为productFlavors中配置的名称)
- 设置不同的产品引入不同的包
二、项目结构分析
1.新建工程名为MultiAppDemo,打开app module下的build.gradle文件,在android结构下添加productFlavors,示例如下:
添加完成后,需要gradle同步一下,让我们的配置生效。
这个时候我们点击左侧工具栏中的Build Variants(翻译为编译变体)中可以看到现在对应三种编译类型:
2.完成第一步,接下来需要在app/src下,建立和productFlavors中声明的类型同名的目录,当中分别添加java和res两个目录,示例如下:
我在res目录下又添加了drawable-xxhdpi和valuesu 两个目录,可以看到values目录下有colors.xml和strings.xml资源文件,这里存放的就是对应的产品下的资源文件,稍后会具体看到。
3.分别在productFlavors对应的res/values/colors.xml下添加资源属性,如下图所示:
4.同理,在productFlavors对应的res/values/strings.xml下添加资源属性,如下图所示:
这里对应不同产物的app名称,同时我们需要删除/注释app module下的main/res/strings.xml中的app_name属性,避免冲突。
5.打开activity_main.xml布局文件,进行一些小改动,如图所示:
现在我们可以看到一个雏形,我们在三个productFlavors对应目录下的资源会被找到,这是根据上面提到的Build Variants中选择的编译类型决定的,会自动寻找对应的资源文件中的属性。
6.在MainActivity中设置文字显示当前应用包名:
7.接下来看看AndroidManifest.xml,打开AndroidManifest.xml文件:
注意到现在package="com.multi.app"是这个值,但是这个并不是最终值,最终值是什么呢?马上揭晓!
8.是骡子是马,跑起来看看,当前编译环境选择的是firstappdebug,点击运行,这个时候找到如图目录下的文件:
在我们的app/build/intermediates/manifests/full/firstapp/debug下,有我们最终生成的清单文件,这个才是最终的输出产物,可以看到这个时候package的值已经变成了我们在productFlavors中给firstapp设置的applicationId的值了,这里简单提一下,因为清单文件会在打包的时候汇总所有module下的AndroidManifest.xml文件,去重,然后生成一个最终产物,如果有不了解的同学可以自行查阅相关资料。
9.我们选择不同的编译类型,各自运行起来,截个图做个对比,如图所示:
可以看到我们并没有手动创建3个布局文件,而是在布局中引用了@color/main_color和@string/app_name,在选择对应的Build Variants的时候,就会加载对应Variants目录下的资源,并且获取到的包名也是不一样的,这就达到了我们开头说的多渠道打包的原理,一套代码,多套资源,根据不同编译变体自动选择所需资源,节省了我们的开发工作量。
10.我们这里只做了简单的string和color的演示,如果大家有图片资源的需要,在对应目录下创建drawable文件夹,然后在对应的drawable文件夹添加对应的图片资源即可,操作方式一致,但是有一点,所有多渠道资源,文件名都必须保持一致,否则同一套代码在编译不同产物时会找不到对应的资源文件,会导致编译失败!
三、applicationId和BuildConfig路径不匹配的问题
还记得我们上面看到的AndroidManifest.xml中的package中的值在最终产物里会变成和applicationId中设置的一样么,那么你会不会理所当然的认为BuildConfig的路径也是和applicationId一样呢?
答案是否!
下面来科普一下:
- 每个BuildConfig的类名是由这个buildConfig所在module(不管是app还是lib)的AndroidManifest中的package属性决定,和应用的包名无关。
- 比如 package=“com.xxx.yyy” 则BuildConfig的类名为 com.xxx.yyy.BuildConfig
需要注意的是:
- build.gradle可以通过applicationId修改应用包名,但是BuildConfig类名不会变
- build.gradle可以通过buildConfigField给BuildConfig添加属性,用于代码配置,每个module的BuildConfig只能获取自己module的配置。
来看看我们代码中的:
可以看到BuildConfig的包名就是package中的那个,并没有因为applicationId改变了而变化,所以这会导致什么问题呢?
问题:
在某些框架初始化的时候,会在初始化方法中通过反射机制来获取BuildConfig,通过BuildConfig获取一些配置信息,但是当中做了一些判断,如果我们没有传入包名的时候,他们会根据context.getPackageName()拼接“BuildConfig”字段,然后通过反射获取,但是如果我们设置了applicationId和package不一致的话,这个时候拿到的packageName其实是applicationId的值(比如我们现在的com.zy.special.firstapp),然后用"com.zy.special.firstapp"+"."+“BuildConfig”,通过反射去获取就会抛出ClassNotFoundException异常,因为我们的BuildConfig路径只是com.multi.app.BuildConfig,举个例子:
static void initAppDefault(Context context,String packageName)
mAppContext = context;
String[] modules = null;
try
if(TextUtils.isEmpty(packageName))
packageName = context.getPackageName();
Class<?> buildConfig = Class.forName(packageName + DOT + "BuildConfig");
if (buildConfig == null) return;
Field allModules = buildConfig.getField(ALL_MODULES);
String modules_name = (String) allModules.get(buildConfig);
modules = modules_name.split(",");
if (modules.length == 0) return;
catch (ClassNotFoundException e)
LogUtil.e(TAG, "Initialization failed, have you forgotten to apply plugin in application module?", e);
catch (Exception e)
LogUtil.w(TAG, e.getMessage());
......
可以看到这就是我们说的这种情况,所以这种问题怎么解决呢?那就是在框架初始化的时候需要手动传入和AndroidManifest.xml中package一样的值,这个参数必须和package值保持一致,这样才能保证BuildConfig路径能被正确的找到。
四、总结
通过上述的示例,相信大家可以很直观的感受到productFlavors带来的便利,但是也可能有一些大家没有注意到的坑,另外有一点就是AndroidManifest.xml中package值无法通过manifestPlaceholders这种方式占位使用,编译报错找不到包名,毕竟这个和我们的代码目录息息相关。
有问题大家可以留言关注,共同探讨,看到回复,感恩~
以上是关于Android实现多渠道打包,动态替换包名Icon图片等资源,解决因applicationId和BuildConfig路径不匹配的问题的主要内容,如果未能解决你的问题,请参考以下文章
Gradle多渠道打包(动态设定App名称,应用图标,替换常量,更改包名,变更渠道)