增量更新实践

定义

所谓的增量更新,区别于传统的更新方式,将新的apk全部下载下来后安装覆盖掉旧的版本,增量更新只需要下载一个差分包即可,然后把下载的差分包和原来旧的apk进行合成,生成一个新的apk,这样就可以极大的减少升级新版本所需要的流量,下面就通过一个简单的demo来了解一下什么时增量更新。

如果想更深层次的了解增量更新的原理,可以参考下面两个博客

http://blog.csdn.net/hmg25/article/details/8100896
http://blog.csdn.net/tu_bingbing/article/details/8538592

  • 首先需要准备生成差分包的工具

http://download.csdn.net/detail/hmg25/4676737

下载后解压如图
这里写图片描述

  • 下面就是具体的代码了
    有两个别人写好的工具类ApkUtils,PatchUtils
package com.cundong.utils;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.text.TextUtils;

import java.util.Iterator;
import java.util.List;

/**
 * 类说明:  Apk工具类
 *
 * @author Cundong
 * @version 1.1
 * @date
 */
public class ApkUtils {

    /**
     * 获取已安装apk的PackageInfo
     *
     * @param context
     * @param packageName
     * @return
     */
    public static PackageInfo getInstalledApkPackageInfo(Context context, String packageName) {
        PackageManager pm = context.getPackageManager();
        List<PackageInfo> apps = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);

        Iterator<PackageInfo> it = apps.iterator();
        while (it.hasNext()) {
            PackageInfo packageinfo = it.next();
            String thisName = packageinfo.packageName;
            if (thisName.equals(packageName)) {
                return packageinfo;
            }
        }

        return null;
    }

    /**
     * 判断apk是否已安装
     *
     * @param context
     * @param packageName
     * @return
     */
    public static boolean isInstalled(Context context, String packageName) {
        PackageManager pm = context.getPackageManager();
        boolean installed = false;
        try {
            pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
            installed = true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return installed;
    }

    /**
     * 获取已安装Apk文件的源Apk文件
     *
     * @param context
     * @param packageName
     * @return
     */
    public static String getSourceApkPath(Context context, String packageName) {
        if (TextUtils.isEmpty(packageName))
            return null;

        try {
            ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
            return appInfo.sourceDir;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 安装Apk
     *
     * @param context
     * @param apkPath
     */
    public static void installApk(Context context, String apkPath) {

        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.parse("file://" + apkPath),
                "application/vnd.android.package-archive");

        context.startActivity(intent);
    }

    /**
     * 获取版本名称
     *
     * @param context 上下文
     * @return 版本名称
     */
    public static String getVersionName(Context context) {
        PackageManager pm = context.getPackageManager();
        try {
            PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0);
            return packageInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /* 获取版本号
    * @param context 上下文
    * @return 版本号
    */
    public static int getVersionCode(Context context) {
        PackageManager pm = context.getPackageManager();
        try {
            PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0);
            return packageInfo.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 0;
    }
}
package com.cundong.utils;

/**
 * 类说明:     APK Patch工具类
 * 
 * @author  Cundong
 * @date    2013-9-6
 * @version 1.0
 */
public class PatchUtils {

    /**
     * native方法 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath
     * 
     * 返回:0,说明操作成功
     * 
     * @param oldApkPath 示例:/sdcard/old.apk
     * @param newApkPath 示例:/sdcard/new.apk
     * @param patchPath  示例:/sdcard/xx.patch
     * @return
     */
    public static native int patch(String oldApkPath, String newApkPath, String patchPath);
}

注意这两个类一定要放在com.cundong.utils的包中,以为这里用到了一个native方法,使用了一个so库实现合成新的apk
然后将so库拷贝到项目中

  • MainActivity的代码,首先是旧的版本
package com.demo.patch;

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.cundong.utils.ApkUtils;
import com.cundong.utils.PatchUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("ApkPatchLibrary");//加载so库
    }

    private final String NEW_APK_PATH = Environment.getExternalStorageDirectory() + File.separator + "patchdemo.apk";
    private final String PATCH_PATH = Environment.getExternalStorageDirectory() + File.separator + "patchdemo.patch";
    private final String URL = "http://192.168.126.1:8080/patchdemo.patch";
    private final int SUCCESS = 5;
    private final int FAIL = 6;
    FileOutputStream fos = null;
    HttpURLConnection conn = null;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case SUCCESS:
                    //弹出Toast,提示合并成功,安装apk
                    Toast.makeText(MainActivity.this, "合并成功", Toast.LENGTH_SHORT).show();
                    ApkUtils.installApk(MainActivity.this, NEW_APK_PATH);
                    break;
                default:
                    Toast.makeText(MainActivity.this, "合并失败", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = (TextView) findViewById(R.id.tv);
        textView.setText("旧的版本" + ApkUtils.getVersionName(this) + "    " + ApkUtils.getVersionCode(this));
        new Thread(new Runnable() {
            @Override
            public void run() {
                loadApk();
            }
        }).start();
        findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                updateApk();
            }
        });
    }

    private void updateApk() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //获取已经安装过的旧的apk的路径
                String oldApkSource = ApkUtils.getSourceApkPath(MainActivity.this, getPackageName());
                //合并旧的apk和补丁成为新的apk
                int patchResult = PatchUtils.patch(oldApkSource, NEW_APK_PATH, PATCH_PATH);
                int what;
                if (patchResult == 0) {
                    what = SUCCESS;
                } else {
                    what = FAIL;
                }
                handler.obtainMessage(what).sendToTarget();
            }
        }).start();
    }

    private void loadApk() {
        try {
            File file = new File(PATCH_PATH);
            if (file.exists()) {
                file.delete();
            }
            URL url = new URL(URL);
            conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(5000);
            if(conn.getResponseCode()==HttpURLConnection.HTTP_OK){
                InputStream is=conn.getInputStream();
                fos=new FileOutputStream(file);
                byte[] b=new byte[1024];
                int len=0;
                while((len=is.read(b))!=-1){  //先读到内存
                    fos.write(b, 0, len);
                }
                fos.flush();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this,"下载成功了",Toast.LENGTH_LONG).show();
                    }
                });
            }
        } catch (final Exception e) {
            e.printStackTrace();
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this,e.toString(),Toast.LENGTH_LONG).show();
                }
            });
        }
    }
}

