Android 更新方案实现

需求说明

为了保证自己 APP 的新版本使用率,现在有很多已有的“软件更新”框架供各位使用,本文的主要内容是如何自己动手来实现软件的后台下载,更新。

下面详细说明下软件更新的逻辑,流程图如下:

 

每步详细代码

1. 检测当前的网络状态

    public static boolean isWifiConnected(Context context) {
        if (context != null) {
            // 获取手机所有连接管理对象
            ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            // 获取 NetworkInfo 对象
            NetworkInfo networkInfo = manager.getActiveNetworkInfo();
            // 类型是否为WIFI
            if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI)
                return networkInfo.isAvailable();
        }
        return false;
    }

上面的代码,传入一个 Context 对象,就可以返回当前的 Wifi 是否连接。

注意:需要添加获取网络状态权限

2. 检测和服务器版本是否一致

 我们检查完网络状态后,就可以执行这一步骤。这一步骤,我们需要后端小伙伴的配合。

后端服务,需要提供一个 API,该接口的返回值类型包括如下主要内容:

{  
   "version_code":1,
   "download_url":"你软件的下载地址"
}

其中,version_code 是服务器上软件的最新版本号,download_url 是 APK 的下载地址。本教程默认使用 HTTP 协议来下载 APK 文件。

我们在 Wifi 网络下,请求以上 API,将获得的版本号与当前的版本号进行比较,如果版本号不一致(或者说小于 API 返回的版本号),开始进入 APK 下载流程。

3. 多线程下载 APK

下面我们来完成多线程下载的代码,该代码要实现以下功能:

  1. 多线程下载,目的是不造成用户在主线程的操作卡顿
  2. 任务完成后,启动安装程序开始安装 APK。
  3. 任务完成后的标志,该功能主要解决用户下载一半就(强制)退出程序的问题,防止文件坏损,保证安装的文件都是完整的文件。

下面是多线程下载的程序:

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * 用来下载文件的,多线程,主要用来下载 APK 文件,用来更新
 * <p>
 * https://github.com/Jere3y/utils/blob/master/xz.java
 * Created by Tianyu Xin (tianyurui@gmail.com) on 2017/8/2.
 */

public abstract class Xz {
    private static final String TAG = "Xz";

    private static final int BUFFER_SIZE = 1024 * 500;
    private static final int THREAD_COUNT = 2;
    private static volatile int completeCount = 0;

    public void start(String url) {
        start(url, "");
    }

    public void start(String urlStr, String path) {
        Log.i(TAG, "开始下载任务,地址是:" + urlStr);
        Log.i(TAG, "保存到:" + path);
        HttpURLConnection connection;
        URL url;
        try {
            url = new URL(urlStr);
            Log.i(TAG, "开始建立连接...");
            connection = (HttpURLConnection) url.openConnection();
            Log.i(TAG, "连接建立成功....");
            int code = connection.getResponseCode();
            Log.i(TAG, "code ---->" + code);
            if (code == 200) {
                Log.i(TAG, "文件获取成功,开始下载...");
                final int length = connection.getContentLength();
                connection.disconnect();
                Log.i(TAG, "文件大小获取成功,大小为:" + length);
                String[] split = urlStr.split("/");
                String defFileName = split[split.length - 1];
                Log.i(TAG, "开始创建,文件名为:" + defFileName);
                File file = new File(path);
                if (!file.exists()) {
                    file.mkdir();
                }
                String p = path + defFileName;
                RandomAccessFile raf = new RandomAccessFile(p, "rw");
                Log.i(TAG, "文件创建成功");
                raf.setLength(length);
                raf.close();
                int partSize = length / THREAD_COUNT;
                for (int i = 0; i < THREAD_COUNT; i++) {
                    int startPosition, endPosition;
                    startPosition = i * partSize;
                    endPosition = (i + 1) * partSize - 1;
                    if (i == THREAD_COUNT - 1) {
                        endPosition = length - 1;
                    }
                    new DownloadThread(url, p, startPosition, endPosition, i).start();
                }
            }
        } catch (MalformedURLException e) {
            Log.i(TAG, "地址解析失败,请检查后重新尝试!");
            e.printStackTrace();
        } catch (Exception e) {
            Log.i(TAG, "地址链接失败,请检查地址是否正确!");
            e.printStackTrace();
        }

    }

