android推广识别,根据下载地址识别区分推广人的id(在服务器上面动态解压,打包,签名apk)亲测通过,非常完整。

有一个需求,就是很多时候,我们的应用为了推广,都会鼓励用户邀请好友,然后给别人奖励,这个邀请的链接,可以动态的生成,但是,下载的apk里面,没法保存邀请人的信息,就没法确定到底是谁邀请的,所以呢,就有了这个需求。

1、三种实现方式

我找了一早上的资料,目前大概有3种方法:

  • 邀请的时候,要求用户直接在网页上面注册,这样,就可以把是谁邀请的,直接保存在数据库中,这种方法适合大多数应用,但是很少的一部分,不行用户注册的,就不好弄了;
  • 用户注册的时候,手动的填写邀请码,这种方式,对于很懒的用户,都懒得写,而且邀请码一般还不会太短,很麻烦的一个事情;
  • 服务器根据邀请的链接,动态的打包apk,把邀请的信息直接写入apk,用户安装以后,就可以从apk里面读取出来,使用起来很方便,全过程用户无感知,可以说是最好的一种方式。当然,也有缺点,那就是签发apk的密钥库必须放在服务器上面,要保证安全,一次解压,打包,签名,花费的时间,还是比较长的,大概下几十秒。

2、重新打包的两种实现方式

前面两种都很简单就可以实现,下面我来说一下第三种实现的方式。
第三种方法,写入信息,我是直接写入到assets文件夹里面的,因为这个文件夹本来是保存静态资源的,而且不会编译,正合我意,哈哈。在android里面,直接读取就可以了:

//读取一个资源文件
InputStream open = getResources().getAssets().open("ad.conf");
String text=in2String(open);

