apk生成多渠道的安裝包

一、前言->需求

  最近公司的項目需要試上線,安卓包會放到多個渠道進行推廣,玩家會進行下載安裝登錄,後臺爲了得到渠道包的下載使用數據,就會給每個渠道包加入了不同的渠道ID以便統計數據。那問題就來了,每出一個新版本的包,要快速生成幾十上百個渠道包,那改怎麼辦,不可能手動去改ID再去生成吧,這樣會很費時費力還容易出錯,同時文件還要命名好。所以我就花了兩天的時間開發這麼個工具解決該問題。另外使其他人方便打包,所以需要增加http功能方便其他人來使用。

二、開始->思路構想

1、使用apktool d -s xxxx.apk 拆包可獲得一個xxxx文件夾,裏面可以得到明文的AndroidMenifest.xml文件。

2、找到對應的meta-data參數用正則匹配規則進行替換掉。

3、使用apktool b xxxx -o new_xxxx.apk 生成未簽名的apk包,注意這個包還是不能安裝在手機上的,需要簽名才行。

4、使用jarsigner -keystore  -keypass istormkj -storepass istormkj -signedjar new_xxxx_singed.apk new_xxxx.apk alise 

5、步驟完成。

三、選擇的語言與開發

因爲時間比較急,所以就直接選擇了Java語言進行開發,選擇Java的原因是開發快,工具類庫比較多,網上資料也相當的豐富,好了,開始編碼。

先準備一個配置文件,文件包括:啓動http端口,渠道包的版本號、應用ID、應用子ID、主渠道號、子渠道號,登錄平臺等,參數根據自己決定。如下圖

複製代碼

<configuration>
    <option name="httpPort" value="8089"/>
    <option name="version" value="1.0.8"/>
    <option name="app_id" value="1039"/>
    <option name="appchi_id" value="10139"/>
    <option name="channel_id" value="1000124"/>
    <option name="channelchi_ids" value="10002102,10002106,10002108"/>
    <option name="login_env" value="test_113"/>
</configuration>

複製代碼

上面的參數key命名建議與AndriodMenifest.xml文件中的Meta-data一致,方便寫代碼進行修改,我的AndriodMenifest.xml部分內容如下:

        <meta-data android:name="app_id" android:value="1039"/>
        <meta-data android:name="appchi_id" android:value="10139"/>
        <meta-data android:name="channel_id" android:value="1000124"/>
        <meta-data android:name="channelchi_id" android:value="10002106"/>
        <meta-data android:name="login_env" android:value="cg_xz_wan_001"/>

啓動服務後,通過http:127.0.0.1:8089/ 可以看到渠道方面的配置參數,如下,這裏是可以進行打包(渠道包)和設置渠道參數的操作

1

2

3

4

5

6

7

============>打多渠道服務器<============

一、使用方法:

1、查看配置:http://192.168.2.160:8089/

2、開始打包:http://192.168.2.160:8089/?a=b

3、設置渠道:http://192.168.2.160:8089/?a=c&key=value&...

-------------------------------------------------------------------------------------------------------------

{"appchi_id":"10139","httpPort":"8089","channelchi_ids":"10002102,10002106,10002108","version":"1.0.8","app_id":"1039","channel_id":"1000124","login_env":"test_113"}

請求 http:127.0.0.1:8089/?a=b 會進行打包 ,道具是進行解包,下面的代碼是一個解包的類。

複製代碼

package gateway;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;

import config.FileSystem;

/**
 * 
 * @author hzd
 * 
 */
public class DecompressionApk extends TimerTask {
    
    private String serverDir;
    
    public DecompressionApk(String serverDir)
    {
        this.serverDir = serverDir;
    }

