欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

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

来源: 开发者 投稿于  被查看 41766 次 评论:243

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


作者:隔壁王较瘦

链接:

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 ,下面是目录结构




到这里基本结束了。



用户评论