查看原文
其他

教你如何一套源码编译多个APP,不同的签名,包名,界面,字段...

隔壁王较瘦 鸿洋 2019-04-05

每日推荐


https://github.com/NoEndToLF/Gif-Load-ReTry-Refresh


支持gif图片的Load反馈框架,只需一张gif图,一行代码解决初次加载,重试加载,刷新加载,与生命周期绑定,LeakCanary检测无内存泄漏


本文作者


作者:隔壁王较瘦

链接:

https://www.jianshu.com/p/4a5625a00f8d

本文由作者授权发布。


1概述


线上源码地址,结合源码看比较容易理解 

源码地址:

https://github.com/lmyDevs/multiApp


作为一个刚入行一两年的 代码 Copy 者 来说,这么久没有深入了解 Gradle 真的是一大遗憾,熟悉了 Gradle 之后开发效率能够提高很多,当然,现在我还没有到那个地步,这篇文章就是我实际开发中的需求,刚开始的时候用的是最基本的开发方式,后来东西越来越多,渐渐的感觉这种开发方式太拖慢进度了,然后就搜索学习一番,没有理解,只是学习了基本的操作来解决开发中的需求,特写篇文章记录一下,怕忘记。


emmm, 我可能有一个这样的需求,不知道可不可以...


我的需求很简单...


小张是一个四肢健全,各项功能正常的人,我需要根据这个人克隆出更多人


我的目的很明确...


  • 我要跟别人长得不一样(界面不一样)

  • 我要身体会特殊的技能(额外功能)

  • 我要改名身份证上的名字(APP 名字)

  • 我要换身份证上的头像 (APP 桌面图标)

  • 我要换身份证号码 (APP 包名)

  • 我要换肤色 (不同的界面颜色,icon等)

  • 我要不同的签证 (不同的应用签名)


ennn, 我可能有这样一种解决办法, 应该可以...


2首先,我们得有个小张这个人


小张的基本信息是这样


信息描述 基本信息
身份证头像 (APP 名字)小张

身份证头像 (APP 头像)

身份证号码 (APP 包名) com.persons.zhang


小张是长这样的




这是小张的源码


class MainActivity : AppCompatActivity() {
   private val mOnNavigationItemSelectedListener =  BottomNavigationView.OnNavigationItemSelectedListener { item ->
       when (item.itemId) {
           R.id.navigation_home -> {
               viewPager.currentItem = 0
               return@OnNavigationItemSelectedListener true
           }
           R.id.navigation_dashboard -> {
               viewPager.currentItem = 1
               return@OnNavigationItemSelectedListener true
           }
           R.id.navigation_notifications -> {
               viewPager.currentItem = 2
               return@OnNavigationItemSelectedListener true
           }
       }
       false
   }
   override fun onCreate(savedInstanceState: Bundle?) {
       super.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(state: Int) {
           }
           override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
           }
           override fun onPageSelected(position: Int) {
               navigation.menu.getItem(position)?.setChecked(true)
           }
       })
   }
}


这是小张的布局


<?xml version="1.0" encoding="utf-8"?>
<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">

   <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"/>

   <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" />

</android.support.constraint.ConstraintLayout>


3我们要在小张的基础上增加出来一个小李


这是我们要增加的小李的大概需求


  • 名字要不一样 (APP 名字)

  • 身份证号码要不一样 (APP 包名)

  • 身份证头像要不一样 (APP 图标)

  • 肤色不一样 (界面颜色)

  • 头跟别人不一样 (主界面的头)


生产之前的准备工作


我们把 Android 切换到 Project



1、新建关于包括小李一切的目录


看着上图,对比着看啊,上图中的 zhang -> app -> src 目录上 右键 -> New -> Directory 新建一个小李的目录,我这里就叫做小李了,下面是图操作:



然后,我们对比着 src/main 这个目录里面的结构,在 li 这个目录下创建一模一样的结构,如下图:


这是li 目录下新建的



这是main 的结构



好了,新建完成。


2. 新建针对小李的Gradle构建变体


我也理解不清楚,大概就是把小李这个人当成备胎,这样这个人什么时候想换备胎了,小李这个人可以是其中一个。就是在编译的时候多一个小李的选项,可以单独针对小李进行编译,也就是备胎转正。


我们在 app的gradle下 添加如下代码


android {
   compileSdkVersion 26
   buildToolsVersion "26.0.2"
   defaultConfig {
       applicationId "com.persons.zhang"
       minSdkVersion 15
       targetSdkVersion 26
       versionCode 1
       versionName "1.0"
   }
   ...
 // 重点在这下面这些
   flavorDimensions "app"
 // 重点--------------------------
   productFlavors {
       li {
           dimension "app"
       }
       zhang {
           dimension "app"
       }
   }
}


