android版本更新

Android 一个简单的版本更新下载apk小示例

2016-11-21  本文已影响9186人  哇牛Aaron

一、简介:

1.运用 okhttp + notification 通知栏带进度的下载apk,下载完毕后并自动安装,如果用户取消可在通知栏点击安装,点击一次通知栏移除,同时支持自动静默下载(后台默默下载) 并解决UI卡顿问题,支持忽略此版本。
2.另一个服务里用的downloadManager进行下载效果基本一样,大家下载源码酌情考虑看那个更合心意吧。
废话不多说,我们来点实际的看看效果图。

提示效果.png 下载中显示进度效果图.png 下载完毕效果图.png 安装效果图以今日头条为例.png

二、简介

写这篇文章的目的也是一个小小的总结,因为项目需要。集成别人的势必冗杂,本着简单易维护的原则,自己动手写一个更符合要求。文章末尾会奉献源码,有兴趣的盆友可以下载看看。当然欢迎大家的指正交流。

1.通常情况下,用户进入主页面,我们会连接服务器查看用户当前apk版本是否是最新版本,不是则显示dialog提示用户下载最新版本客户端。
2.当用户点击下载后启动服务,通知栏显示下载进度。下载完毕后自动安装。同时,如果用户点击错误,取消安装。则可以继续查看通知栏,点击也可实现安装。
3.我们也需要一个开关。提供用户选择wifi下自动更新。这样当用户进入主页面有新版本后自动后台下载(静默下载)。完毕后显示安装界面提供用户安装。

三、代码实现

  1. 准备工作

这些工具类就不一一给出了 需要大家下载Demo自行提取吧
1.需要SP存储工具类 SPUtils
2.获取apk版本,并比较的工具类 VersionUtils
3.判断当前网络的工具列 NetworkTypeUtils

  1. MainActivity关键代码:

1.进入主页面,联网请求服务器比较当前apk版本与服务器端apk版本信息,如果不小于,则不做任何操作。如果小于服务版本信息则进行下一步判断,判断WiFi下自动更新的开关状态,网络状态。SP存储的版本状态。
2.这里我们用到了VersionUtils 版本比较工具类的方法:
VersionUtils.compareVersion(String.valueOf(VersionUtils.getVersionCode(this)), "1.2.0"); 返回值第一个值 < 第二个值则返回 -1 (1.2.0是我伪造的服务器版本号)。
3.VersionUtils.compareVersion()当返回值为-1时,我们要对用户进行提示 ,显示自定义的dialog,其中忽略当前版本是根据服务器端的版本号,用户选中ChecBox后将服务器版本号保存到SP储存中,下次进入判断SP储存的版本号于服务器版本号是否相等,相等则不再提示。
4.启动 DownloadService服务下载apk

package com.aaron.download_demo;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;

import com.aaron.download_demo.service.DownloadService;
import com.aaron.download_demo.utils.NetworkTypeUtils;
import com.aaron.download_demo.utils.SPUtils;
import com.aaron.download_demo.utils.VersionUtils;

/**
 * 作者:哇牛Aaron
 * 作者简书文章地址: http://www.jianshu.com/users/07a8b5386866/latest_articles
 * 时间: 2016/11/18
 * 功能描述: 主页面
 * 判断当前apk版本与服务器apk版本是否一致,不一致则弹出对话框,提示用户下载最新版本apk。
 * 如果wifi下自动更新开关处于关闭状态,用户点击更新,则启动服务下载apk。并在前台显示进度,下载完成后自动安装。
 * 如果wifi下自动更新开关处于打开状态,则在后台服务默认下载,下载完成后自动安装。
 * 如果用户点击忽略此版本,则当前版本将不再提示用户更新。
 */


