Android差异化打包,实现一套代码打出不同的APP

1. 前言

当一个APP成熟起来,功能会越来越多,业务会越来越复杂,面向的用户群越来越大。这个时候为了更进一步的发展和扩大业务,我们可以对APP进行拆分,做成两个甚至更多个的APP,每个APP都服务特定的用户群。为了缩短开发时间,降低维护成本,肯定是不能再单独新建一个项目工程的。那怎么在原有的项目工程来进行开发,从而实现一套代码能够打出不同的APP呢?这就是本篇文章要介绍的“差异化打包”。

2. 解决方案

2.1 原理

本篇只介绍使用Gradle来进行打包的方式,其他打包方式就不介绍了(因为我就只会这种,哈哈哈)。在 Android Studio 升级到3.0之后,Gradle 增加了多维度管理配置 FlavorDimensions 和 ProductFlavors,详细介绍可见这篇文章 “Android : 代码多维度管理(产品风味) ”。而我们就是利用这个多维度管理配置来进行差异化打包。

2.2 需求

在这里我将用我的一个开源项目 BlogDemo 进行演示。先来说一下具体的需求:

  • 将APP拆分成两个相互独立的APP,一个是简易版,即原来的APP,另一个是专业版,即新的APP
  • 简易版APP的名称为“BlogDemo”,包名为 com.fantasy.blogdemo,服务器地址为 https://fantasy.com
  • 专业版APP的名称为“BlogDemoPro”,包名为 com.fantasy.blogdemo.pro,服务器地址为 https://fantasy.pro.com
  • 简易版APP在欢迎页使用 Android 自带的 Dialog 来提示内容,专业版APP在欢迎页使用第三方UI库 QMUI 的 Dialog 来提示内容

需求就这几个,接下来就开始编码吧!

2.3 编码

在 app 模块的 build.gradle 中,我们可以在 android 这个闭包里面加上如下配置,就可以实现两个APP使用不同的APP名称、包名和服务器地址。

android {
    // 差异化打包配置
    flavorDimensions "mode"

    productFlavors {
        simple { // 简易版
            applicationId "com.fantasy.blogdemo"
            resValue "string", "app_name", "BlogDemo"
            buildConfigField "String", "SERVER_URL", "\"https://fantasy.com\""
        }
        profession { // 专业版
            applicationId "com.fantasy.blogdemo.pro"
            resValue "string", "app_name", "BlogDemoPro"
            buildConfigField "String", "SERVER_URL", "\"https://fantasy.pro.com\""
        }
    }
}

温馨提示:在这里已经配置了 app_name,那 strings.xml 文件中的 app_name 就要删了,不然会报错。

接下来来实现这一需求:简易版APP在欢迎页使用Android自带的Dialog来提示内容,专业版APP在欢迎页使用第三方UI库 QMUI 的Dialog来提示内容。

首先在 app 模块的 build.gradle 中,添加以下依赖:

professionImplementation 'com.qmuiteam:qmui:1.2.0'

一般我们添加依赖使用 implementation 或 api 就可以了,但是为了避免影响到简易版APP,这就要求只有在编译专业版APP的时候,才能依赖 QMUI,所以这里才在前面加了 profession,这个是专业版APP的差异化打包的风味标识,这样做就可以满足需求了。如果是简易版的话,我们就用 simple 这个。

因为两个APP的欢迎页有点不一样,所以我们要做两个欢迎页。首先我们要在 app模块的 src 里创建两个文件夹 simple 和 profession。

如上图所示,main 文件中的代码和资源是公共的,即简易版和专业版都有的功能。simple 文件夹就是放简易版APP特有的文件,而profession 文件夹就是放专业版APP特有的文件。

先来讲讲专业版APP,在java文件夹中,我们在 com.fantasy.blogdemo.main 目录下建立 WelcomeActivity.java,详细代码如下:

package com.fantasy.blogdemo.main;

import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;

import com.fantasy.blogdemo.BuildConfig;
import com.fantasy.blogdemo.R;
import com.fantasy.blogdemo.base.BaseActivity;
import com.qmuiteam.qmui.widget.dialog.QMUIDialog;
import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction;

/**
 * 主界面(专业版)
 * <pre>
 *     author  : Fantasy
 *     version : 1.0, 2020-05-30
 *     since   : 1.0, 2020-05-30
 * </pre>
 */
public class WelcomeActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 解决Android应用Launcher重复启动的问题
        if (!isTaskRoot() && getIntent() != null) {
            String action = getIntent().getAction();
            if (getIntent().hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) {
                finish();
                return;
            }
        }
        setContentView(R.layout.activity_welcome);

        // 全屏,透明状态栏
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            View decorView = getWindow().getDecorView();
            int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
            decorView.setSystemUiVisibility(option);
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        }

        new QMUIDialog.MessageDialogBuilder(this)
                .setMessage("服务器地址为:" + BuildConfig.SERVER_URL)
                .addAction(R.string.btn_confirm, new QMUIDialogAction.ActionListener() {
                    @Override
                    public void onClick(QMUIDialog dialog, int index) {
                        dialog.dismiss();
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                MainActivity.actionStart(WelcomeActivity.this);
                                finish();
                            }
                        }, 1500);
                    }
                })
                .show();
    }
}

