这篇文章只介绍三种打包方式:1,通过gradle配置productFlaveors来完成;2,生成APK后,通过解压apk,在META-INF里面添加空的文件(文件名标识渠道)来完成;3,通过apk末尾的一个 File comment 区域来添加信息

1,Gradle


这种方式大家都非常熟悉。之前,也写过这种方式。简单说下:

-1 AndroidManifest 声明meta-data

<meta-data
                android:name="UMENG_CHANNEL"
                android:value="${UMENG_CHANNEL}"/>

-2 gradle –productFlavors 动态替换

productFlavors {
        inner {
            manifestPlaceholders = [UMENG_CHANNEL: "c002"]
        }
        Official {
            manifestPlaceholders = [UMENG_CHANNEL: "c001"]
        }
        Yingyongbao {
            manifestPlaceholders = [UMENG_CHANNEL: "c003"]
        }
    }

-3 gradlew assembleRelease 打包所有渠道包

2,META-INF


在META-INF里面添加空文件,可以不用重新签名应用。这样,只要打包一个APK,不同渠道的应用添加不同的名称的空文件就可以了(参照美团的文章)。

-1 下面的python代码作用:循环复制apk,并添加不同的渠道空文件

import zipfile,shutil
    inputpath = 'app-debug.apk'
    channels = ["baidu","yingyongboa","offical"]


    def addChannel(channel_name):
        outputfile = "app-debug-{subffix}.apk".format(subffix=channel_name)
        shutil.copyfile(inputpath, outputfile)
        zipped = zipfile.ZipFile(outputfile, 'a') 
        empty_channel_file = "META-INF/channel_{channel}".format(channel=channel_name)
        zipped.write('empty', empty_channel_file, zipfile.ZIP_DEFLATED)
        zipped.close() 
        return;

    for i in range(0, len(channels)):
        addChannel(channels[i])

-2 添加后,META-INF下面就多出了一个带渠道名字的空的文件

Android打包systrace Android打包方式_gradle

-3 下面是java代码读取空文件的渠道名

public static String getChannel(Context context) {
            ApplicationInfo appinfo = context.getApplicationInfo();
            String sourceDir = appinfo.sourceDir;
            String ret = "";
            ZipFile zipfile = null;
            try {
                zipfile = new ZipFile(sourceDir);
                Enumeration<?> entries = zipfile.entries();
                while (entries.hasMoreElements()) {
                    ZipEntry entry = ((ZipEntry) entries.nextElement());
                    String entryName = entry.getName();
                    if (entryName.startsWith("channel")) {
                        ret = entryName;
                        break;
                    }
                    if (entryName.startsWith("META-INF/channel")) {
                        ret = entryName;
                        break;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (zipfile != null) {
                    try {
                        zipfile.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            String[] split = ret.split("_");
            if (split != null && split.length >= 2) {
                return ret.substring(split[0].length() + 1);

            } else {
                return "";
            }
    }

这样需要打几个渠道,就会复制出来几个apk,而且,速度非常快。

运行结果:

Android打包systrace Android打包方式_Android打包systrace_02

3,COMMENT添加字段


这个方式跟上面的META-INF添加文件差不多,只需要打一个APK,然后,往不同渠道应用市场的APK添加不同的comment字段就可以了。

Android 使用的 Apk 包的压缩方式是 zip,与 zip 有相同的文件结构,在 zip 文件的末尾有一个 Central Directory Record 区域,其末尾包含一个 File comment 区域,可以存放一些数据,所以 File comment 是 zip 文件一部分,如果可以正确的修改这个部分,就可以在不破坏压缩包、不用重新打包的的前提下快速的给 Apk 文件写入自己想要的数据。(原文章直接抓过来的,下面有原文章连接。)

-1 JAVA代码,写入APK comment。

public static void main(String[] args) {
        String filePath = "D:\\test\\app-release.apk";
        String comment = "baidu";
        writeApkComment(new File(filePath),comment);
    }

    public static void writeApkComment(File file, String comment) {
        ZipFile zipFile = null;
        ByteArrayOutputStream outputStream = null;
        RandomAccessFile accessFile = null;
        try {
            zipFile = new ZipFile(file);
            String zipComment = zipFile.getComment();
            if (zipComment != null) {
                return;
            }

            byte[] byteComment = comment.getBytes();
            outputStream = new ByteArrayOutputStream();

            outputStream.write(byteComment);
            outputStream.write(StreamUtils.short2Stream((short) byteComment.length));

            byte[] data = outputStream.toByteArray();

            accessFile = new RandomAccessFile(file, "rw");
            accessFile.seek(file.length() - 2);
            accessFile.write(StreamUtils.short2Stream((short) data.length));
            accessFile.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (zipFile != null) {
                    zipFile.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
                if (accessFile != null) {
                    accessFile.close();
                }
            } catch (Exception e) {

            }

        }}

-2 JAVA代码,读取comment内容

获取APK路径

public static String getPackagePath(Context context) {
        if (context != null) {
            return context.getPackageCodePath();
        }
        return null;
    }

获取 Apk 路径之后就可以读取 comment 的内容了,这里不能直接使用 ZipFile中的getComment() 方法直接获取 comment,因为这个方法是 Java7 中的方法,在 android4.4 之前是不支持 Java7 的,所以我们需要自己去读取 Apk 文件中的 comment。(原文复制)

public static String readApkComment(File file) {
        if (file == null) {
            return null;
        }
        byte[] bytes = null;
        try {
            RandomAccessFile accessFile = new RandomAccessFile(file, "r");
            long index = accessFile.length();

            bytes = new byte[2];
            index = index - bytes.length;
            accessFile.seek(index);
            accessFile.readFully(bytes);

            int contentLength = stream2Short(bytes, 0);

            bytes = new byte[contentLength];
            index = index - bytes.length;
            accessFile.seek(index);
            accessFile.readFully(bytes);

            return new String(bytes, "utf-8");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static short stream2Short(byte[] stream, int offset) {
        ByteBuffer buffer = ByteBuffer.allocate(2);
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        buffer.put(stream[offset]);
        buffer.put(stream[offset + 1]);
        return buffer.getShort(0);
    }

运行结果:

Android打包systrace Android打包方式_android_03

大写的注意:

第二,三种方式在7.0一下是没有问题的。7.0以后,这样做就无法安装了

android 7.0 引入一项新的应用签名方案 APK Signature Scheme v2,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护。在默认情况下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 会使用 APK Signature Scheme v2 和传统签名方案来签署您的应用。 —–官网

我们需要在gradle里面禁用新版签名模式

android {
    ...
    defaultConfig { ... }
    signingConfigs {
      release {
        storeFile file("myreleasekey.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
        v2SigningEnabled false
      }
    }
  }

最后测试,发现命令行,使用这个没问题。但是,手动打包的时候,还是无法在7.0的手机上安装;
希望看到的大大可以帮助下。

参考文献(嗯,大部分都是复制的,自己就实践了一下)


META-INF方式—>点击

comment方式:—>点击

推荐一个Android渠道打包工具—>