多渠道打包总结

使用gradle进行多渠道打包

使用Android studio

基本多渠道打包

以友盟的为例

渠道信息一般在 AndroidManifest.xml中修改以下值:

<meta-data android:name="UMENG_CHANNEL" android:value="wandoujia" />

首先你必须在AndroidManifest.xml中的meta-data修改以下的样子:

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

其中${UMENG_CHANNEL_VALUE}中的值就是你在gradle中自定义配置的值。

build.gradle文件就利用productFlavors这样写:

productFlavors {
    wandoujia {}
    baidu {}
    c360 {}
    uc {}

    productFlavors.all { flavor ->
        flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
    }

}

其中name的值对相对应各个productFlavors的选项值,这样就达到自动替换渠道值的目的了。

这样生成apk时,选择相应的Flavors来生成指定渠道的包就可以了,而且生成的apk会自动帮你加上相应渠道的后缀,非常方便和直观。大家可以自己反编译验证。

还可以一次生成所有渠道包.

在as底栏中有个命令行工具Terminal,打开后就就在当前项目的目录下。

输入这个命令:

gradlew assembleRelease

就可以一次性生成所有的渠道包了

所有生成的apk在项目的build\outputs\apk

第一次使用命令可能需要下载gradle,请自备梯子。

进阶:适配渠道包

主要是使用productFlavors这个DSL容器进行渠道包的适配,如果对gradle不够了解,可以看看这个系列的教程http://blog.csdn.net/qinxiandiqi/article/category/2394347

看下面这部分代码:

android {
    ....

    productFlavors {
        flavor1 {
            minSdkVersion 14
        }
    }
}

上例定义了一个flavor:flavor1,并指定了应用的minSdkVersion为14(当然还可以配置更多的属性,具体可参考相关文档)。与此同时,Gradle还会为该flavor关联对应的sourceSet,默认位置为src/目录,对应到本例就是src/flavor1。

接下来,要做的就是根据具体的需求在build.gradle文件中配置flavor,并添加必要的代码和资源文件。以flavor1为例,运行gradle assembleFlavor1命令既可生成所需的适配包。

  1. 使用不同的包名
    示例如下:

    productFlavors {
        test1 {
            applicationId "com.app.test1"
        }
    }
    

    上面的代码添加了一个名为test1的flavor,并指定了应用的包名为com.app.test1,运行gradle assembleTest1命令即可生成test1适配包。

  2. 使用不同的应用名
    Gradle在构建应用时,会优先使用flavor所属dataSet中的同名资源。所以,解决思路就是在flavor的dataSet中添加同名的字符串资源,以覆盖默认的资源。下面以适配wandoujia渠道的应用名为美团团购为例进行介绍。
    首先,在build.gradle配置文件中添加如下flavor:

    android {
        productFlavors {
            wandoujia { 
            }
        }
    }
    

    上面的配置会默认src/wandoujia目录为wandoujia flavordataSet

    接下来,在src目录内创建wandoujia目录,并添加如下应用名字符串资源(src/wandoujia/res/values/appname.xml):

    <resources>
        <string name="app_name">美团团购</string>
    </resources>
    

    默认的应用名字符串资源如下(src/main/res/values/strings.xml):

    <resources>
        <string name="app_name">美团</string>
    </resources>
    

    最后,运行gradle assembleWandoujia命令即可生成应用名为美团团购的应用了。

  3. 使用第三方SDK
    某些渠道会要求客户端嵌入第三方SDK来满足特定的适配需求。比如360应用市场要求美团团购Android客户端的精品应用模块使用他们提供的SDK。问题的难点在于如何只为特定的渠道添加SDK,其他渠道不引入该SDK。使用flavor可以很好的解决这个问题,下面以为qihu360 flavor引入com.qihoo360.union.sdk:union:1.0 SDK为例进行说明:

    android {
        productFlavors {
            qihu360 {
            }
        }
    }
    ...
    dependencies {
        provided 'com.qihoo360.union.sdk:union:1.0'
        qihu360Compile 'com.qihoo360.union.sdk:union:1.0'
    }
    

    上例添加了名为qihu360的flavor,并且指定编译和运行时都依赖com.qihoo360.union.sdk:union:1.0。而其他渠道只是在构建的时候依赖该SDK,打包的时候并不会添加它。
    接下来,需要在代码中使用反射技术判断应用程序是否添加了该SDK,从而决定是否要显示360 SDK提供的精品应用。部分代码如下:

    class MyActivity extends Activity {
        private boolean useQihuSdk;
    
        @override
        public void onCreate(Bundle savedInstanceState) {
            try {
                Class.forName("com.qihoo360.union.sdk.UnionManager");
                useQihuSdk = true;
            } catch (ClassNotFoundException ignored) {
    
            }
        }
    }
    

    最后,运行gradle assembleQihu360命令即可生成包含360精品应用模块的渠道包了。