public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private Button btn_wifi;
    private Boolean isCheck = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        initListener();
        progressVersion();
    }

    /**
     * 说明:
     * 比较服务器版本与当前apk版本,如果低于服务器版本
     * 1.wifi下自动更新开关打开 启动服务,后台静默下载apk,下载完毕后自动弹出安装界面
     * 2.wifi下自动更新开关关闭 启动服务,下载apk,并用notification通知栏显示下载进度等,下载完毕后自动弹出安装界面
     * --
     * 目前我没有线程的接口,去比较服务的版本,所以写一个假的服务器版本 用到的朋友自行显示比较即可
     */
    private void progressVersion() {
        //VersionUtils.getVersionCode(this)工具类里获取当前安装的apk版本号
        int version = VersionUtils.compareVersion(String.valueOf(VersionUtils.getVersionCode(this)),
                "1.2.0");//这里 1.2.0使我们伪造的 你完全可以得到自己服务器接口里的版本号 然后进行比对

        /**
         * 比较版本大小 version1为当前所安装的版本
         * version1 < version2  则  返回 -1
         * version1 > version2  则  返回 1
         * version1 == version2 则 返回  0
         */
        if (version == -1) {
            //判断 用户是否进入app主页面
            Intent intent = new Intent();
            intent.setClassName("com.aaron.download_demo", "MainActivity");

            if (intent.resolveActivity(getPackageManager()) == null) {
                Log.e(TAG, "不存在MainActivity");
                // 说明系统MainActivity没有被打开
                return;
            } else {
                Log.e(TAG, "存在MainActivity");

                /**
                 * wifi状态下自动下载
                 */
                if ((boolean) SPUtils.get(this, SPUtils.WIFI_DOWNLOAD_SWITCH, false)
                        && NetworkTypeUtils.getCurrentNetType(MainActivity.this).equals("wifi")) {

                    startService(new Intent(MainActivity.this, DownloadService.class));
                    //startService(new Intent(MainActivity.this, DownloadService2.class));
                    //Log.e("TAG", "startService");
                } else { //提示dialog

                    //判断 忽略的版本sp信息是否与当前版本相等 如果不相等 则显示更新的dialog
                    String spVersion = (String) SPUtils.get(this, SPUtils.APK_VERSION, "");
                    if (!spVersion.equals("1.2.0")) {//服务器版本 依旧填假数据 1.2.0

                        //下面是自定义dialog
                        View view = View.inflate(this, R.layout.download_layout, null);
                        final Dialog dialog = new AlertDialog.Builder(this).create();
                        dialog.show();

                        dialog.setContentView(view);
                        TextView content = (TextView) view.findViewById(R.id.tv_content);
                        content.setText("解决不能及时..." + "等其它版本信息");
                        //取消
                        TextView cancel = (TextView) view.findViewById(R.id.btn_cancel);
                        cancel.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {

                                //当true时 保存版本信息
                                if (isCheck) {
                                    SPUtils.put(MainActivity.this, SPUtils.APK_VERSION, "1.2.0");
                                }

                                //Log.e("TAG","isCheck == " + isCheck);

                                dialog.dismiss();
                            }
                        });

                        //确定
                        TextView Sure = (TextView) view.findViewById(R.id.btn_ok);
                        Sure.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                startService(new Intent(MainActivity.this, DownloadService.class));
                                //startService(new Intent(MainActivity.this, DownloadService2.class));
                                //当true时 保存版本信息
                                if (isCheck) {
                                    SPUtils.put(MainActivity.this, SPUtils.APK_VERSION, "1.2.0");
                                }

                                dialog.dismiss();
                                //Log.e("TAG", "isCheck == " + isCheck);
                            }
                        });


                        //忽略该版本
                        CheckBox checkBox = (CheckBox) view.findViewById(R.id.cb_ignore);
                        checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                            @Override
                            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                                if (isChecked) {
                                    isCheck = true;
                                } else {
                                    isCheck = false;
                                }

                            }
                        });
                    }
                }
            }
        }
    }

    private void initListener() {
        btn_wifi.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, SettingActivity.class));
            }
        });
    }

    private void initView() {
        btn_wifi = (Button) findViewById(R.id.btn_wifi);
    }
}

3 . DownloadService服务:

1.用hongyang大神封装好的okhttp下载工具下载apk,改工具包含下2载进度。hongyang大神封装库:okhttputils
2.关于notification不甚了解的盆友可以看看这篇文章
Android 通知栏Notification的整合
3.说明:
其中,服务中通过handler发送消息,区分是静默下载还是显示通知栏下载。
联网下载中重写inProgress()方法中,参数progress为下载进度。但我们 直接拿来用会出现卡顿问题,严重时会报异常。notification多次快速刷新进度会导致卡顿。这里我们也已经解决,通过handler + 判断语言去除小于或等于当前进度的数据,不在重复发送数据。具体看代码,很好理解。