注意看 productFlavors 里面的 li zhang ,他们里面都有 applicationIdversionCodeversionName 这些,这些就是针对他们不同的人,有不同的版本,不同的包名。


这样写的话必须保证我们新建的那个目录要跟 productFlavors 里面的这个 li { ... } 保持一致 ,如果想使用不一样的名字可以按照以下操作来,至于上面的flavorDimensions 我也不太理解,没使用到。


android {
   compileSdkVersion 26
   buildToolsVersion "26.0.2"
   ...
   sourceSets {
       li {
           java.srcDir('src/li/java')
           res.srcDir('src/li/res')
       }
   }
}


这样显示指定源码路径,名字不一致也无所谓了。


这样我们就新建出来了一个小李 ,但是新建出来了我们怎么才能把默认点击 Run 安装的 debug 版本的app改成 小李呢,如果上面已经一步步配置好了之后,会有下图这些东西:



选择完成之后再点击 Run 安装的就是小李的 debug版本了


此时的小李还是和小张一模一样的,下面我们要把小李 改成他该有的模样。


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 已经修改成功了,其他没修改的还是按照小张的样子来展示的,包括下面的头,手,脚,这些。


4.身份证号码不一样(包名不一样)


上面我说了,小张被保留下来是我改了一些东西才能保留下来和小李作对比的,正常情况下安装了小李之后,小张就会被覆盖掉,因为他们的包名一样的。


下面我们来更改包名让 App 共存,我们的操作还是在 app/gradle 里面的 productFlavors 进行更改。


android {
   compileSdkVersion 26
   buildToolsVersion "26.0.2"
   ...
   productFlavors {
       li {
           applicationId "com.persons.li"
           versionCode 2
           dimension "app"
           versionName "1.0.2"
       }
       zhang {
           applicationId "com.persons.zhang"
           versionCode 1
           dimension "app"
           versionName "1.0"
       }
   }
}


仔细看,我每个都加了 applicationId ,顺便还加了 versionCode,versionName, 这可以单独设置每个app不同的版本号,不同的版本名称,不同的包名,怕晕了,给个截图让你瞅瞅




5.身份证头像不一样 (APP 图标)


我们把版本号修改了,现在我们安装了之后就可以两个APP共存了,而且包名也是不相同的,这样基本上实现两个人就可以出现在一个手机里面了。


但是只是名字改了,图标还是一样的,猛一看,很懵逼啊,这一不小心就点错了,多尴尬


众所周知,我们的app图标是放在 res/mipmap 目录下的, 我们之前拷贝的 li 目录里面是和 小张(main) 的是一样的,我们在准备工作的时候还设置了 sourceSet ,而且,前面还说了相同名字的资源被覆盖,不同的被增加 ,我们要做的只是把 小李(li) 的图标放在 li/src/res/mipmap 下就可以了, 然后运行安装到手机:



下面看改过的效果



好了,替换完成了。


6.不同的肤色


我们现在小李的肤色还跟小张的肤色是一样的,下面我们来改一下,一切关于资源的要么被替换,要么被合并


我们要改的是 li/res/values/colors.xml , 因为现在小李这个目录下面还没有 colors.xml 这个文件,我们直接从 小张(main) 的目录里面 copy 过来一个,改一下里面的色值就可以了。


小张的肤色



小李的肤色



小李改过肤色的样子




好了,肤色也改完了。


7.头界面不一样(重点)


现在小李的头界面还是上面这张图的界面,什么也没有。我们要做的是在头这个界面里面加上眼睛,鼻子,小张是个无面男 ,我们要小李有个鼻子,点击鼻子的时候弹出一个提示。


注意: 这个方法是通用的,就是说,只要是关于 java 代码的都按照这样的套路来做,把要修改的那个类拷贝出来,把基础包里面的删掉,给其他的目录里面也拷贝进去这个类,要不你在编译其他目录的apk的时候会报错的,找不到类。


1.首先我们先把小李的头copy过来。


为什么要拷贝过来? 不拷贝过来怎么会有自己的头,如果没有拷贝过来,默认使用的是小张的头。


注意 :这时我们需要把源码大改一下了,我们要把原本小张的源码给改成通用的,就是把原来小张的源码当成一个基础包,我们要是加人就需要另外新建一个目录来做,就跟我们增加小李一样的。


下面是我们改过的目录结构



可以看到,我们单独把 小张 给新建了一个目录,他的存在方式跟 小李 的是一样的。