使用Ant多渠道打包

针对使用Eclipse的用户

跟着这个链接做(图文教程)http://blog.csdn.net/zhaokaiqiang1992/article/details/38086747

因为我的需求是多渠道,多项目打包,所以略微修改了以下,如果是单个项目,完全使用上面链接中的方式即可

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;

public class AntTest {
    private Project project;

    private static String sourceProjectPath;// 要打包的项目根目录
    private static String targetApkPath;// 保存打包之后的apk的根目录
    private static String signApk;// 这里的文件名必须是准确的项目名!就是Project工程的bin目录下面的apk安装包的名字
    private static String signedApkPrex;// 重命名之后的apk名称前缀(地图项目不用改)
    private static String placeHolder = "@market@";// 需要修改manifest文件的地方(占位符)

    public static void main(String args[]) {
        // 工程目录File对象
        File file = new File("..//");
        // 获得本工作空间中所有完美攻略工程
        String[] projects = file.list(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                return name.startsWith("PerfectTools")
                        && new File(dir, name).isDirectory();
            }
        });
        for (String project : projects) {
            sourceProjectPath = new File(file, project).getAbsolutePath();
            targetApkPath = "E:\\apk_release" + File.separator + project;
            File targetApkDir = new File(targetApkPath);
            if (!targetApkDir.exists()) {
                targetApkDir.mkdirs();
            }
            signApk = project + "-release.apk";
            signedApkPrex = project + "_";
            //开始打包
            startPacking();
        }
    }

    @SuppressWarnings("resource")
    protected static void startPacking() {
        long startTime = 0L;
        long endTime = 0L;
        long totalTime = 0L;
        Calendar date = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:HH:mm:ss");
        try {
            System.out.println("---------ant批量自动化打包开始----------");
            startTime = System.currentTimeMillis();
            date.setTimeInMillis(startTime);
            System.out.println("开始时间为:" + sdf.format(date.getTime()));
            BufferedReader br = new BufferedReader(new FileReader("market.txt"));
            String flag = null;
            while ((flag = br.readLine()) != null) {
                // 先修改manifest文件:读取临时文件中的@market@修改为市场标识,然后写入manifest.xml中
                String tempFilePath = sourceProjectPath + File.separator
                        + "AndroidManifest.xml.temp";
                String filePath = sourceProjectPath + File.separator
                        + "AndroidManifest.xml";
                write(filePath, read(tempFilePath, flag.trim()));
                // 执行打包命令
                AntTest mytest = new AntTest();
                mytest.init(sourceProjectPath + File.separator + "build.xml",
                        sourceProjectPath);
                mytest.runTarget("clean");
                mytest.runTarget("release");
                // 打完包后执行重命名加拷贝操作
                File file = new File(sourceProjectPath + File.separator + "bin"
                        + File.separator + signApk);// bin目录下签名的apk文件

                File renameFile = new File(targetApkPath + File.separator
                        + signedApkPrex + flag + ".apk");
                // 将打包好的apk重命名后移动到copyApkPath位置
                boolean renametag = file.renameTo(renameFile);
                System.out.println("rename------>" + renametag);
                System.out.println("file ------>" + file.getAbsolutePath());
                System.out.println("rename------>"
                        + renameFile.getAbsolutePath());
            }
            System.out.println("---------ant批量自动化打包结束----------");
            endTime = System.currentTimeMillis();
            date.setTimeInMillis(endTime);
            System.out.println("结束时间为:" + sdf.format(date.getTime()));
            totalTime = endTime - startTime;
            System.out.println("耗费时间为:" + getBeapartDate(totalTime));

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("---------ant批量自动化打包中发生异常----------");
            endTime = System.currentTimeMillis();
            date.setTimeInMillis(endTime);
            System.out.println("发生异常时间为:" + sdf.format(date.getTime()));
            totalTime = endTime - startTime;
            System.out.println("耗费时间为:" + getBeapartDate(totalTime));
        }
    }

    public void init(String _buildFile, String _baseDir) throws Exception {
        project = new Project();
        project.init();
        DefaultLogger consoleLogger = new DefaultLogger();
        consoleLogger.setErrorPrintStream(System.err);
        consoleLogger.setOutputPrintStream(System.out);
        consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
        project.addBuildListener(consoleLogger);
        if (_baseDir == null)
            _baseDir = new String(".");
        project.setBasedir(_baseDir);
        if (_buildFile == null)
            _buildFile = new String(sourceProjectPath + File.separator
                    + "build.xml");
        ProjectHelper.configureProject(project, new File(_buildFile));
    }

    public void runTarget(String _target) throws Exception {
        if (project == null)
            throw new Exception(
                    "No target can be launched because the project has not been initialized. Please call the 'init' method first !");
        if (_target == null)
            _target = project.getDefaultTarget();
        project.executeTarget(_target);
    }

    /**
     * 根据所秒数,计算相差的时间并以**时**分**秒返回
     * 
     * @param d1
     * @param d2
     * @return
     */
    public static String getBeapartDate(long m) {
        m = m / 1000;
        String beapartdate = "";
        int nDay = (int) m / (24 * 60 * 60);
        int nHour = (int) (m - nDay * 24 * 60 * 60) / (60 * 60);
        int nMinute = (int) (m - nDay * 24 * 60 * 60 - nHour * 60 * 60) / 60;
        int nSecond = (int) m - nDay * 24 * 60 * 60 - nHour * 60 * 60 - nMinute
                * 60;
        beapartdate = nDay + "天" + nHour + "小时" + nMinute + "分" + nSecond + "秒";

        return beapartdate;
    }

    public static String read(String filePath, String replaceStr) {
        BufferedReader br = null;
        String line = null;
        StringBuffer buf = new StringBuffer();

        try {
            // 根据文件路径创建缓冲输入流
            br = new BufferedReader(new FileReader(filePath));
            // 循环读取文件的每一行, 对需要修改的行进行修改, 放入缓冲对象中
            while ((line = br.readLine()) != null) {
                // 此处根据实际需要修改某些行的内容
                if (line.contains(placeHolder)) {
                    line = line.replace(placeHolder, replaceStr);
                    buf.append(line);
                } else {
                    buf.append(line);
                }
                buf.append(System.getProperty("line.separator"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    br = null;
                }
            }
        }

        return buf.toString();
    }

    /**
     * 将内容回写到文件中
     * 
     * @param filePath
     * @param content
     */
    public static void write(String filePath, String content) {
        BufferedWriter bw = null;

        try {
            // 根据文件路径创建缓冲输出流
            bw = new BufferedWriter(new FileWriter(filePath));
            // 将内容写入文件中
            bw.write(content);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    bw = null;
                }
            }
        }
    }
}