//转换成字符串的函数
 public String in2String(InputStream in){
        ByteArrayOutputStream byteOut=new ByteArrayOutputStream();
        byte[] b=new byte[1024];
        int len;
        try {
            while ((len=in.read(b))!=-1){
                byteOut.write(b,0,len);
            }
            return byteOut.toString("UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

第三种方法,其实也有2种实现的方式:

  • 第一种是我看到网上大多数人使用的方式,就是使用apktool来解压,打包,这种方式不是很好,如果是使用360、百度等加固了的apk,会失败,打包的时候,会提示找不到资源;
  • 因为apk其实就是一个zip压缩包,所以,直接使用unzip,zip(Liunx下可以直接调用命令)就可以了,推荐使用这种方法实现。

2.1、使用apktool来实现

我是在ubuntu 14.4 LTS+ JDK1.8下面通过测试的,因为一般服务器上都是部署在Liunx上,所以,最好使用Liunx来开发。

需要的工具:

  • java–这个不说了
  • apktool–点击这里去下载 我使用的是2.2.1
  • jarsigner java自带的前面工具,一般在{jdk}/bin下面

详细的实现如下:

import java.io.*;
/**
 * 本类是演示使用apktool在动态的解压,打包,签名一个apk,用来向apk写入一些信息,比如推广的时候
 * 使用本类需要在执行的机器上面配置java,apktool。
 * Author:Mr.Jie
 * DateTime:16-11-6 上午11:02
 */
public class Main {
    public static void main(String[] args) {

        Process process = null;
        //记录处理开始的时间
        long start = System.currentTimeMillis();

        //为了方便使用我把所有需要配置的信息,全部写在下面了(所有配置均不能包含空格)

        //java的位置,如果你有环境变量,可以直接写java
        String javaPath="java";
        //apktool路径
        String apktoolPath = "apktool_2.2.1.jar";
        //需要解压的apk文件路径
        String apkPath = "my.apk";
        //保存解压以后文件夹的路径
        String unPackagePath = "myapp/";
        //重新打包以后,apk存放的路径
        String rePackagePath = "repackage.apk";
        //签名以后apk的
        String signedApkPath = "signedApk.apk";
        //jarsigner的路径,一般在jdk的bin目录,用于对apk签名
        String jarsignerPath = "jarsigner";
        //用于签名的密钥库的路径
        String keyPath = "testkey.jks";
        //密钥库完整性的口令
        String keyStorepass = "123456";
        //使用密钥库里面密钥的别名
        String keyAlias = "测试签名";
        //密钥的口令
        String keypass = "123456";
        //1----解压apk
        try {
            //解压apk文件包
            String unPackageCmd = javaPath+" -jar " + apktoolPath + " d -f -o " + unPackagePath + " " + apkPath;
            System.out.println("1.正在执行解压:" + unPackageCmd);
            process = Runtime.getRuntime().exec(unPackageCmd);
            if (process.waitFor() != 0) {
                System.out.println("解压失败,过程终止");
                return;
            }
            System.out.println("解压成功");
        } catch (Exception e) {
            e.printStackTrace();
        }

        //2----内容修改,这里是在assets文件夹里面写入信息
        OutputStreamWriter osw = null;
        try {
            String targetFilePath = unPackagePath + "assets";
            File wfile = new File(targetFilePath);
            if (!wfile.exists()) {
                wfile.mkdirs();
            }
            wfile = new File(wfile, "ad.conf");
            String content = "这里是解压以后,新写入的内容";
            System.out.println("2.正在执行写入:" + content);
            osw = new OutputStreamWriter(new FileOutputStream(wfile));
            osw.write(content, 0, content.length());
            osw.flush();
            System.out.println("写入文件新内容成功");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("写入文件信息出错,过程终止");
            return;
        } finally {
            try {
                osw.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }


        //3----重新打包
        try {
            String packageCmd =javaPath+" -jar " + apktoolPath + " b -o  " + rePackagePath + " " + unPackagePath;
            System.out.println("3.正在重新打包apk:" + packageCmd);
            process = Runtime.getRuntime().exec(packageCmd);
            if (process.waitFor() != 0) {
                System.out.println("打包失败,过程终止");
                return;
            }
            System.out.println("打包成功");
        } catch (Exception e) {
            e.printStackTrace();
        }


        //4----对apk签名
        try {
            String signCmd = jarsignerPath + " -keystore " + keyPath + " -storepass " + keyStorepass + " -signedjar " + signedApkPath + " " + rePackagePath + " -keypass " + keypass + " " + keyAlias;
            System.out.println("4.正在签名apk:" + signCmd);
            process = Runtime.getRuntime().exec(signCmd);
            if (process.waitFor() != 0) {
                System.out.println("签名失败。。。");
                return;
            }
            System.out.println("签名成功");
        } catch (Exception e) {
            e.printStackTrace();
        }

        //5----删除临时文件
        try {
            System.out.println("5.正在删除临时文件");
            //删除生成的没有前面的apk
            delDir(rePackagePath);
            //删除解压的目录
            delDir(unPackagePath);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("删除临时文件出错:"+e.getMessage());

        }

        float end = (System.currentTimeMillis() - start) / 1000f;
        System.out.println("解压,打包,签名apk完成,一共耗时:" + end + "S");

    }

    /**
     * 用来删除一个文件夹或者文件(写的有点麻烦,主要是删除文件夹,如果在Liunx下面,可以直接勇敢的调用rm命令,为了兼容windows,我就直接用java实现了)
     * @param filePath 文件夹路径
     */
    private static  void delDir(String filePath) {
        File file = new File(filePath);
        //不存在就返回
        if (!file.exists()) return;

        if (file.isFile()) {
            //是文件就直接删除
            file.delete();
            return;
        } else {
            //是文件夹就需要迭代的删除
            File[] files = file.listFiles();
            int len = files.length;
            for (int i = 0; i < len; i++) {
                if (files[i].isFile()) {
                    //是文件就直接删除
                    files[i].delete();
                } else {
                    //是文件夹就递归调用
                    delDir(files[i].getPath());
                }
            }
            file.delete();
        }
    }
}

到这里就完全实现了,跑一个看看:

1.正在执行解压:java -jar apktool_2.2.1.jar d -f -o myapp/ my.apk
解压成功
2.正在执行写入:这里是解压以后,新写入的内容
写入文件新内容成功
3.正在重新打包apk:java -jar apktool_2.2.1.jar b -o  repackage.apk myapp/
打包成功
4.正在签名apk:jarsigner -keystore testkey.jks -storepass 123456 -signedjar signedApk.apk repackage.apk -keypass 123456 测试签名
签名成功
5.正在删除临时文件
解压,打包,签名apk完成,一共耗时:20.174S

Process finished with exit code 0

2.2、使用zip,unzip来实现

这种方法,如果在Liunx下直接调用命令,就很简单了,我这个也是在Ubuntu 14.04 LTS下面测试通过的。

过程和上面差不多,直接解压,写信息,打包的方式,不过要把原来的签名信息删除掉,不然安装的时候,会提示签名错误。
全部实现如下,是直接调用Linux命令实现的:

import java.io.*;

/**
 * Author:Mr.Jie
 * DateTime:16-11-6 下午2:30
 */
public class ZipApk {
    public static void main(String[] args) {
        Process process = null;
        //记录处理开始的时间
        long start = System.currentTimeMillis();

        //为了方便使用我把所有需要配置的信息,全部写在下面了(所有配置均不能包含空格)

        //java的位置,如果你有环境变量,可以直接写java
        //需要解压的apk文件路径
        String apkPath = "my.apk";
        //保存解压以后文件夹的路径
        String unPackagePath = "myapp/";
        //重新打包以后,apk存放的路径
        String rePackagePath = "repackage.apk";
        //签名以后apk的
        String signedApkPath = "signedApk.apk";
        //jarsigner的路径,一般在jdk的bin目录,用于对apk签名
        String jarsignerPath = "jarsigner";
        //用于签名的密钥库的路径
        String keyPath = "testkey.jks";
        //密钥库完整性的口令
        String keyStorepass = "123456";
        //使用密钥库里面密钥的别名
        String keyAlias = "测试签名";
        //密钥的口令
        String keypass = "123456";


        //1----解压apk
        try {
            //解压apk文件包
            String unPackageCmd = "unzip -o " + apkPath + " -d  " + unPackagePath;
            System.out.println("1.正在执行解压:" + unPackageCmd);
            process = Runtime.getRuntime().exec(unPackageCmd);
            if (process.waitFor() != 0) {
                System.out.println(in2String( process.getInputStream()));
                System.out.println("解压失败,过程终止");
                return;
            }
            System.out.println("解压成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
         //2----写入信息
        OutputStreamWriter osw = null;
        try {
            String targetFilePath = unPackagePath + "assets";
            File wfile = new File(targetFilePath);
            if (!wfile.exists()) {
                wfile.mkdirs();
            }
            wfile = new File(wfile, "ad.conf");
            String content = "这里是解压以后,新写入的内容";
            System.out.println("2.正在执行写入:" + content);
            osw = new OutputStreamWriter(new FileOutputStream(wfile));
            osw.write(content, 0, content.length());
            osw.flush();
            System.out.println("写入文件新内容成功");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("写入文件信息出错,过程终止");
            return;
        } finally {
            try {
                osw.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        //3----删除原来的签名,不然最后打包出来的应用安装的时候,会提示签名错误
        String s=unPackagePath+"META-INF";
        System.out.println("3.删除原来的签名等信息:"+s);
        delDir(s);
        System.out.println("删除成功");


        //4----重新打包
        try {
            String packageCmd ="zip -r  "+rePackagePath+"  .";
            System.out.println("3.正在重新打包apk:" + packageCmd);
            process = Runtime.getRuntime().exec(packageCmd,null,new File(unPackagePath));
            if (process.waitFor() != 0) {
                System.out.println(in2String( process.getInputStream()));
                System.out.println("打包失败,过程终止");
                return;
            }
            System.out.println("打包成功");
        } catch (Exception e) {
            e.printStackTrace();
        }

        //5----打包以后目录不对,移动到指定的位置
        try {
            String moveCmd ="mv "+rePackagePath+"  ..";
            System.out.println("3.正在移动重新打包apk:" + moveCmd);
            process = Runtime.getRuntime().exec(moveCmd,null,new File(unPackagePath));
            if (process.waitFor() != 0) {
                System.out.println(in2String( process.getInputStream()));
                System.out.println("移动失败,过程终止");
                return;
            }
            System.out.println("移动成功");
        } catch (Exception e) {
            e.printStackTrace();
        }


        //6----对apk签名
        try {
            String signCmd = jarsignerPath + " -keystore " + keyPath + " -storepass " + keyStorepass + " -signedjar " + signedApkPath + " " + rePackagePath + " -keypass " + keypass + " " + keyAlias;
            System.out.println("4.正在签名apk:" + signCmd);
            process = Runtime.getRuntime().exec(signCmd);
            if (process.waitFor() != 0) {
                System.out.println(in2String( process.getInputStream()));
                System.out.println("签名失败。。。");
                return;
            }
            System.out.println("签名成功");
        } catch (Exception e) {
            e.printStackTrace();
        }

        //7----删除临时文件
        try {
            System.out.println("5.正在删除临时文件");
            //删除生成的没有签名的apk
            delDir(rePackagePath);
            //删除解压的目录
            delDir(unPackagePath);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("删除临时文件出错:"+e.getMessage());
        }

        float end = (System.currentTimeMillis() - start) / 1000f;
        System.out.println("解压,打包,签名apk完成,一共耗时:" + end + "S");


    }

    //转换成字符串的函数,用来读取出错信息
    public static String in2String(InputStream in){
        ByteArrayOutputStream byteOut=new ByteArrayOutputStream();
        byte[] b=new byte[1024];
        int len;
        try {
            while ((len=in.read(b))!=-1){
                byteOut.write(b,0,len);
            }
            return byteOut.toString("UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 用来删除一个文件夹或者文件,用来删除临时文件
     * @param filePath 文件夹路径
     */
    private static  void delDir(String filePath) {
        File file = new File(filePath);
        //不存在就返回
        if (!file.exists()) return;

        if (file.isFile()) {
            //是文件就直接删除
            file.delete();
            return;
        } else {
            //是文件夹就需要迭代的删除
            File[] files = file.listFiles();
            int len = files.length;
            for (int i = 0; i < len; i++) {
                if (files[i].isFile()) {
                    //是文件就直接删除
                    files[i].delete();
                } else {
                    //是文件夹就递归调用
                    delDir(files[i].getPath());
                }
            }
            file.delete();
        }
    }
}

最后的结果:

1.正在执行解压:unzip -o my.apk -d  myapp/
解压成功
2.正在执行写入:这里是解压以后,新写入的内容sadfsdafsfs
sfsdfsdfsdfsdf
写入文件新内容成功
3.删除原来的签名等信息:myapp/META-INF
删除成功
3.正在重新打包apk:zip -r  repackage.apk  .
打包成功
3.正在移动重新打包apk:mv repackage.apk  ..
移动成功
4.正在签名apk:jarsigner -keystore testkey.jks -storepass 123456 -signedjar signedApk.apk repackage.apk -keypass 123456 测试签名
签名成功
5.正在删除临时文件
解压,打包,签名apk完成,一共耗时:1.652S

Process finished with exit code 0

3、总结

  • 用两种方法都可以实现,这个都是亲测通过了,但是推荐第二种;

  • 第一种调用时间很长,一个demo的apk处理过程大概要20多秒,很大的apk估计时间还要长一些,第二种因为是直接调用Linux的命令,也只是仅仅解压,打包,签名,非常快,大概1s多,而且还主要是花费在签名上面;

  • 第二种,理论上是可以支持任何apk的,应该可以支持加固以后的应用,把加固以后的apk拿来,然后用上面的程序,应该可以直接跑。不过我没测试,也不敢保证;

  • 另外,提示一下,网上很多人写的博客,不知道是版本不一样,还是怎麽的,很多参数都不对,需要自己看一个文档,这里是个坑。

原创文章,转载请注明本博客地址:http://blog.csdn.net/jie11447416/article/details/50489957

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值