[转]一套源码编译多个APP,不同的签名,包名,界面,字段
1. 1概述
线上源码地址,结合源码看比较容易理解
源码地址:
https://github.com/lmyDevs/multiApp
作为一个刚入行一两年的 代码 Copy 者 来说,这么久没有深入了解 Gradle 真的是一大遗憾,熟悉了 Gradle 之后开发效率能够提高很多,当然,现在我还没有到那个地步,这篇文章就是我实际开发中的需求,刚开始的时候用的是最基本的开发方式,后来东西越来越多,渐渐的感觉这种开发方式太拖慢进度了,然后就搜索学习一番,没有理解,只是学习了基本的操作来解决开发中的需求,特写篇文章记录一下,怕忘记。
emmm, 我可能有一个这样的需求,不知道可不可以…
我的需求很简单…
小张是一个四肢健全,各项功能正常的人,我需要根据这个人克隆出更多人
我的目的很明确…
- 我要跟别人长得不一样(界面不一样)
- 我要身体会特殊的技能(额外功能)
- 我要改名身份证上的名字(APP 名字)
- 我要换身份证上的头像 (APP 桌面图标)
- 我要换身份证号码 (APP 包名)
- 我要换肤色 (不同的界面颜色,icon等)
- 我要不同的签证 (不同的应用签名)
ennn, 我可能有这样一种解决办法, 应该可以…
2. 2首先,我们得有个小张这个人
小张的基本信息是这样
信息描述 | 基本信息 |
身份证头像 (APP 名字) | 小张 |
身份证头像 (APP 头像) | |
身份证号码 (APP 包名) | com.persons.zhang |
小张是长这样的
这是小张的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<span class="">class MainActivity : AppCompatActivity() {</span> private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item -> <span class="">when</span> (item.itemId) { R.id.navigation_home -> { viewPager.currentItem = <span class="">0</span> <span class="">return</span>@OnNavigationItemSelectedListener <span class="">true</span> } R.id.navigation_dashboard -> { viewPager.currentItem = <span class="">1</span> <span class="">return</span>@OnNavigationItemSelectedListener <span class="">true</span> } R.id.navigation_notifications -> { viewPager.currentItem = <span class="">2</span> <span class="">return</span>@OnNavigationItemSelectedListener <span class="">true</span> } } <span class="">false</span> } override fun onCreate(<span class="">savedInstanceState:</span> Bundle?) { <span class="">super</span>.onCreate(savedInstanceState) setContentView(R.layout.activity_main) navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener) var fragments : MutableList<Fragment> = mutableListOf() fragments.add(HeadFragment()) fragments.add(HandFragment()) fragments.add(FootFragment()) viewPager.adapter = MainViewPagerAdapter(supportFragmentManager,fragments) viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{ override fun onPageScrollStateChanged(<span class="">state:</span> Int) { } override fun onPageScrolled(<span class="">position:</span> Int, <span class="">positionOffset:</span> Float, <span class="">positionOffsetPixels:</span> Int) { } override fun onPageSelected(<span class="">position:</span> Int) { navigation.menu.getItem(position)?.setChecked(<span class="">true</span>) } }) } } |
这是小张的布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<span class=""><?xml version="1.0" encoding="utf-8"?></span> <span class=""><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.persons.zhang.MainActivity"></span> <span class=""><android.support.v4.view.ViewPager android:id="@+id/viewPager" app:layout_constraintBottom_toTopOf="@id/navigation" android:layout_width="match_parent" android:layout_height="wrap_content"/></span> <span class=""><android.support.design.widget.BottomNavigationView android:id="@+id/navigation" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="0dp" android:layout_marginStart="0dp" android:background="?android:attr/windowBackground" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:menu="@menu/navigation" /></span> <span class=""></android.support.constraint.ConstraintLayout></span> |
3. 3我们要在小张的基础上增加出来一个小李
这是我们要增加的小李的大概需求
- 名字要不一样 (APP 名字)
- 身份证号码要不一样 (APP 包名)
- 身份证头像要不一样 (APP 图标)
- 肤色不一样 (界面颜色)
- 头跟别人不一样 (主界面的头)
生产之前的准备工作
我们把 Android 切换到 Project
3-1. 1、新建关于包括小李一切的目录
看着上图,对比着看啊,上图中的 zhang -> app -> src 目录上 右键 -> New -> Directory 新建一个小李的目录,我这里就叫做小李了,下面是图操作:
然后,我们对比着 src/main 这个目录里面的结构,在 li 这个目录下创建一模一样的结构,如下图:
这是li 目录下新建的
这是main 的结构
好了,新建完成。
3-2. 2. 新建针对小李的Gradle构建变体
我也理解不清楚,大概就是把小李这个人当成备胎,这样这个人什么时候想换备胎了,小李这个人可以是其中一个。就是在编译的时候多一个小李的选项,可以单独针对小李进行编译,也就是备胎转正。
我们在 app的gradle下 添加如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<span class="">android</span> { <span class="">compileSdkVersion</span> <span class="">26</span> buildToolsVersion <span class="">"26.0.2"</span> defaultConfig { <span class="">applicationId</span> <span class="">"com.persons.zhang"</span> minSdkVersion <span class="">15</span> targetSdkVersion <span class="">26</span> versionCode <span class="">1</span> versionName <span class="">"1.0"</span> } ... // 重点在这下面这些 flavorDimensions <span class="">"app"</span> // 重点-------------------------- productFlavors { <span class="">li</span> { <span class="">dimension</span> <span class="">"app"</span> } zhang { <span class="">dimension</span> <span class="">"app"</span> } } } |
注意看 productFlavors 里面的 li 和 zhang ,他们里面都有 applicationId、versionCode、versionName 这些,这些就是针对他们不同的人,有不同的版本,不同的包名。
这样写的话必须保证我们新建的那个目录要跟 productFlavors 里面的这个 li { … } 保持一致 ,如果想使用不一样的名字可以按照以下操作来,至于上面的flavorDimensions 我也不太理解,没使用到。
1 2 3 4 5 6 7 8 9 10 11 12 |
android { compileSdkVersion 26 buildToolsVersion <span class="">"26.0.2"</span> ... sourceSets { li { java.srcDir(<span class="">'src/li/java'</span>) res.srcDir(<span class="">'src/li/res'</span>) } } } |
这样显示指定源码路径,名字不一致也无所谓了。
这样我们就新建出来了一个小李 ,但是新建出来了我们怎么才能把默认点击 Run 安装的 debug 版本的app改成 小李呢,如果上面已经一步步配置好了之后,会有下图这些东西:
选择完成之后再点击 Run 安装的就是小李的 debug版本了
此时的小李还是和小张一模一样的,下面我们要把小李 改成他该有的模样。
3-3. 3.不一样的名字
既然我们这个项目都叫做小李了,那我们不能名字还是小张的名字吧,下面我们来给小李改改名字,一般情况下我们的名字都是在 strings.xml 里面的 app_name 字段来定义的,当然不排除你直接在Manifest 里面写死,那你很棒棒哦
我们的 strings.xml 现在只有 src/main/res/values 里面才有,我们要做的是在 src/li/res/values 里面新建一个 strings.xml ,里面新建一个 app_name 的字段,值为 小李 ,如下图:
小张(也就是main)
小李(也就是li)
不知道你注意到没有,小张(main) 的 strings.xml 里面还有 head 等字段, 小李(li) 却没有。
原因 :资源文件会自动合并。就这么简单,就比如说,现在 张 里面有 head 这些,李 没有,等后面我们修改过,资源文件合并之后会变成以下状态:
相同的被替换,不同的被增加 ,相同的会被替换成当前目标的,比如说你现在小张有app_name,小李
也有,如果现在编译目标 是小李的话,app_name 的值就会是小李,好了我们来运行看一下效果:
好了,看来我们修改成功了,上面小张这个APP是我改了些东西保留下来的,为了作对比,正常情况下以前装的小张会被小李覆盖掉, 下面我们来看一下小李的主界面长什么样子。
可以看到我们的app_name 已经修改成功了,其他没修改的还是按照小张的样子来展示的,包括下面的头,手,脚,这些。
3-4. 4.身份证号码不一样(包名不一样)
上面我说了,小张被保留下来是我改了一些东西才能保留下来和小李作对比的,正常情况下安装了小李之后,小张就会被覆盖掉,因为他们的包名一样的。
下面我们来更改包名让 App 共存,我们的操作还是在 app/gradle 里面的 productFlavors 进行更改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="">android</span> { <span class="">compileSdkVersion</span> <span class="">26</span> buildToolsVersion <span class="">"26.0.2"</span> ... productFlavors { <span class="">li</span> { <span class="">applicationId</span> <span class="">"com.persons.li"</span> versionCode <span class="">2</span> dimension <span class="">"app"</span> versionName <span class="">"1.0.2"</span> } zhang { <span class="">applicationId</span> <span class="">"com.persons.zhang"</span> versionCode <span class="">1</span> dimension <span class="">"app"</span> versionName <span class="">"1.0"</span> } } } |
仔细看,我每个都加了 applicationId ,顺便还加了 versionCode,versionName, 这可以单独设置每个app不同的版本号,不同的版本名称,不同的包名,怕晕了,给个截图让你瞅瞅
3-5. 5.身份证头像不一样 (APP 图标)
我们把版本号修改了,现在我们安装了之后就可以两个APP共存了,而且包名也是不相同的,这样基本上实现两个人就可以出现在一个手机里面了。
但是只是名字改了,图标还是一样的,猛一看,很懵逼啊,这一不小心就点错了,多尴尬
众所周知,我们的app图标是放在 res/mipmap 目录下的, 我们之前拷贝的 li 目录里面是和 小张(main) 的是一样的,我们在准备工作的时候还设置了 sourceSet ,而且,前面还说了相同名字的资源被覆盖,不同的被增加 ,我们要做的只是把 小李(li) 的图标放在 li/src/res/mipmap 下就可以了, 然后运行安装到手机:
下面看改过的效果
好了,替换完成了。
6.不同的肤色
我们现在小李的肤色还跟小张的肤色是一样的,下面我们来改一下,一切关于资源的要么被替换,要么被合并 。
我们要改的是 li/res/values/colors.xml , 因为现在小李这个目录下面还没有 colors.xml 这个文件,我们直接从 小张(main) 的目录里面 copy 过来一个,改一下里面的色值就可以了。
小张的肤色
小李的肤色
小李改过肤色的样子
好了,肤色也改完了。
3-6. 7.头界面不一样(重点)
现在小李的头界面还是上面这张图的界面,什么也没有。我们要做的是在头这个界面里面加上眼睛,鼻子,小张是个无面男 ,我们要小李有个鼻子,点击鼻子的时候弹出一个提示。
注意: 这个方法是通用的,就是说,只要是关于 java 代码的都按照这样的套路来做,把要修改的那个类拷贝出来,把基础包里面的删掉,给其他的目录里面也拷贝进去这个类,要不你在编译其他目录的apk的时候会报错的,找不到类。
1.首先我们先把小李的头copy过来。
为什么要拷贝过来? 不拷贝过来怎么会有自己的头,如果没有拷贝过来,默认使用的是小张的头。
注意 :这时我们需要把源码大改一下了,我们要把原本小张的源码给改成通用的,就是把原来小张的源码当成一个基础包,我们要是加人就需要另外新建一个目录来做,就跟我们增加小李一样的。
下面是我们改过的目录结构
可以看到,我们单独把 小张 给新建了一个目录,他的存在方式跟 小李 的是一样的。
为了以防万一,我们还要在 app/gradle 里面修改一下 sourceSet 保证我们的APP源码指向是正确的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
sourceSets { li { java.srcDir(<span class="">'src/li/java'</span>) res.srcDir(<span class="">'src/li/res'</span>) // assets.srcDir(<span class="">'src/li/assets'</span>) // manifest.srcFile(<span class="">'src/li/AndroidManifest.xml'</span>) } zhang { java.srcDir(<span class="">'src/zhang/java'</span>) res.srcDir(<span class="">'src/zhang/res'</span>) // assets.srcDir(<span class="">'src/zhang/assets'</span>) // manifest.srcFile(<span class="">'src/zhang/AndroidManifest.xml'</span>) } } |
怕看懵逼,加一张图
我们想要小李的头跟其他的不一样的话,需要单独把 copy 出来进行修改,这个跟res 下的文件不一样, res 下的文件相同了会覆盖合并,java 目录下面是会报错的,
因为基础包目录下已经有了相同名字的类了。你在 li 的里面加了相同的 类, 李使用的源码是用 基础包里的来做基础的, 现在这个状态就相当于 你在同一个包里面,创建了两个相同名字的类
2. 现在我们给小李的头界面加上鼻子
首先我们先把 基础包 里面 头 的布局文件 copy 到 li 的layout目录下。
看清楚,是 li 目录下面的 layout 文件夹,拷贝到这里以后我们就跟平常写界面一样修改就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span class=""><?xml version="1.0" encoding="utf-8"?></span> <span class=""><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"></span> <span class=""><Button android:id="@+id/click" android:layout_centerInParent="true" android:text="我是鼻子" android:layout_width="wrap_content" android:layout_height="wrap_content" /></span> <span class=""></RelativeLayout></span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span class="">class HeadFragment : Fragment() {</span> override fun onCreateView(<span class="">inflater:</span> LayoutInflater?, <span class="">container:</span> ViewGroup?, <span class="">savedInstanceState:</span> Bundle?): View? { val view = inflater?.inflate(R.layout.fragment_head, container, <span class="">false</span>) <span class="">return</span> view } override fun onViewCreated(<span class="">view:</span> View?, <span class="">savedInstanceState:</span> Bundle?) { <span class="">super</span>.onViewCreated(view, savedInstanceState) click.setOnClickListener { Toast.makeText(activity,<span class="">"我的鼻子被点了一下"</span>,Toast.LENGTH_LONG).show() } } } |
就是界面上有一个叫鼻子的按钮,设置点击之后弹出吐司,是不是还很骚气,而我们加上这个鼻子之后,小张还是原来的小张 ,鼻子只是被加在了小李的头上。
4. 4不同的签名文件
这样好几个app在一块编译,我们在androidstudio里面点击打包还要选择不同的 签名文件,很麻烦,我们还是在 app/src/gradle 里面进行配置就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
android { .... signingConfigs { <span class="">// 只是用来做演示,没有特地生成签名文件</span> li { storeFile file(<span class="">"src/li/liKeystore.jks"</span>) storePassword <span class="">"li123"</span> keyAlias <span class="">"li"</span> keyPassword <span class="">"li1234"</span> <span class="">// 开启 V2 签名</span> v2SigningEnabled <span class="">true</span> } zhang { storeFile file(<span class="">"src/zhang/liKeystore.jks"</span>) storePassword <span class="">"zhang123"</span> keyAlias <span class="">"zhang"</span> keyPassword <span class="">"zhang1234"</span> v2SigningEnabled <span class="">true</span> } } productFlavors { li { applicationId <span class="">"com.persons.li"</span> versionCode <span class="">2</span> dimension <span class="">"app"</span> versionName <span class="">"1.0.2"</span> <span class="">// 注意看这里---------------------------------------------</span> signingConfig signingConfigs.li } zhang { applicationId <span class="">"com.persons.zhang"</span> versionCode <span class="">1</span> dimension <span class="">"app"</span> versionName <span class="">"1.0"</span> signingConfig signingConfigs.zhang } } } |
5. 5其他操作
1.批量打包和运行签名包测试第三方登录或分享,或支付。
根据我们上面 gradle 的配置,签名打包很方便,打包使用这种方式,上面可以单独打一个,也可以一次打完,打一个就选择第四步其他的名字,比如 assembleZhang 这些。
打包使用这种方式,上面可以单独打一个,也可以一次打完,打一个就选择第四步其他的名字,比如 assembleZhang 这些。
2. Manifest value 值设置占位符
在 Manifest 中
在 Gradle 中
3. 关于微信回调
微信的回调是需要放在 包名/wxapi/ 目录下的,我们这样拷贝之后没有了包名都是不一样的,我们要为每个app的 src/java 目录下新建一个和包名一样的目录,比如小张, 我们为了方便区分,把小张的包名改成 cn.zhang ,下面是目录结构
到这里基本结束了。