package com.aaron.download_demo.service;

import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;

import com.aaron.download_demo.R;
import com.aaron.download_demo.utils.SPUtils;
import com.zhy.http.okhttp.OkHttpUtils;
import com.zhy.http.okhttp.callback.FileCallBack;

import java.io.File;

import okhttp3.Call;

/**
 * 作者:哇牛Aaron
 * 作者简书文章地址: http://www.jianshu.com/users/07a8b5386866/latest_articles
 * 时间: 2016/11/18
 * 功能描述: OkHttp + notification 下载的服务类
 * OkHttp下载框架hongyang大神封装的好的库 链接:https://github.com/hongyangAndroid/okhttputils
 */
public class DownloadService extends Service {
   /* private static final String DOWN_APK_URL = "http://219.238.2.164/apk.r1.market.hiapk.com/" +
            "data/upload/apkres/2016/11_10/11/com.ss.android.article.news_115902.apk?" +
            "wsiphost=local";*/

    private String DOWN_APK_URL = "http://111.204.176.254:8081/api/basic/apk";
    private static final String TAG = DownloadService.class.getSimpleName();

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (msg.what) {
                case 1:
                    //显示 Notification
                    showNotificationProgress(msg.arg1);
                    break;
                case 2:
                    //安装apk
                    installApk();
                    break;
            }
        }
    };

    private Boolean autoDownLoad;
    private int currentProgress = 0;

    private NotificationManager manager;
    private static final String APK_NAME = "downloadDemo.apk";


    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        //hongyang大神封装好的okHttp网络请求
        //启动分线程下载
        new Thread(new Runnable() {
            @Override
            public void run() {
                okHttpDownLoadApk(DOWN_APK_URL);
            }
        }).start();
    }


    /**
     * 联网下载最新版本apk
     */
    private void okHttpDownLoadApk(final String url) {
        OkHttpUtils
                .get()
                .url(url)
                .build()// Environment.getExternalStorageDirectory().getAbsolutePath() 存储路径
                .execute(new FileCallBack(Environment.getExternalStorageDirectory().getAbsolutePath(), APK_NAME) {
                    @Override
                    public void onError(Call call, Exception e, int id) {
                        Log.e(TAG, "onError :" + e.getMessage());
                    }

                    @Override
                    public void onResponse(File response, int id) {
                        //Log.e(TAG, "onResponse() 当前线程 == " + Thread.currentThread().getName());
                        //Log.e(TAG, "onResponse :" + response.getAbsolutePath());
                    }

                    @Override
                    public void inProgress(final float progress, long total, int id) {
                        super.inProgress(progress, total, id);
                        //Log.e(TAG, "inProgress() 当前线程 == " + Thread.currentThread().getName());
                        autoDownLoad = (Boolean) SPUtils.get(DownloadService.this, SPUtils.WIFI_DOWNLOAD_SWITCH, false);
                        //判断开关状态 开 则静默下载
                        if (autoDownLoad) {
                            //说明自动更新 这里服务在后台默默运行下载 只能看日志了
                            //Log.e(TAG, "自动安装的进度 == " + (100 * progress));

                            if ((100 * progress) == 100.0) {
                                //Log.e(TAG, "网络请求 自动安装 当前线程 == " + Thread.currentThread().getName());

                                handler.sendEmptyMessage(2);
                            }

                        } else {//否则 进度条下载

                            int pro = (int) (100 * progress);

                            //解决pro进度重复传递 progress的问题 这里解决UI界面卡顿问题
                            if (currentProgress < pro && pro <= 100) {
                                //Log.e(TAG, "进入");

                                currentProgress = pro;

                                Message msg = new Message();
                                msg.what = 1;
                                msg.arg1 = currentProgress;
                                handler.sendMessage(msg);
                            }

                        }
                    }
                });
    }


    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void showNotificationProgress(int progress) {
        //Log.e(TAG, "进度 == " + progress);
        String message = "当前下载进度: " + progress + "%";
        int AllProgress = 100;


        Intent intent = null;
        if (progress == 100) {
            message = "下载完毕,点击安装";
            //Log.e(TAG, "下载完成 " + progress);

            //安装apk
            installApk();
            if (manager != null) {
                manager.cancel(0);//下载完毕 移除通知栏
            }

            //当进度为100%时 传入安装apk的intent
            File fileLocation = new File(Environment.getExternalStorageDirectory(), APK_NAME);
            intent = new Intent(Intent.ACTION_VIEW);
            intent.addCategory("android.intent.category.DEFAULT");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setDataAndType(Uri.fromFile(fileLocation), "application/vnd.android.package-archive");
        }
        if (intent == null){    
            intent = new Intent(); //暂时没有更好的办法 保证不能为空即可
        }
        //表示返回的PendingIntent仅能执行一次,执行完后自动取消
        PendingIntent pendingIntent  = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);

        Notification notification = new Notification.Builder(this)
                .setSmallIcon(R.drawable.icon)//App小的图标
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))//App大图标
                .setContentTitle("自定义内容")//设置通知的信息
                .setContentIntent(pendingIntent)
                .setWhen(System.currentTimeMillis())
                .setContentText(message)
                .setAutoCancel(false)//用户点击后自动删除
                        //.setDefaults(Notification.DEFAULT_LIGHTS)//灯光
                .setPriority(Notification.PRIORITY_DEFAULT)//设置优先级
                .setOngoing(true)
                .setProgress(AllProgress, progress, false) //AllProgress最大进度 //progress 当前进度
                .build();
        notification.flags = Notification.FLAG_AUTO_CANCEL;
        manager.notify(0, notification);
    }

    /**
     * 安装apk
     */
    private void installApk() {
        //Environment.getExternalStorageDirectory() 保存的路径
        Log.e(TAG, "installApk运行了");

        File fileLocation = new File(Environment.getExternalStorageDirectory(), APK_NAME);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addCategory("android.intent.category.DEFAULT");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(Uri.fromFile(fileLocation), "application/vnd.android.package-archive");
        startActivity(intent);

        //停止服务
        stopSelf();
    }

    @Override
    public void onDestroy() {
        //Log.e(TAG, "onDestroy()");
        super.onDestroy();

        /*if (manager != null) {
            manager.cancel(0);//下载完毕 移除通知栏
        }*/
    }
}

