码迷,mamicode.com
首页 > 移动开发 > 详细

Android 多渠道打包

时间:2015-09-16 16:10:52      阅读:327      评论:0      收藏:0      [点我收藏+]

标签:

     现在Android多渠道打包普遍使用的是gradle设置productFlavor方式,通过gradle aR,可以执行一个命令,打出多个包,但是这种方式每次都要走一遍打包流程,而目前很多包仅仅是渠道号不一致,并不需要重新在走一遍编译,打包流程。

     看了美团的解决方案,他们利用了签名的漏洞,META-INF目录内添加空文件,可以不用重新签名应用,本文介绍了一种用户执行过gradle aR命令,自动运行渠道包生成脚本,打多个渠道包的方式。想要入门gradle脚本,请查看邓凡平大神的博客文章:http://blog.csdn.net/innost/article/details/48228651 。

     以下是打包脚本:

apply plugin: ‘com.android.application‘

def versionNameString="1.0"
def versionCodeInt=1
def appName="打包测试"   //你的应用的名称

def  releaseApk=‘app/build/outputs/apk/app-release.apk‘

def packageChannel(String versionName,String appName,String releaseApk){
    try {
        def stdout = new ByteArrayOutputStream()
        exec {
            //执行Python脚本
            commandLine ‘python‘,rootProject.getRootDir().getAbsolutePath()+"/app/mulit_channel.py",versionName,appName,releaseApk
            standardOutput = stdout
        }
        return stdout.toString().trim()
    }
    catch (ignored) {
        return "UnKnown";
    }
}


android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.ndktest"
        minSdkVersion 14
        targetSdkVersion 22
        versionCode versionCodeInt
        versionName versionNameString

    }
    signingConfigs {
        debug {
            // No debug config
        }

        release {
            storeFile file("../keystore/netstars.keystore")
            storePassword "123456"
            keyAlias "netstars.keystore"
            keyPassword "123456"
        }
    }
    buildTypes {
        release {
            buildConfigField "boolean", "LOG_DEBUG", "false"
            minifyEnabled true
            zipAlignEnabled true
            // 移除无用的resource文件
            shrinkResources true
            signingConfig signingConfigs.release

            proguardFiles getDefaultProguardFile(‘proguard-android.txt‘), ‘proguard-rules.pro‘
        }
        debug {
            minifyEnabled false
            debuggable true
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = [‘libs‘]
        }
    }

    project.afterEvaluate {
        //在Release执行以后
        tasks.getByName("assembleRelease"){
            it.doLast{
                def rApk=new File(releaseApk);
                if(rApk.exists()){
                    packageChannel(versionNameString,appName,rApk.absolutePath)
                }
            }
        }
    }

}

dependencies {
    compile fileTree(dir: ‘libs‘, include: [‘*.jar‘])
    compile ‘com.android.support:appcompat-v7:22.2.0‘
}

  Python脚本:

#!/usr/bin/python
# coding=utf-8
import zipfile
import shutil
import os
import datetime
import sys

# 空文件 便于写入此空文件到apk包中作为channel文件
src_empty_file = ‘empty.txt‘
# 创建一个空文件(不存在则创建)
f = open(src_empty_file, ‘w‘)
f.close()

# 获取渠道列表
channel_file = ‘channel.txt‘
f = open(channel_file)
lines = f.readlines()
f.close()

src_apk=sys.argv[3]

# file name (with extension)
src_apk_file_name = os.path.basename(src_apk)
print(src_apk_file_name)

# 分割文件名与后缀
temp_list = os.path.splitext(src_apk_file_name)
# name without extension
src_apk_name = temp_list[0]
# 后缀名,包含.   例如: ".apk "
src_apk_extension = temp_list[1]

# 创建生成目录,与文件名相关
output_dir = ‘../output‘ + ‘/‘
# 目录不存在则创建
if not os.path.exists(output_dir):
    os.mkdir(output_dir)