    @Override
    public void run() {
        
        String[] files = FileSystem.ls(serverDir);
        for(int i = 0; i < files.length; ++i)
        {
            String file = files[i];
            if(file.endsWith("-debug.apk") || file.endsWith("-release.apk") ) //
            {
                FileSystem.delete(serverDir + "/" + file.substring(0,file.lastIndexOf(".")));    
                String cmd = serverDir + "/apktool.bat d -s " + serverDir + "/" + file;
                Process p = null;
                try {
                    p = Runtime.getRuntime().exec(cmd);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                BufferedReader in = new BufferedReader(new InputStreamReader(
                        p.getInputStream()));
                String s = "";
                String line = null;
                try {
                    while ((line = in.readLine()) != null) {
                        s += line + "\n";
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                System.out.println("cmd:========\n" + s);
                new Timer().schedule(new MakeChannelsAndUnsigneApk(serverDir, file),1000);
                break;
            }
        }
        
        System.out.println("========解壓Apk完成=============");
    }

}

複製代碼

解壓完後,進行生成渠道用的AndroidMenifest.xml文件,把每個文件放到渠道號爲名的文件夾中,然後現進行打未簽名的apk名。

代碼如下:

複製代碼

package gateway;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import config.Config;
import config.DateTime;
import config.FileSystem;

/**
 * 
 * @author hzd
 * 
 */
public class MakeChannelsAndUnsigneApk extends TimerTask {
    
    private String serverDir;
    private String file;
    
    public MakeChannelsAndUnsigneApk(String serverDir,String file)
    {
        this.serverDir = serverDir;
        this.file = file;
    }
    
    @Override
    public void run() {
        
        FileSystem.delete(serverDir + "/AllChannels");
        FileSystem.delete(serverDir + "/UnSignePackages");
        
        String[] files = FileSystem.ls(serverDir + "/SignedPackages/");
        for(int i = 0; i < files.length; ++i)
        {
            String file = files[i];
            if(file.endsWith(".apk"))
            {
                FileSystem.delete(serverDir + "/SignedPackages/" + file);
            }
        }
        
        String version = Config.getString("version").replaceAll("\\.","");
        String app_id = Config.getString("app_id");
        String appchi_id = Config.getString("appchi_id");
        String channel_id = Config.getString("channel_id");
        String channelchi_ids = Config.getString("channelchi_ids");    
        String[] channelChiIds = channelchi_ids.split(",");
        
        String xmlFile = serverDir + "/" + file.substring(0,file.lastIndexOf("."))+"/AndroidManifest.xml";
        String xmlStr = FileSystem.read(xmlFile);
        String packname = "com.istorm.hllad";
        Pattern pattern = Pattern.compile("com.istorm.\\w+ad");
        Matcher m = pattern.matcher(xmlStr);
        while(m.find()) {
            packname = m.group();
            break;
        }
        
        xmlStr = xmlStr.replaceFirst("android:name=\"app_id\" android:value=\"\\d{4,8}\"", "android:name=\"app_id\" android:value=\""+app_id+"\"");
        xmlStr = xmlStr.replaceFirst("android:name=\"appchi_id\" android:value=\"\\d{4,8}\"", "android:name=\"appchi_id\" android:value=\""+appchi_id+"\"");
        xmlStr = xmlStr.replaceFirst("android:name=\"channel_id\" android:value=\"\\d{4,8}\"", "android:name=\"channel_id\" android:value=\""+channel_id+"\"");
        
        for (int i = 0; i < channelChiIds.length; i++) {
            String channelchi_id = channelChiIds[i];
            String newXmlStr = xmlStr.replaceFirst("android:name=\"channelchi_id\" android:value=\"\\d{8}\"", "android:name=\"channelchi_id\" android:value=\""+channelchi_id+"\"");
            String newXmlFileName = serverDir + "/AllChannels/"+ channelchi_id +"/AndroidManifest.xml";
            FileSystem.write(newXmlFileName, newXmlStr, false);
        }
        
        HttpRequestHandler.pack_cur_counts = 0;
        HttpRequestHandler.pack_all_counts = channelChiIds.length;
        
        // 開啓定時去打第一個包
        for(int i = 0; i < channelChiIds.length; i++)
        {
            String channelchi_id = channelChiIds[i];
            String subFileName = file.substring(0, file.lastIndexOf("."));
            String newXmlFileName = serverDir + "/AllChannels/" + channelchi_id
                    + "/AndroidManifest.xml";
            String dstXmlFileName = serverDir + "/"
                    + file.substring(0, file.lastIndexOf("."))
                    + "/AndroidManifest.xml";
            FileSystem
                    .write(dstXmlFileName, FileSystem.read(newXmlFileName), false);
            String outFileName = packname.substring(packname.lastIndexOf(".") + 1,
                    packname.length());
            outFileName = outFileName.substring(0, 3);
            if (subFileName.indexOf("debug") > -1) {
                outFileName += "_d_";
            } else {
                outFileName += "_r_";
            }

            outFileName += channelchi_id + "_" + version + "_"
                    + DateTime.date("MMddHHmm") + ".apk";
            String cmd = serverDir + "/apktool.bat b " + serverDir + "/"
                    + file.substring(0, file.lastIndexOf(".")) + " -o " + serverDir
                    + "/UnSignePackages/" + outFileName;
            String s = "";
            try {
                Process p = Runtime.getRuntime().exec(cmd);
                BufferedReader in = new BufferedReader(new InputStreamReader(
                        p.getInputStream()));
                String line = null;
                while ((line = in.readLine()) != null) {
                    s += line + "\n";
                }
            } catch (IOException e) {
                e.printStackTrace();
            }    
            System.out.println("cmd==" + s);
            System.out.println("===打未簽名包:" + outFileName + "..............ok!");
            new Timer().schedule(new MakeSingedApk(serverDir, outFileName),1000);
        }
    
    }

}

複製代碼

最後就是要進行籤包了,相關的代碼如下:

複製代碼

package gateway;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;

import config.FileSystem;

/**
 * 
 * @author hzd
 * 
 */
public class DecompressionApk extends TimerTask {
    
    private String serverDir;
    
    public DecompressionApk(String serverDir)
    {
        this.serverDir = serverDir;
    }

    @Override
    public void run() {
        
        String[] files = FileSystem.ls(serverDir);
        for(int i = 0; i < files.length; ++i)
        {
            String file = files[i];
            if(file.endsWith("-debug.apk") || file.endsWith("-release.apk") ) //
            {
                FileSystem.delete(serverDir + "/" + file.substring(0,file.lastIndexOf(".")));    
                String cmd = serverDir + "/apktool.bat d -s " + serverDir + "/" + file;
                Process p = null;
                try {
                    p = Runtime.getRuntime().exec(cmd);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                BufferedReader in = new BufferedReader(new InputStreamReader(
                        p.getInputStream()));
                String s = "";
                String line = null;
                try {
                    while ((line = in.readLine()) != null) {
                        s += line + "\n";
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                System.out.println("cmd:========\n" + s);
                new Timer().schedule(new MakeChannelsAndUnsigneApk(serverDir, file),1000);
                break;
            }
        }
        
        System.out.println("========解壓Apk完成=============");
    }

}

複製代碼

四、演示例子

https://wenwen.sogou.com/question/q651246773146682189.htm?ch=ww.grzx.wdtwques

https://wenwen.sogou.com/question/q651245665481327382.htm?ch=ww.grzx.wdtwques

https://wenwen.sogou.com/question/q651245534694540046.htm?ch=ww.grzx.wdtwques

https://wenwen.sogou.com/question/q651244803585409773.htm?ch=ww.grzx.wdtwques

https://wenwen.sogou.com/question/q651244590506377950.htm?ch=ww.grzx.wdtwques

https://wenwen.sogou.com/question/q651171207827620869.htm?ch=ww.grzx.wdtwques

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章