因为专业版会单独使用 QMUI,又因为 QMUI 需要设置 theme,所以我们在 profession 的 res 文件夹中,加入values/styles.xml文件,在里面配置 AppTheme 的style,而 main 里 values/styles.xml 文件中的 AppTheme style 就要移除。profession 的 AppTheme 具体如下:

<resources>

    <!-- profession 专业版的主题 -->
    <style name="AppTheme" parent="QMUI.Compat.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

接下来就是简易版APP,相同的,在 simple 文件夹中,我们在 java/com.fantasy.blogdemo.main 目录下建立 WelcomeActivity.java,在 res/values 文件夹中加入styles.xml。具体代码如下:

package com.fantasy.blogdemo.main;

import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Toast;

import com.fantasy.blogdemo.BuildConfig;
import com.fantasy.blogdemo.R;
import com.fantasy.blogdemo.base.BaseActivity;

/**
 * 主界面(简易版)
 * <pre>
 *     author  : Fantasy
 *     version : 1.1, 2020-05-30
 *     since   : 1.0, 2019-06-05
 * </pre>
 */
public class WelcomeActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 解决Android应用Launcher重复启动的问题
        if (!isTaskRoot() && getIntent() != null) {
            String action = getIntent().getAction();
            if (getIntent().hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) {
                finish();
                return;
            }
        }
        setContentView(R.layout.activity_welcome);

        // 全屏,透明状态栏
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            View decorView = getWindow().getDecorView();
            int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
            decorView.setSystemUiVisibility(option);
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        }

        // 因为简易版没有依赖 QMUI,所以无法使用 QMUIDialog
        Toast.makeText(this, "服务器地址为:" + BuildConfig.SERVER_URL, Toast.LENGTH_LONG).show();
//        new QMUIDialog.MessageDialogBuilder(this)
//                .setMessage("服务器地址为:" + BuildConfig.SERVER_URL)
//                .addAction(R.string.btn_confirm, new QMUIDialogAction.ActionListener() {
//                    @Override
//                    public void onClick(QMUIDialog dialog, int index) {
//                        dialog.dismiss();
//                    }
//                })
//                .show();

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                MainActivity.actionStart(WelcomeActivity.this);
                finish();
            }
        }, 1500);
    }
}
<resources>

    <!-- simple 简易版的主题 -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

最后我们在 main 目录里的 AndroidManifest.xml 中,加入简易版APP和专业版APP共有的配置:

<activity android:name=".main.WelcomeActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

因为现在有2套产品风味,那我们在平常开发中,要如何调测呢?其实很简单,我们只要在 build variant 进行切换就可以了。

为了能够区分两个不同APP的安装包,我们可以在 app 模块的 build.gradle 中的 android 闭包里面增加下列配置:

android {
    // 自定义安装包的名称,这里使用了 productFlavors 的名称,这样简易版和专业版的安装包也就可以区分出来了
    applicationVariants.all { variant ->
        variant.outputs.all {
            outputFileName = "BLOG_V${variant.versionName}_${variant.versionCode}_${getTime()}" +
                    "_${variant.productFlavors[0].name}.apk"
        }
    }
}

打包的时候,我们只要选择两个产品风味各自的release就行了。

2.4 演示

把打好的两个APP安装到手机里面,安装好后,可以看到手机桌面会有两个APP(如下图所示),其中 BlogDemo 就是简易版APP,而 BlogDemoPro 就是专业版APP。

分别打开一下,各自的欢迎页效果如下所示:

简易版APP
专业版APP

以上所演示的功能的相关代码已提交到 BlogDemo,欢迎查阅!

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
以下是使用蓝湖切图适配不同机型的一些代码实现方式: 1. 使用不同分辨率的资源文件 在res目录下新建drawable、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi等子目录,分别存放不同分辨率的图片资源文件。在布局文件或代码中引用图片时,系统会自动选择合适分辨率的资源文件进行加载。示例代码如下: ``` <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/my_image" /> ``` 2. 使用dp作为尺寸单位 在布局文件中使用dp作为尺寸单位,可以保证在不同分辨率的设备上显示效果基本一致。示例代码如下: ``` <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16dp" android:text="Hello World!" /> ``` 3. 使用代码动态设置布局参数 在代码中可以使用代码动态设置布局参数,根据不同机型的屏幕尺寸和分辨率进行适配。示例代码如下: ``` // 获取屏幕宽度和高度 DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int screenWidth = displayMetrics.widthPixels; int screenHeight = displayMetrics.heightPixels; // 动态设置View的宽度和高度 View view = findViewById(R.id.my_view); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); layoutParams.width = screenWidth / 2; layoutParams.height = screenHeight / 3; view.setLayoutParams(layoutParams); ``` 4. 使用代码动态加载不同分辨率的图片 在代码中可以使用代码动态加载不同分辨率的图片,根据不同机型的屏幕分辨率进行适配。示例代码如下: ``` // 获取屏幕密度 float density = getResources().getDisplayMetrics().density; // 动态加载不同分辨率的图片 if (density <= 1.0f) { imageView.setImageResource(R.drawable.my_image_mdpi); } else if (density <= 1.5f) { imageView.setImageResource(R.drawable.my_image_hdpi); } else if (density <= 2.0f) { imageView.setImageResource(R.drawable.my_image_xhdpi); } else { imageView.setImageResource(R.drawable.my_image_xxhdpi); } ``` 以上是一些常用的适配代码实现方式,根据实际需求和机型适配情况,可以进行相应的调整和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值