# 遍历渠道号并创建对应渠道号的apk文件
for line in lines:
    # 获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下
    target_channel = line.strip()

    #获取日期
    now = datetime.datetime.now()
    nowTime=now.strftime(‘%Y-%m-%d‘)

    # 拼接对应渠道号的apk
    length=len(sys.argv)
    if length>1 :
        target_apk = output_dir +sys.argv[2]+"v"+sys.argv[1]+"_"+nowTime+ "_" + target_channel + src_apk_extension
    else:
        target_apk = output_dir +src_apk_name + "_" + target_channel + src_apk_extension

    # 拷贝建立新apk
    shutil.copy(src_apk,  target_apk)
    # zip获取新建立的apk文件
    zipped = zipfile.ZipFile(target_apk, ‘a‘, zipfile.ZIP_DEFLATED)
    # 初始化渠道信息
    empty_channel_file = "META-INF/channel_{channel}".format(channel = target_channel)
    # 写入渠道信息
    zipped.write(src_empty_file, empty_channel_file)
    # 关闭zip流
    zipped.close()

  1.获取到渠道号:

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.preference.PreferenceManager;
import android.text.TextUtils;

import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

 /***
 *https://github.com/GavinCT/AndroidMultiChannelBuildTool
 ***/
public class ChannelUtil {
   
   private static final String CHANNEL_KEY = "channel";
   private static final String CHANNEL_VERSION_KEY = "channel_version";
   private static String mChannel;
   /**
    * 返回市场。  如果获取失败返回""
    * @param context
    * @return
    */
   public static String getChannel(Context context){
      return getChannel(context, "");
   }
   /**
    * 返回市场。  如果获取失败返回defaultChannel
    * @param context
    * @param defaultChannel
    * @return
    */
   public static String getChannel(Context context, String defaultChannel) {
      //内存中获取
      if(!TextUtils.isEmpty(mChannel)){
         return mChannel;
      }
      //sp中获取
      mChannel = getChannelBySharedPreferences(context);
      if(!TextUtils.isEmpty(mChannel)){
         return mChannel;
      }
      //从apk中获取
      mChannel = getChannelFromApk(context, CHANNEL_KEY);
      if(!TextUtils.isEmpty(mChannel)){
         //保存sp中备用
         saveChannelBySharedPreferences(context, mChannel);
         return mChannel;
      }
      //全部获取失败
      return defaultChannel;
    }
   /**
    * 从apk中获取版本信息
    * @param context
    * @param channelKey
    * @return
    */
   private static String getChannelFromApk(Context context, String channelKey) {
      //从apk包中获取
        ApplicationInfo appinfo = context.getApplicationInfo();
        String sourceDir = appinfo.sourceDir;
        //默认放在meta-inf/里, 所以需要再拼接一下
        String key = "META-INF/" + channelKey;
        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(key)) {
                    ret = entryName;
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (zipfile != null) {
                try {
                    zipfile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        String[] split = ret.split("_");
        String channel = "";
        if (split != null && split.length >= 2) {
           channel = ret.substring(split[0].length() + 1);
        }
        return channel;
   }
   /**
    * 本地保存channel & 对应版本号
    * @param context
    * @param channel
    */
   private static void saveChannelBySharedPreferences(Context context, String channel){
      SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
      Editor editor = sp.edit();
      editor.putString(CHANNEL_KEY, channel);
      editor.putInt(CHANNEL_VERSION_KEY, getVersionCode(context));
      editor.commit();
   }
   /**
    * 从sp中获取channel
    * @param context
    * @return 为空表示获取异常、sp中的值已经失效、sp中没有此值
    */
   private static String getChannelBySharedPreferences(Context context){
      SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
      int currentVersionCode = getVersionCode(context);
      if(currentVersionCode == -1){
         //获取错误
         return "";
      }
      int versionCodeSaved = sp.getInt(CHANNEL_VERSION_KEY, -1);
      if(versionCodeSaved == -1){
         //本地没有存储的channel对应的版本号
         //第一次使用  或者 原先存储版本号异常
         return "";
      }
      if(currentVersionCode != versionCodeSaved){
         return "";
      }
      return sp.getString(CHANNEL_KEY, "");
   }
   /**
    * 从包信息中获取版本号
    * @param context
    * @return
    */
   private static int getVersionCode(Context context){
      try{
         return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
      }catch(NameNotFoundException e) {
         e.printStackTrace();
      }
      return -1;
   }
}

  友盟SDK中提供了通过代码设置渠道号的功能,结合上述打包脚本和获取脚本信息代码,相信多渠道打包问题基本可以得到解决了。

项目Demo:http://git.oschina.net/fengcunhan/AndroidMulitChannel

Android 多渠道打包

标签:

原文地址:http://my.oschina.net/fengcunhan/blog/506852

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!