前言: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,示例如下:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第1张图片
添加完成后,需要gradle同步一下,让我们的配置生效。

这个时候我们点击左侧工具栏中的Build Variants(翻译为编译变体)中可以看到现在对应三种编译类型:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第2张图片

2.完成第一步,接下来需要在app/src下,建立和productFlavors中声明的类型同名的目录,当中分别添加java和res两个目录,示例如下:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第3张图片
我在res目录下又添加了drawable-xxhdpi和valuesu 两个目录,可以看到values目录下有colors.xml和strings.xml资源文件,这里存放的就是对应的产品下的资源文件,稍后会具体看到。

3.分别在productFlavors对应的res/values/colors.xml下添加资源属性,如下图所示:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第4张图片
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第5张图片
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第6张图片
4.同理,在productFlavors对应的res/values/strings.xml下添加资源属性,如下图所示:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第7张图片
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第8张图片
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第9张图片
这里对应不同产物的app名称,同时我们需要删除/注释app module下的main/res/strings.xml中的app_name属性,避免冲突。

5.打开activity_main.xml布局文件,进行一些小改动,如图所示:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第10张图片
现在我们可以看到一个雏形,我们在三个productFlavors对应目录下的资源会被找到,这是根据上面提到的Build Variants中选择的编译类型决定的,会自动寻找对应的资源文件中的属性。

6.在MainActivity中设置文字显示当前应用包名:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第11张图片
7.接下来看看AndroidManifest.xml,打开AndroidManifest.xml文件:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第12张图片
注意到现在package="com.multi.app"是这个值,但是这个并不是最终值,最终值是什么呢?马上揭晓!

8.是骡子是马,跑起来看看,当前编译环境选择的是firstappdebug,点击运行,这个时候找到如图目录下的文件:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第13张图片
在我们的app/build/intermediates/manifests/full/firstapp/debug下,有我们最终生成的清单文件,这个才是最终的输出产物,可以看到这个时候package的值已经变成了我们在productFlavors中给firstapp设置的applicationId的值了,这里简单提一下,因为清单文件会在打包的时候汇总所有module下的AndroidManifest.xml文件,去重,然后生成一个最终产物,如果有不了解的同学可以自行查阅相关资料。

9.我们选择不同的编译类型,各自运行起来,截个图做个对比,如图所示:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第14张图片
可以看到我们并没有手动创建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的配置。

来看看我们代码中的:
Android实现多渠道打包,动态替换包名、Icon、图片等资源,解决因applicationId和BuildConfig路径不匹配的问题_第15张图片
可以看到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这种方式占位使用,编译报错找不到包名,毕竟这个和我们的代码目录息息相关。

有问题大家可以留言关注,共同探讨,看到回复,感恩~

更多相关文章

  1. android 图片处理 (滤镜,图片位置)
  2. 关于android图片的传输,android图片传输方式,xml传输图片,android
  3. 【Android 内存优化】Bitmap 内存占用计算 ( Bitmap 图片内存占
  4. 转载——Android大图片裁剪终极解决方案
  5. Android ListView 图片异步加载和图片内存缓存
  6. Android中打包含有Activity以及资源文件的jar包在工程中调用
  7. 一起写一个Android图片轮播控件

随机推荐

  1. 关于Android主题splash全屏和主页面的沉
  2. android读取sd卡图片并进行缩放操作
  3. TextInputEditText样式设置
  4. android sudio 快捷键
  5. android 屏幕旋转
  6. Android使用HttpPost向服务器发送Json数
  7. 安卓学习笔记之使用widget桌面小控件及源
  8. android 双击图片放大,再双击缩小效果 【
  9. 关于android设备的分辨率
  10. 【Android多屏适配】动态改变Listview it