前面博文写了如何通过apktool工具实现apk二次打包,二次打包是实现了,但终究感觉一行一行的输入命令也是比较麻烦,当然我们可以使用bat脚本来简化操作。不过本人对脚本代码不太熟悉,对bat脚本的批处理和文件操作等也不太擅长,所以最终决定还是回归java,用我最擅长java代码来实现apk的二次打包,修改apk文件功能。
按照上面要点,下面来一一实现我们的小程序
实现小程序前,我们首先需要生成一个原始apk,这里我通过AS随便新建一个项目运行生成一个apk,这个apk要在运行后在Application获取里获取apk的渠道号,并打印出来。这里我的思路是在Application里获取assets目录里的渠道文件,而这个渠道文件是在实现二次打包后添加的,这样我们就可以通过apk母包然后用自己的小程序二次打包进而实现apk的多渠道打包。
先看我们apk的Application代码:
public class AppApplication extends Application {
private static final String TAG = "AppApplication";
private ApplicationInfo appInfo;
private String channelName;
private int channelNum;
@Override
public void onCreate() {
super.onCreate();
String channelInfo = getChannelInfo(this);
Log.d("channelInfo",channelInfo);
}
private String getChannelInfo(Context context){
try {
InputStream in = context.getAssets().open("channel.txt");//获取渠道文件流
int size = in.available();//获取文件内容大小
byte[] buffer = new byte[size];
in.read(buffer);
in.close();
String channelInfo = new String(buffer,"utf-8");
String[] channelInfos = channelInfo.split("\n");
for (String info :channelInfos){
if (info.contains("channelName")){
channelName = info.substring("channelName=".length());
Log.d(TAG,"channelName:"+channelName);
}else if (info.contains("channelNum")){
channelNum = Integer.parseInt(info.substring("channelNum=".length()));
Log.d(TAG,"channelNum:"+channelNum);
}
}
return channelInfo;
} catch (IOException e) {
e.printStackTrace();
}
return "unKnow channel";
}
}
接下来在Eclipse上正式编写我们的二次打包功能java小程序
接下来是具体代码
1.拆包
/**
* 拆包apk
* @param apkPath apk所在目录
* @return 拆包成功,返回拆包路径,否则为null
*/
private static String unZipApkFile(String apkPath) {
File apkPathFile = new File(apkPath);
String childAPKPath = "";
String unZipPath = null;//拆包所在目录
if (!apkPathFile.exists()) {
System.out.println(apkPath+":指定目录不存在");
return null;
}
String[] childrenFile = apkPathFile.list();
for (String childFile : childrenFile) {
if (childFile.endsWith(".apk")) {
System.out.println("apkFile:"+childFile);
originApkName = childFile;
childAPKPath = apkPath + childFile;
unZipPath = apkPathFile.getParent()+"\\"+childFile.split("\\.")[0];
}
}
String unZipApkCmd = "cmd.exe /c "+"apktool d "+childAPKPath; //apktool拆包命令
try {
Process ps = Runtime.getRuntime().exec(unZipApkCmd);
// int status = ps.waitFor();
// System.out.println(status);
InputStream in = ps.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in,"GBK"));
String line = br.readLine();
while(line!=null) {
System.out.println(line);
line = br.readLine();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
return unZipPath;
}
2.给拆后的包添加渠道文件
/**
* 添加或修改渠道文件
* @param dirPath 原始apk拆包后目录
* @param channelFile 渠道路径
*/
private static boolean changeChannel(String dirPath,String channelFile) {
File unZipFile = new File(dirPath);
if (!unZipFile.exists()) {
System.out.println("File path("+dirPath+")is not existed!!");
return false;
}
if (unZipFile.isDirectory()) {
File[] childFiles = unZipFile.listFiles();
for(File childFile:childFiles) {//遍历拆包目录
if (childFile.getName().equals("assets")) {
System.out.println(childFile.getPath());
File[] files = childFile.listFiles();
for(File file:files) {//遍历assets目录
if (file.getName().contains("channel.txt")) {
file.delete();
break;
}
}
break;
}
}
File assetFile = new File(unZipFile.getPath()+"\\"+"assets");
if (!assetFile.exists()) {
assetFile.mkdirs();
}
copyfile(assetFile.getAbsoluteFile(), channelFile);
}
return true;
}
/**
* 拷贝文件
* @param targetDir 拷贝到的目标目录
* @param channelFile 拷贝文件路径
*/
private static void copyfile(File targetDir,String channelFile) {
// TODO Auto-generated method stub
if (targetDir.exists() && targetDir.isDirectory()) {
File channel = new File(channelFile);
if (channel.exists()) {
try {
Files.copy(channel.toPath(), new FileOutputStream(new File(targetDir.getAbsolutePath()+"\\"+channel.getName())));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3.重新打包
/**
* 重新打包apk
* @param buildPath 拆包目录
* @return 返回重新打包的apk所在目录
*/
private static String reBuildApk(String buildPath) {
File buildDir = new File(buildPath);
if (!buildDir.exists()) {
System.out.println(buildPath+":文件不存在");
return null;
}
String buildApkCmd = "cmd.exe /c "+"apktool b "+buildPath; //打包命令
try {
Process ps = Runtime.getRuntime().exec(buildApkCmd);
// int status = ps.waitFor();
// System.out.println(status);
InputStream in = ps.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in,"GBK"));
String line = br.readLine();
while(line!=null) {
System.out.println(line);
line = br.readLine();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
return buildPath+"\\dist\\";
}
4.给新包签名
/**
* 执行签名命令,给apk签名
* @param signCMD
*/
private static void signApk(String apkPath) {
//签名命令,无需再输入签名口令
//注意,这里需对应各自的签名文件信息,比如,这里的keystore是签名文件名,123456是签名文件密码,keyalias是签名文件别名
String signCMD ="jarsigner -verbose -keystore "+keystore+" -storepass 123456 -keypass 123456"+" -signedjar "+ apkPath+ originApkName.split("\\.")[0]+"_signed.apk" +" "+apkPath+originApkName +" "+keyalias;
String cmd = "cmd.exe /c "+ signCMD;// pass
try {
Process ps = Runtime.getRuntime().exec(cmd);
// int status = ps.waitFor();
// System.out.println(status);
InputStream in = ps.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in,"GBK"));
String line = br.readLine();
while(line!=null) {
System.out.println(line);
line = br.readLine();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
System.out.println("child thread donn");
}
5.最终main函数里调用
private static final String PATH_ZIP_DIR = "E:\\AndroidProject\\PersonalProject\\ApkQuDao\\apkDir\\";//原始包路径
private static final String PATH_CHANNEL_FILE = "E:\\AndroidProject\\PersonalProject\\ApkQuDao\\apkDir\\channel.txt";
private static final String keystore = "mykey.keystore";
private static final String keyalias = "mykeystore";
private static String originApkName = "";
public static void main(String[] args) {
String unZipFilePath = unZipApkFile(PATH_ZIP_DIR);//apktool拆包
if (null == unZipFilePath) return;
System.out.println("拆包路径:"+ unZipFilePath);
boolean changeResult = changeChannel(unZipFilePath, PATH_CHANNEL_FILE);
if (changeResult) {
String reBuildApkPath = reBuildApk(unZipFilePath);
if (null == reBuildApkPath) return;
System.out.println("新包路径:"+ reBuildApkPath);
signApk(reBuildApkPath);
}
}
运行,执行小程序,然后安装我们二次打包后的apk,下面来安装这个apk验证(如果安装失败,可参考俺上篇博文查找原因)
先看渠道文件信息(channel.txt)
再点击运行apk打开AS查看log信息:
OK,成功添加并获取渠道信息,没有问题。
我们这里实现了通过java小程序对apk进行二次功能,现实项目中我们可能需要对我们开发的apk进行多渠道打包或者其他相关需求,这是我们就可以利用我们的小程序进行快速二次打包以提高工作效率,举一反三,这里我只是实现了一个最简单实用的功能。