    /**
     * 同步方法,增加完成的线程数量 +1
     */
    private synchronized void incCompletedCount() {
        completeCount += 1;
    }

    /**
     * 这个必须实现
     *
     * @param path 这个是下载完成后回调的方法,参数是下载完成后改名的文件路径
     */
    public abstract void onDownloadCompleted(String path);

    /**
     * 是否能成功改名
     *
     * @param path 改名前的文件的路径
     * @return 改名是否成功
     */
    private boolean canRenameToCompleted(String path) {
        File file = new File(path);
        String replace = markingTaskCompleted(path);
        if (file.exists()) {
            return file.renameTo(new File(replace));
        }
        return false;
    }

    /**
     * 把下载完成后的文件改名,能区分是否下载完成
     *
     * @param path 改名前的路径
     * @return 改名后的路径
     */
    public static String markingTaskCompleted(String path) {
        return path.replace(".apk", "-complete.apk");
    }

    class DownloadThread extends Thread {
        URL url;
        String defFileName;
        int startPosition;
        int endPosition;
        int id;

        DownloadThread(URL url, String defFileName, int startPosition, int endPosition, int id) {
            super();
            this.url = url;
            this.defFileName = defFileName;
            this.startPosition = startPosition;
            this.endPosition = endPosition;
            this.id = id;
        }

        @Override
        public void run() {
            Long startTime = System.currentTimeMillis();
            Log.i(TAG, "线程开始id:" + id);
            HttpURLConnection connection;
            InputStream inputStream = null;
            try {

                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);

                int code = connection.getResponseCode();

                if (206 == code) {
                    inputStream = connection.getInputStream();
                    RandomAccessFile raf = new RandomAccessFile(defFileName, "rwd");
                    raf.seek(startPosition);
                    Log.i(TAG, "线程" + id + "创建成功,开始下载...");
                    int hasRead = -1;
                    byte[] buffer = new byte[BUFFER_SIZE];
                    while ((hasRead = inputStream.read(buffer)) != -1) {
                        raf.write(buffer, 0, hasRead);
                    }
                    raf.close();
                }
                Long endTime = System.currentTimeMillis();
                Log.i(TAG, "线程" + id + "下载完毕,耗时:" + (endTime - startTime) / 1000 + "秒");
                incCompletedCount();
                // 完成后并且成功改名,回调方法
                if (completeCount == THREAD_COUNT && canRenameToCompleted(defFileName)) {
                    onDownloadCompleted(markingTaskCompleted(defFileName));
                }
            } catch (MalformedURLException e) {
                Log.i(TAG, "线程" + id + "地址解析失败,请检查后重新尝试!");
                e.printStackTrace();
            } catch (IOException e) {
                Log.i(TAG, "线程" + id + "地址链接失败,请检查地址是否正确!");
                e.printStackTrace();
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
        }
    }
}

上面的代码使用多线程完成了 HTTP 协议的下载,并且在成功下载后,会将文件改名,用来标志文件的成功下载。

这样我们只需要判断改名后的文件是否存在,就可以知道文件是否完整的下载完成。

如果完整的下载完成,那么执行安装。

如果没有完整的下载完成(就是,改名后的文件不存在),那么重新执行下载。

上面的代码在下载完成后会回调方法 onDownloadCompleted(String defFile)

其中,参数 defFile 是下载完成后保存的文件路径,我们可以直接使用这个参数来调用 Android 的安装代码。

4. 执行安装

关于执行安装的代码,可以参照上一篇文章,调用系统的包管理安装 APK:

http://www.cnblogs.com/newjeremy/p/7294519.html

posted @ 2017-08-16 14:40  newJeremy  阅读(457)  评论(0)    收藏  举报