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

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

以下是打包脚本:

applyplugin:'com.android.application'defversionNameString="1.0"defversionCodeInt=1defappName="打包测试"//你的应用的名称defreleaseApk='app/build/outputs/apk/app-release.apk'defpackageChannel(StringversionName,StringappName,StringreleaseApk){try{defstdout=newByteArrayOutputStream()exec{//执行Python脚本commandLine'python',rootProject.getRootDir().getAbsolutePath()+"/app/mulit_channel.py",versionName,appName,releaseApkstandardOutput=stdout}returnstdout.toString().trim()}catch(ignored){return"UnKnown";}}android{compileSdkVersion22buildToolsVersion"22.0.1"defaultConfig{applicationId"com.ndktest"minSdkVersion14targetSdkVersion22versionCodeversionCodeIntversionNameversionNameString}signingConfigs{debug{//Nodebugconfig}release{storeFilefile("../keystore/netstars.keystore")storePassword"123456"keyAlias"netstars.keystore"keyPassword"123456"}}buildTypes{release{buildConfigField"boolean","LOG_DEBUG","false"minifyEnabledtruezipAlignEnabledtrue//移除无用的resource文件shrinkResourcestruesigningConfigsigningConfigs.releaseproguardFilesgetDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'}debug{minifyEnabledfalsedebuggabletrue}}sourceSets{main{jniLibs.srcDirs=['libs']}}project.afterEvaluate{//在Release执行以后tasks.getByName("assembleRelease"){it.doLast{defrApk=newFile(releaseApk);if(rApk.exists()){packageChannel(versionNameString,appName,rApk.absolutePath)}}}}}dependencies{compilefileTree(dir:'libs',include:['*.jar'])compile'com.android.support:appcompat-v7:22.2.0'}

Python脚本:

#!/usr/bin/python#coding=utf-8importzipfileimportshutilimportosimportdatetimeimportsys#空文件便于写入此空文件到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]#filename(withextension)src_apk_file_name=os.path.basename(src_apk)print(src_apk_file_name)#分割文件名与后缀temp_list=os.path.splitext(src_apk_file_name)#namewithoutextensionsrc_apk_name=temp_list[0]#后缀名,包含.例如:".apk"src_apk_extension=temp_list[1]#创建生成目录,与文件名相关output_dir='../output'+'/'#目录不存在则创建ifnotos.path.exists(output_dir):os.mkdir(output_dir)#遍历渠道号并创建对应渠道号的apk文件forlineinlines:#获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下target_channel=line.strip()#获取日期now=datetime.datetime.now()nowTime=now.strftime('%Y-%m-%d')#拼接对应渠道号的apklength=len(sys.argv)iflength>1:target_apk=output_dir+sys.argv[2]+"v"+sys.argv[1]+"_"+nowTime+"_"+target_channel+src_apk_extensionelse:target_apk=output_dir+src_apk_name+"_"+target_channel+src_apk_extension#拷贝建立新apkshutil.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.获取到渠道号:

importandroid.content.Context;importandroid.content.SharedPreferences;importandroid.content.SharedPreferences.Editor;importandroid.content.pm.ApplicationInfo;importandroid.content.pm.PackageManager.NameNotFoundException;importandroid.preference.PreferenceManager;importandroid.text.TextUtils;importjava.io.IOException;importjava.util.Enumeration;importjava.util.zip.ZipEntry;importjava.util.zip.ZipFile;/****https://github.com/GavinCT/AndroidMultiChannelBuildTool***/publicclassChannelUtil{privatestaticfinalStringCHANNEL_KEY="channel";privatestaticfinalStringCHANNEL_VERSION_KEY="channel_version";privatestaticStringmChannel;/***返回市场。如果获取失败返回""*@paramcontext*@return*/publicstaticStringgetChannel(Contextcontext){returngetChannel(context,"");}/***返回市场。如果获取失败返回defaultChannel*@paramcontext*@paramdefaultChannel*@return*/publicstaticStringgetChannel(Contextcontext,StringdefaultChannel){//内存中获取if(!TextUtils.isEmpty(mChannel)){returnmChannel;}//sp中获取mChannel=getChannelBySharedPreferences(context);if(!TextUtils.isEmpty(mChannel)){returnmChannel;}//从apk中获取mChannel=getChannelFromApk(context,CHANNEL_KEY);if(!TextUtils.isEmpty(mChannel)){//保存sp中备用saveChannelBySharedPreferences(context,mChannel);returnmChannel;}//全部获取失败returndefaultChannel;}/***从apk中获取版本信息*@paramcontext*@paramchannelKey*@return*/privatestaticStringgetChannelFromApk(Contextcontext,StringchannelKey){//从apk包中获取ApplicationInfoappinfo=context.getApplicationInfo();StringsourceDir=appinfo.sourceDir;//默认放在meta-inf/里,所以需要再拼接一下Stringkey="META-INF/"+channelKey;Stringret="";ZipFilezipfile=null;try{zipfile=newZipFile(sourceDir);Enumeration<?>entries=zipfile.entries();while(entries.hasMoreElements()){ZipEntryentry=((ZipEntry)entries.nextElement());StringentryName=entry.getName();if(entryName.startsWith(key)){ret=entryName;break;}}}catch(IOExceptione){e.printStackTrace();}finally{if(zipfile!=null){try{zipfile.close();}catch(IOExceptione){e.printStackTrace();}}}String[]split=ret.split("_");Stringchannel="";if(split!=null&&split.length>=2){channel=ret.substring(split[0].length()+1);}returnchannel;}/***本地保存channel&对应版本号*@paramcontext*@paramchannel*/privatestaticvoidsaveChannelBySharedPreferences(Contextcontext,Stringchannel){SharedPreferencessp=PreferenceManager.getDefaultSharedPreferences(context);Editoreditor=sp.edit();editor.putString(CHANNEL_KEY,channel);editor.putInt(CHANNEL_VERSION_KEY,getVersionCode(context));editor.commit();}/***从sp中获取channel*@paramcontext*@return为空表示获取异常、sp中的值已经失效、sp中没有此值*/privatestaticStringgetChannelBySharedPreferences(Contextcontext){SharedPreferencessp=PreferenceManager.getDefaultSharedPreferences(context);intcurrentVersionCode=getVersionCode(context);if(currentVersionCode==-1){//获取错误return"";}intversionCodeSaved=sp.getInt(CHANNEL_VERSION_KEY,-1);if(versionCodeSaved==-1){//本地没有存储的channel对应的版本号//第一次使用或者原先存储版本号异常return"";}if(currentVersionCode!=versionCodeSaved){return"";}returnsp.getString(CHANNEL_KEY,"");}/***从包信息中获取版本号*@paramcontext*@return*/privatestaticintgetVersionCode(Contextcontext){try{returncontext.getPackageManager().getPackageInfo(context.getPackageName(),0).versionCode;}catch(NameNotFoundExceptione){e.printStackTrace();}return-1;}}

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

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

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. Android(安卓)Studio NDK项目移植问题 re-run cmake with a diff
  6. Djinni_初试Android
  7. gdb+gdbserver调试android的so文件
  8. ReactNative中把js编译成bundle后,js引用的图片的去向
  9. Android世界第一个activity启动过程

随机推荐

  1. 银联手机支付 手机客户端有几个?
  2. android 单击 切换图片 --- 注意图片大小
  3. 使用android 隐藏命令
  4. 深入解析RxJava源码(一)Observable对象的构
  5. 【Android自学笔记】为Android应用程序添
  6. Android RecyclerView网格布局的学习
  7. [置顶] Android 通过经纬度获取地理位置
  8. android设置一个图片为全屏大小
  9. 07文本处理工具和正则表达式
  10. Android 永久隐藏导航栏,系统栏的方法