PendingIntent 设置通知栏的点击意图,当进度为100%时才让其点击意图进行。
PendingIntent.FLAG_ONE_SHOT 表示返回的PendingIntent仅能执行一次,执行完后自动取消

4.DownloadService2

用downloadManager进行下载
参考资料:Android DownloadManager 的使用

package com.aaron.download_demo.service;

import android.app.DownloadManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;

import com.aaron.download_demo.utils.SPUtils;

import java.io.File;
/**
 * 作者:哇牛Aaron
 * 作者简书文章地址: http://www.jianshu.com/users/07a8b5386866/latest_articles
 * 时间: 2016/11/18
 * 功能描述: 运用DownloadManager实现下载 一样通知栏会显示
 *
 */
public class DownloadService2 extends Service {
    public static final String DOWNLOAD_FOLDER_NAME = "Trinea";
    public static final String DOWNLOAD_FILE_NAME = "123456Aaron.apk";

    private DownloadManager downloadManager;

    private String DOWN_APK_URL = "http://219.238.2.164/apk.r1.market.hiapk.com/" +
            "data/upload/apkres/2016/11_10/11/com.ss.android.article.news_115902.apk?" +
            "wsiphost=local";

    private long downloadId;
    private DownloadFinish downloadFinish;


    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        Log.e("TAG", "onCreate() 启动服务");
        downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);

        //下载之前先移除上一个 不然会导致 多次下载不成功问题
        long id = (long) SPUtils.get(DownloadService2.this, SPUtils.KEY, (long) 0);
        if (id != 0) {
            downloadManager.remove(id);
        }

        initData();

        downloadFinish = new DownloadFinish();

        //动态注册广播接收器
        registerReceiver(downloadFinish, new IntentFilter(
                DownloadManager.ACTION_DOWNLOAD_COMPLETE));

    }

    private void initData() {
        Log.e("TAG", "initData() 执行了~");

        //判断文件是否存在 不存在则创建
        File folder = new File(DOWNLOAD_FOLDER_NAME);
        if (!folder.exists() || !folder.isDirectory()) {
            folder.mkdirs();
        }

        //设置下载的URL
        DownloadManager.Request request = new DownloadManager.Request(
                Uri.parse(DOWN_APK_URL));
        request.setDestinationInExternalPublicDir(DOWNLOAD_FOLDER_NAME,
                DOWNLOAD_FILE_NAME);
        //设置样式 貌似必须用 getString() 如果不用 下载完毕后会显示 下载的路径
        //request.setTitle(getString(R.string.download_notification_title));
        request.setTitle("下载标题XX");
        request.setDescription("自定义随便填");

        //判断开关状态
        Boolean isOpen = (Boolean) SPUtils.get(this, SPUtils.WIFI_DOWNLOAD_SWITCH, false);
        //自动下载开关是否打开 如果打开
        if (isOpen) {
            Log.e("TAG", "下载完才显示");
            /**
             * 在下载过程中通知栏会一直显示该下载的Notification,在下载完成后该Notification会继续显示,直到用户点击该
             * Notification或者消除该Notification。
             */
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION);
        } else {
            Log.e("TAG", "正在下载时显示");
            /**
             * 在下载过程中通知栏会一直显示该下载的Notification,在下载完成后该Notification会继续显示,
             * 直到用户点击该Notification或者消除该Notification。
             */
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        }

        //是否显示下载用户接口
        request.setVisibleInDownloadsUi(false);
        //request.setMimeType("application/cn.trinea.download.file");
        request.setMimeType("application/vnd.android.package-archive");//设置此Type不然点击通知栏无法安装

        downloadId = downloadManager.enqueue(request);
        SPUtils.put(this, SPUtils.KEY, downloadId);

    }

    /**
     * 接受下载完成的广播
     */
    class DownloadFinish extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e("TAG", "DownloadFinish 广播接受完毕");

            //此ID为下载完成的ID
            long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            //如果完成的ID 于 我们下载的ID 一致则表示下载完成
            if (downloadId == completeDownloadId) {
                Log.e("TAG", "DownloadFinish downloadId == completeDownloadId");
                //安装apk
                String apkFilePath = new StringBuilder(Environment
                        .getExternalStorageDirectory().getAbsolutePath())
                        .append(File.separator)
                        .append(DOWNLOAD_FOLDER_NAME)
                        .append(File.separator).append(DOWNLOAD_FILE_NAME)
                        .toString();
                install(context, apkFilePath);
            }

        }
    }

    /**
     * 安装APK
     *
     * @param context
     * @param filePath
     */
    private void install(Context context, String filePath) {
        Log.e("TAG", "install() 安装");
        Intent i = new Intent(Intent.ACTION_VIEW);
        File file = new File(filePath);
        if (file != null && file.length() > 0 && file.exists() && file.isFile()) {
            i.setDataAndType(Uri.parse("file://" + filePath),
                    "application/vnd.android.package-archive");
            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(i);
        }

        //停止服务
        stopSelf();
    }


    //打印看看有没有停止服务 带完善的地方 下载完成以后通知栏应该移除掉啊 还没弄不知道行不行 这里只能用户手动移除
    //而且也没有做用户点击前台服务就启动安装 以前做过貌似报错 记不清了
    @Override
    public void onDestroy() {
        Log.e("TAG", "onDestroy()");
        super.onDestroy();

        if (downloadFinish != null) {
            //解注册
            unregisterReceiver(downloadFinish);
        }
    }

}

源码下载点这里↓
源码下载
欢迎盆友们纠正知错,互相交流学习。谢谢

纠错 晚上检测了下发现DownloadService空指针 上方文章代码已经更改,DownloadService类中174行添加这句即可 if (intent == null){ intent = new Intent(); }

上一篇下一篇

猜你喜欢

热点阅读