为了以防万一,我们还要在 app/gradle 里面修改一下 sourceSet 保证我们的APP源码指向是正确的。


sourceSets {
   li {
       java.srcDir('src/li/java')
       res.srcDir('src/li/res')
       // assets.srcDir('src/li/assets')
       // manifest.srcFile('src/li/AndroidManifest.xml')
   }
   zhang {
       java.srcDir('src/zhang/java')
       res.srcDir('src/zhang/res')
       // assets.srcDir('src/zhang/assets')
       // manifest.srcFile('src/zhang/AndroidManifest.xml')
   }
}


怕看懵逼,加一张图



我们想要小李的头跟其他的不一样的话,需要单独把 copy 出来进行修改,这个跟res 下的文件不一样, res 下的文件相同了会覆盖合并,java 目录下面是会报错的,


因为基础包目录下已经有了相同名字的类了。你在 li 的里面加了相同的 类, 李使用的源码是用 基础包里的来做基础的, 现在这个状态就相当于 你在同一个包里面,创建了两个相同名字的类




2. 现在我们给小李的头界面加上鼻子


首先我们先把 基础包 里面 头 的布局文件 copy 到 li 的layout目录下。



看清楚,是 li 目录下面的 layout 文件夹,拷贝到这里以后我们就跟平常写界面一样修改就可以了。


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <Button
       android:id="@+id/click"
       android:layout_centerInParent="true"
       android:text="我是鼻子"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content" />

</RelativeLayout>


class HeadFragment : Fragment() {
   override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
       val view = inflater?.inflate(R.layout.fragment_head, container, false)
       return view
   }
   override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)
       click.setOnClickListener {
           Toast.makeText(activity,"我的鼻子被点了一下",Toast.LENGTH_LONG).show()
       }
   }
}




就是界面上有一个叫鼻子的按钮,设置点击之后弹出吐司,是不是还很骚气,而我们加上这个鼻子之后,小张还是原来的小张 ,鼻子只是被加在了小李的头上。


4不同的签名文件


这样好几个app在一块编译,我们在androidstudio里面点击打包还要选择不同的 签名文件,很麻烦,我们还是在 app/src/gradle 里面进行配置就可以了。


android {
 ....
   signingConfigs {
       // 只是用来做演示,没有特地生成签名文件
       li {
           storeFile file("src/li/liKeystore.jks")
           storePassword "li123"
           keyAlias "li"
           keyPassword "li1234"
           // 开启 V2 签名
           v2SigningEnabled true
       }
       zhang {
           storeFile file("src/zhang/liKeystore.jks")
           storePassword "zhang123"
           keyAlias "zhang"
           keyPassword "zhang1234"
           v2SigningEnabled true
       }
   }
   productFlavors {
       li {
           applicationId "com.persons.li"
           versionCode 2
           dimension "app"
           versionName "1.0.2"
         // 注意看这里---------------------------------------------
           signingConfig signingConfigs.li
       }
       zhang {
           applicationId "com.persons.zhang"
           versionCode 1
           dimension "app"
           versionName "1.0"
           signingConfig signingConfigs.zhang
       }
   }
}


5其他操作


1.批量打包和运行签名包测试第三方登录或分享,或支付。


根据我们上面 gradle 的配置,签名打包很方便,打包使用这种方式,上面可以单独打一个,也可以一次打完,打一个就选择第四步其他的名字,比如 assembleZhang 这些。


打包使用这种方式,上面可以单独打一个,也可以一次打完,打一个就选择第四步其他的名字,比如 assembleZhang 这些。



2. Manifest value 值设置占位符


在 Manifest 中



在 Gradle 中



3. 关于微信回调


微信的回调是需要放在 包名/wxapi/ 目录下的,我们这样拷贝之后没有了包名都是不一样的,我们要为每个app的 src/java 目录下新建一个和包名一样的目录,比如小张, 我们为了方便区分,把小张的包名改成 cn.zhang ,下面是目录结构




到这里基本结束了。


推荐阅读:


Android截屏相关的都在这儿

我的2017年终总结



帮朋友推荐一下他们的一个专栏《 5 分钟机器学习》,每天花五分钟了解机器学习的基础知识,言语通熟易懂,作者之前从事 Android 开发,现在成功转型正在某大型医药互联网公司做机器学习相关工作。相信对各位从事 Android 开发又想转型的同学具有非常大参考价值。


关注小专栏平台,回复“机器学习”即可免费订阅这份材料。



END



如果你想要跟大家分享你的文章,欢迎投稿~



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存