Android studio gradle 打包 那些事

希爾瓦娜斯女神發表於2015-12-08

總結了一下 目前覺得比較好用的gradle 和一些打包 經驗。放在這裡。

首先說下 渠道號 這個概念,我們經常會統計我們的api 訪問來源 是來自於那個app store,這有利於 我們針對性的推廣。也可以知道使用者的分佈情況,目前我們的做法通常是這樣的:

然後在我們的自定義application裡面 定義一個函式 來取得這個渠道號

 1 package com.example.administrator.gradletest;
 2 
 3 import android.app.Application;
 4 import android.content.Context;
 5 import android.content.pm.ApplicationInfo;
 6 import android.content.pm.PackageManager;
 7 
 8 /**
 9  * Created by Administrator on 2015/12/7.
10  */
11 public class BaseApplication extends Application {
12     @Override
13     public void onCreate() {
14         super.onCreate();
15     }
16 
17     private String getChannel(Context context) {
18         try {
19             PackageManager pm = context.getPackageManager();
20 
21             ApplicationInfo appInfo = pm.getApplicationInfo(context.getPackageName(),PackageManager.GET_META_DATA);
22             return appInfo.metaData.getString("channel");
23 
24         } catch (PackageManager.NameNotFoundException e) {
25             e.printStackTrace();
26         }
27         return "";
28     }
29 }

然後再到gradle 指令碼里面 動態更改manifest編譯時的 meta data裡面的值,這個方法是目前流傳最廣也是使用最多的方法,但是有個缺陷是速度太慢了。

隨著我們app的擴充套件,本身我們編譯一次就要很久的時間,然後你想想 至少還要分為debug和release2個版本,然後各種渠道 什麼360 pp 百度 豌豆莢 錘子 小米 華為

之類7788 起碼20個以上的渠道 再加上debug 和release 你想想 動不動 就是40幾個版本,你要是再在gradle 修改指令碼來做這件事,後果就是你編譯一次 產出

就要很久很久,因為你每次修改完manifest檔案裡的meta data 都會重新編譯一次,太浪費時間了!

所以我們採用下面的方法:

首先我們可以看一下 apk 這個檔案 解壓縮以後的結構:

 

 

看一下這個就知道了,我們可以想一下 能否把我們的渠道號 放到這個路徑下面呢?讓我們的android app程式碼來讀取這個檔案路徑下的渠道號,然後想辦法在build出來的apk裡面直接在這個路徑下

放一個渠道號不就行了麼?這樣只用build 一次,修改n次 即可得到我們需要的n個渠道包了!

首先我們來修改我們的app程式碼:

 1 package com.example.administrator.gradletest;
 2 
 3 import android.app.Application;
 4 import android.content.Context;
 5 import android.content.pm.ApplicationInfo;
 6 import android.content.pm.PackageManager;
 7 import android.util.Log;
 8 
 9 import java.io.IOException;
10 import java.util.Enumeration;
11 import java.util.zip.ZipEntry;
12 import java.util.zip.ZipFile;
13 
14 /**
15  * Created by Administrator on 2015/12/7.
16  */
17 public class BaseApplication extends Application {
18     @Override
19     public void onCreate() {
20         super.onCreate();
21     }
22 
23 
24     //這段程式碼的作用就是遍歷自己的apk包 把包裡的檔名全找到
25     //然後找到某個META-INF/mtchannel_xiaomishangcheng 這樣格式的檔案即可
26     //然後把xiaomishangcheng 這個字串提取出來返回就是得到的渠道號了
27     private  String getChannelByFile(Context context) {
28         ApplicationInfo appinfo = context.getApplicationInfo();
29         String sourceDir = appinfo.sourceDir;
30         String ret = "";
31         ZipFile zipfile = null;
32         try {
33             zipfile = new ZipFile(sourceDir);
34             Enumeration<?> entries = zipfile.entries();
35             while (entries.hasMoreElements()) {
36                 ZipEntry entry = ((ZipEntry) entries.nextElement());
37                 String entryName = entry.getName();
38                 Log.v("burning","entryName=="+entryName);
39                 if (entryName.startsWith("META-INF/mtchannel")) {
40                     ret = entryName;
41                     break;
42                 }
43             }
44         } catch (IOException e) {
45             e.printStackTrace();
46         } finally {
47             if (zipfile != null) {
48                 try {
49                     zipfile.close();
50                 } catch (IOException e) {
51                     e.printStackTrace();
52                 }
53             }
54         }
55 
56         String[] split = ret.split("_");
57         if (split != null && split.length >= 2) {
58             return ret.substring(split[0].length() + 1);
59 
60         } else {
61             return "";
62         }
63     }
64 
65 //
66 //
67 //    private String getChannel(Context context) {
68 //        try {
69 //            PackageManager pm = context.getPackageManager();
70 //
71 //            ApplicationInfo appInfo = pm.getApplicationInfo(context.getPackageName(),PackageManager.GET_META_DATA);
72 //            return appInfo.metaData.getString("channel");
73 //
74 //        } catch (PackageManager.NameNotFoundException e) {
75 //            e.printStackTrace();
76 //        }
77 //        return "";
78 //    }
79 }

好,然後我們來編寫指令碼,這個指令碼也很簡單,拿到build出來的apk以後 就開啟這個zip檔案,然後往裡面寫一個我們規定好的那個格式的檔案即可:

注意這是python程式碼,當然你如果不會python 用java也可以做。

 1 # coding=UTF-8
 2 import zipfile
 3 #這個就是你build完以後輸出的apk包的路徑
 4 apkpath = 'C:/Users/Administrator/GradleTest/app/build/outputs/apk/app-debug.apk'
 5 #這個就是隨便建立一個空檔案就可以了 一會要把這個檔案寫入apk
 6 emptypath = 'C:/Users/Administrator/GradleTest/app/build\outputs/apk/none'
 7 zipped = zipfile.ZipFile(apkpath, 'a', zipfile.ZIP_DEFLATED)
 8 #這個就是用format的方法 給上面那個空檔案重新命名一下。
 9 empty_channel_file = "META-INF/mtchannel_{channel}".format(channel='xiaomishangcheng')
10 #命名結束以後 就直接把這個空檔案寫入到apk中即可
11 zipped.write(emptypath,empty_channel_file)

你看,這樣就可以完成我們快速生成渠道包的功能了,哪怕你有1000個渠道包需要打,你也只需要編譯一次,然後稍微修改一下這個指令碼,即可自動生成1000個渠道包。

 

然後接著看下一個:

如何配置簽名資訊,大家都知道android的 簽名資源一般都是很寶貴的,一個組  可能只有1-2個 管理者能拿到那個原始的keystore。所以我們當然不能在我們的版本庫

裡面上傳這個keystore檔案 對吧,尤其做開源專案的時候更是如此,所以有下面的解決方法:

 

你看 我們可以把簽名檔案定義在外面~~

然後繼續修改build.gradle檔案,就是你要打版本的 那個project下的build.gradle檔案,別改錯了!

 1 apply plugin: 'com.android.application'
 2 
 3 android {
 4     compileSdkVersion 23
 5     buildToolsVersion "23.0.1"
 6 
 7 
 8     //配置簽名資訊
 9     signingConfigs{
10         release{
11             storeFile file(RELEASE_STORE_FILE)
12             storePassword RELEASE_STORE_PASSWORD
13             keyAlias RELEASE_KEY_ALIAS
14             keyPassword RELEASE_KEY_PASSWORD
15         }
16     }
17     defaultConfig {
18         applicationId "com.example.administrator.gradletest"
19         minSdkVersion 14
20         targetSdkVersion 23
21         versionCode 1
22         versionName "1.0"
23     }
24     buildTypes {
25         release {
26             //告訴gradle 打release版本的時候使用的簽名資訊
27             signingConfig signingConfigs.release
28             //無效資源不要打包進release版本
29             shrinkResources true
30             minifyEnabled false
31             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
32         }
33     }
34 }
35 
36 dependencies {
37     compile fileTree(dir: 'libs', include: ['*.jar'])
38     testCompile 'junit:junit:4.12'
39     compile 'com.android.support:appcompat-v7:23.1.1'
40     compile 'com.android.support:design:23.1.1'
41 }

混淆開關你們自己決定開不開~~

 

最後說一下 多渠道打包的gradle問題,前面那個 python指令碼 已經告訴了大家 如何針對渠道號這一需求 進行多渠道打包。

有興趣的人可以擴充套件一下那個指令碼,把apk的命名也可以規範一下。這是比較好的習慣。

當然了 關於多渠道打包 還有一種情況就是 比如某些應用市場 或者 某些渠道 非要讓你加入他們的sdk什麼的 才讓你釋出

那針對於這種情況,我們python 指令碼 直接修改apk 這個zip包 就搞不定了。這種情況就需要 用gradle來解決。下面給出一個例子,

以後大家可以針對性的 也進行配置!

 

我們假設 現在要發2個版本,一個版本 要用EventBus的程式碼 還有一個版本不需要。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"
    //這裡我們定義了2個渠道版本 一個是modifyDev 這個dev包就是會打包進去eventbus的
    //還有一個dev包 則是不會把eventbus打包進去
    productFlavors {
        "modifyDev" {
        }
        "dev"{

        }
    }
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            //命名規則,這個就是主要演示一下groovy閉包操作的
            //前面我們知道多渠道打包還是用python來完成速度是最快的
            output.outputFile = new File(
                    output.outputFile.parent,
                    "custom-${variant.buildType.name}-${variant.versionName}-${variant.productFlavors[0].name}.apk".toLowerCase())
            //把unaligned apk刪除掉 看著礙眼
            File unaligned=output.packageApplication.outputFile
            unaligned.delete()

        }
    }

    signingConfigs{
        release{
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
    }
    defaultConfig {
        applicationId "com.example.administrator.gradletest"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        //我們當然也可以針對debug版本進行配置 這裡就省略不進行特殊配置
        debug {

        }
        release {
            //告訴gradle 打release版本的時候使用的簽名資訊
            signingConfig signingConfigs.release
            //無效資源不要打包進release版本
            shrinkResources true
            //release版本我們把擾碼開啟
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:design:23.1.1'
    //注意看這個地方我們使用的是provided 這樣我們在寫程式碼的時候就可以引用這個包下面的所有程式碼了
    provided 'de.greenrobot:eventbus:2.4.0'
    //但實際上你看只有 modifyDev 這個Flavor 會真正的Compile進去 其他預設情況下 是不會Compile進去的
    modifyDevCompile 'de.greenrobot:eventbus:2.4.0'
}

然後看下我們activity的程式碼 就增加了一行:

1 try {
2             Class.forName("de.greenrobot.event.EventBus");
3             Log.v("MainActivity","載入到EventBus包");
4         } catch (ClassNotFoundException e) {
5             Log.v("MainActivity","找不到EventBus包");
6             e.printStackTrace();
7         }

然後我們選擇devRelease 作為我們的build varint看一下日誌輸出什麼:

 

發現 這裡是找不到eventBus這個包的。

然後我們看下modifyDevRelease 看看是什麼效果:

 

發現 這個eventBus已經打進去了~~ 

 

到這,基本上android studio gradle 和 python配合打包的一些主要部分就講解結束了。

 

相關文章