界面就很简单了,一个TextView,一个Button。运行这个工程,然后去build/outputs/apk/文件夹下把生成的apk文件拷贝出来放到之前下载的工具的bsdiff4.3-win32文件夹中,并改名为patchdemo-old.apk
效果图:
这里写图片描述

  • 更改textview的显示内容为
TextView textView = (TextView) findViewById(R.id.tv);
        textView.setText("已经更新到最新版本了" + ApkUtils.getVersionName(this) + "    " + ApkUtils.getVersionCode(this));

然后去build.gradle中把versioncode和versionname都做相应的更改,运行项目,生成新的apk文件,拷贝到工具bsdiff4.3-win32文件夹中,改名为patchdemo-new.apk,效果图:
这里写图片描述

  • 打开dos窗口,进入bsdiff4.3-win32路径下,执行命令 bsdiff.exe patchdemo-old.apk patchdemo-new.apk patchdemo.patch 就会生成一个以.patch结尾的文件,如图:
    这里写图片描述
    将这个.patch文件放在自己的的服务器上,我这里用的是tomcat

  • 把新的apk从手机上卸载,把旧的apk安装上,当提示下载成功后,点击安装,就会执行合成新apk的代码,跳转到安装页面,如图;这里写图片描述

  • 点击安装,就会把和合成后新的版本覆盖掉旧的版本,之后打开应用就是最新的版本了,效果图:
    这里写图片描述

一个简单的增量更新的demo就完成了,这里主要的难点在于通过旧的版本和差分包合成新的版本,这一部风代码都是通过调用so库中的函数实现的。这里没有进行版本的判断,以及多个差分包的情况。

github 代码仓库:https://github.com/githubyzd/PathDemo

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值