likes
comments
collection
share

Flutter丐版安卓多渠道打包及加固实践

作者站长头像
站长
· 阅读数 17

  此方法全程围绕360加固宝来实现。

  适用对象:经费不足的又需要对apk进行一般加固的,然后需要多渠道打包而且渠道不多的。 

  为什么说丐版呢,因为操作起来还是手动的,然后借助了360加固宝来免费加固并打出多个渠道包。一开始的时候,写了一个傻瓜式的打包脚本,本质就是循环打出所有的渠道包,然后就是一个渠道包3分钟,10个渠道包就是30分钟,确实傻瓜。而使用360加固宝来出多渠道本质上是通过注入你的渠道参数到AndroidManifest.xml文件对应的meta-data里,理论上就是打一个包的时间。

  在上个月360加固宝更新之前,整个流程是这样的:

上传apk(手动)
加固(自动)
出多渠道包(自动)
每个渠道包重新签名(自动)

更新之后,后面两个阶段开始收费了,然后现在流程变成了这样:

上传apk(手动)
加固(自动)
出多渠道包(手动)
每个渠道包重新签名(手动)

  在更新之前是真的香。。更新之后主要的变化就是出多渠道包这一步需要手动了,但还是一键生成所有渠道包的,这个影响不大。主要影响是需要对每个渠道包一个一个手动重签名了,这个如果是渠道很多的话是不可接受的,所以上面说了对渠道很多的需求此方法不适用。

下面说一下具体的流程:

一. 用任意方法打出你的签名release包

  这里不细说了,打出一个签名的正式包xxx.apk。

二. 下载360加固助手

  官方下载地址:jiagu.360.cn/#/global/do…

三. 将正式包上传到360加固助手(上传后会自动加固)

Flutter丐版安卓多渠道打包及加固实践

  打开工具面板,选择应用加固,选择添加应用添加第一步打出的包,上传完后工具会自动加固,加固完成之后就会显示”任务完成_已加固“。可以打开输出目录查看到一个xxx_jiagu.apk,这个就是加固完的包。

四. 配置渠道参数文件

  需要根据360加固宝渠道参数配置规则来进行配置,新建一个txt文件。

Flutter丐版安卓多渠道打包及加固实践

  配置规则如下

  1. 一个渠道信息一行
  2. 第一个参数UMENG_CHANNEL是统计平台,第二个参数main是渠道名称,第三个参数是渠道值。三个参数需要用空格隔开。
  3. 360会把第一个参数统计平台作为key,第三个参数渠道值作为value注入一个meta-data标签到AndroidManifest.xml中(如下图所示)。我们可以在代码里去获取,如果获取后面会说明。第二个参数渠道名称作用是在打出来的渠道包名称后面加入这个后缀,如xxx_jiagu_main.apk。

Flutter丐版安卓多渠道打包及加固实践

  1. 如果你需要多个统计平台,那么你需要这样配置: Flutter丐版安卓多渠道打包及加固实践 第一个参数中有MY_CHANNEL和UMENG_CHANNEL两个统计平台,中间用‘|’隔开,第三个参数就需要有我的官方和官方两个渠道值了,也需要用‘|’隔开。使它们在顺序上对应就行了。这时候360就会注入两个meta-data标签,如下图所示:

Flutter丐版安卓多渠道打包及加固实践

五. 开始多渠道打包

Flutter丐版安卓多渠道打包及加固实践

  打开工具面板,选择工具包,选择渠道打包,在使用多渠道配置文件中导入第四步的配置文件,可以看到打包详情中显示“已导入当前文件内的3条配置信息”。然后在APK文件中导入刚刚加固好的包,点击生成渠道包即可立即生成,完成后可以在输出目录看到xxx_jiagu_main.apk这样的渠道包。

六. 对每个渠道包进行重签名

  渠道包必须重新签名后,才可以安装使用。这一步只能一个渠道包一个渠道包签名。(怀念免费前)

Flutter丐版安卓多渠道打包及加固实践

  打开工具面板,选择工具包,选择签名APK,签名策略选择V2就行,然后在使用指定文件签名中导入自己的签名。然后在APK文件中导入一个渠道包,拉到面板最下方点击开始签名即可。完成后可以在输出目录看到xxx_jiagu_main_sign.apk这样的重签名的渠道包。

七. 如何在代码中获取渠道参数

获取meta-data标签数据需要在原生实现获取:

在MainActivity.kt中

private val CHANNEL = "test/channel";

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine);
    registerNativeView(flutterEngine);
    handleChannelMethod(flutterEngine);
}

/**
 * 处理通道方法
 */
private fun handleChannelMethod(@NonNull flutterEngine: FlutterEngine){
    MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            CHANNEL
    ).setMethodCallHandler { call, result ->
        when (call.method) {
            "getUmengChannel" -> {
                var channel = getUmengChannel();
                if (channel == null) {
                    result.success("none");
                } else {
                    result.success(channel);
                }
            }
            else -> {
                result.notImplemented();
            }
        }
    }
}

/**
 * 获取友盟渠道
 */
private fun getUmengChannel(): String? {
    return getChannel("UMENG_CHANNEL");
}

/**
 * 获取渠道
 */
private fun getChannel(platformName: String): String? {
    try {
        var appInfo = getAppInfo();
        var metaData = appInfo?.metaData;
        var channel = metaData?.getString(platformName);
        if (!TextUtils.isEmpty(channel)) {
            return channel;
        }
    } catch (e: Exception) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 获取app信息
 */
private fun getAppInfo():ApplicationInfo?{
    var appInfo = if (Build.VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
        getAppInfoAbove33();
    } else {
        getAppInfoBelow33();
    }
    return appInfo;
}

/**
 * 获取app信息 API33以下使用
 */
private fun getAppInfoBelow33(): ApplicationInfo? {
    try {
        var info = this.packageManager.getApplicationInfo(
            this.packageName,
            PackageManager.GET_META_DATA
        );
        return info;
    } catch (e: Exception) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 获取app信息 API33及以上使用
 */
@RequiresApi(VERSION_CODES.TIRAMISU)
private fun getAppInfoAbove33(): ApplicationInfo? {
    try {
        var info = this.packageManager.getApplicationInfo(
            this.packageName, PackageManager.ApplicationInfoFlags.of(
                PackageManager.GET_META_DATA.toLong()
            )
        );
        return info;
    } catch (e: Exception) {
        e.printStackTrace();
    }
    return null;
}

在Flutter中

  static const methodChannel = MethodChannel("test/channel");
  
  ///获取友盟渠道
  Future<String> getUmengChannel() async {
    if (Platform.isAndroid) {
      try {
        final String result =
            await methodChannel.invokeMethod("getUmengChannel");
        return result;
      } catch (e) {
        return '官方';
      }
    } 
    return '官方';
  }

这样就获取到了android:name为UMENG_CHANNEL的meta-data标签的android:value即我们配置的渠道参数。

八. 后续

  等我赚钱了我要写个富版的。