渠道信息market.txt放在java项目的根目录中,我的渠道列表如下:

zhanwai
xiaomi
oppo
lenovo
wandoujia
meizu
huawei
hisense
tencent
liqucn
wo
eoemarket
sogou
yybei
letv
yyjia
mm
anzhi

以下三个文件拷入要打包的项目中:

  • ant.properties

    key.store=D:\\kding.keystore //签名路径
    key.store.password=525b2102
    key.alias=kding.keystore
    key.alias.password=525b2102
    
  • build.xml(ant自动生成的文件,只需要修改project name=你的项目名)

    <?xml version="1.0" encoding="UTF-8"?>
    <project name="PerfectToolsOfZhslm" default="help">
        <property file="local.properties" />
        <property file="ant.properties" />
        <property environment="env" />
        <condition property="sdk.dir" value="${env.ANDROID_HOME}">
            <isset property="env.ANDROID_HOME" />
        </condition>
        <loadproperties srcFile="project.properties" />
        <fail
                message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
                unless="sdk.dir"
        />
        <import file="custom_rules.xml" optional="true" />
        <import file="${sdk.dir}/tools/ant/build.xml" />
    </project>
    
  • local.properties(sdk所在目录)

    sdk.dir=D:\\adt-bundle-windows-x86_64-20140702\\sdk
    

使用python脚本进行多渠道打包

使用脚本的打包方式速度很慢,每次打包都需要重新编译,如果需要打包的渠道很多,建议采用以下

  1. 下载安装python环境
  2. 下载python脚本 AndroidMultiChannelBuildToolhttps://github.com/GavinCT/AndroidMultiChannelBuildTool
  3. ChannelUtil.java代码集成到工程里面,在app启动时获取渠道号传送给后台(如友盟:AnalyticsConfig.setChannel(ChannelUtil.getChannel(this));
  4. PythonTool/Info/channel.txt中编辑渠道列表,以换行隔开,工程中有示例
  5. 打包apk,将apk文件复制到PythonTool目录下(与py同级),运行(直接双击.py文件或者在命令行输入 python MultiChannelBuildTool.py)py脚本即可打包完成。(生成的渠道apk包在output_** 目录下)

参考链接

Android studio 多渠道打包(超简洁版)http://xuyazhou.com/archives/461
美团Android自动化之旅—适配渠道包http://tech.meituan.com/mt-apk-adaptation.html

Android Gradle Plugin指南(五)——Build Variants(构建变种版本)http://blog.csdn.net/qinxiandiqi/article/details/37906449
美团Android自动化之旅—生成渠道包http://tech.meituan.com/mt-apk-packaging.html

Android批量打包提速 - 1分钟900个市场不是梦http://www.cnblogs.com/ct2011/p/4152323.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值