自定义Androidk全量更新组件

  自动更新功能对于一个APP来说是必备的功能,特别是对于未投放市场下载的APP,每次都让用户删掉原来的,再下载新的版本,肯定是不合适的。

实现思路:

  1. 后台提供接口,返回服务端版本号serviceVersion以及APK下载地址
  2. 前端对接接口,用拿到的serviceVersion和APK配置的localVersion比较,如果serviceVersion>localVersion则提示可以更新,通过获取的APK下载地址下载,然后通过api打开安装完成更新。

注意:

  1. localVersion笔者使用的是versionCode,可以再AndroidManifest中配置,通过java代码获取。笔者与后台约定了Code的规则,采用更新时间编辑,例如2018年8月2号,则versionCode=“180802”
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.mintu.dcdb"
    android:versionCode="180802"
    android:versionName="3.6">
  /**
     * 获取当前本地apk的版本号
     * @param mContext
     * @return
     */
    public static int getVersionCode(Context mContext) {
        int versionCode = 0;
        try {
            //获取软件版本号,对应AndroidManifest.xml下android:versionCode
            versionCode = mContext.getPackageManager().
                    getPackageInfo(mContext.getPackageName(), 0).versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return versionCode;
    }
  1. 对于Android7.0以上的手机,打开附件做了改变,无法使用以往的uri发布意图,详情可见笔者之前的一篇文章。Android7.0以上版本打开附件失败问题
  2. 本文的文件下载、附件打开方法使用的是笔者封装的OkHttp3工具类,使用者可以自己随意替换。只要将APK从url上下载下来,用API打开即可。

核心代码:

     /***
     * 检查是否更新版本
     */
    private void checkVersion() {
        if (Integer.parseInt((String) sharedPreferencesUtil.getData(Constant.VERSION_CODE_LOCAL,"")) < Integer
                .parseInt(CommonUtil.getInstance().isNull(sharedPreferencesUtil.getData(Constant.VERSION_CODE,"")) ? "0"
                        : (String) sharedPreferencesUtil.getData(Constant.VERSION_CODE,""))) {
            showDialog(new DownLoadBroadCastReceiver());
        }else{
            if(!isAutoUpdate) Toast.makeText(activity, "未检查到新版本", Toast.LENGTH_SHORT).show();
        }
    }  
    /***
     * 开线程下载
     */
    public void createThread(final String downUrl) {
        final Message message = new Message();
        if (SystemUtils.isNetworkAvailable(getApplicationContext())) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        OkHttpUtil.getInstance().download(downUrl, newApkUrl, file_name, new OkHttpUtil.OnDownloadListener() {
                            @Override
                            public void onDownloadSuccess(File downfile, File file) {
                                hand();
                                stopSelf();
                            }
                            @Override
                            public void onDownloading(int progress, File file) {
                                LogUtil.i("update progress---"+progress);
                                broadCast.putExtra("download", progress);
                                sendBroadcast(broadCast);
                            }
                            @Override
                            public void onDownloadFailed(String error) {
                            }
                        });
                    } catch (Exception e) {
                    }
                }
            }).start();
        } else {
            Toast.makeText(getApplicationContext(), "网络无连接,请稍后下载!",
                    Toast.LENGTH_SHORT).show();
        }
    }
 /**
     * 打开附件的方法
     * @param f
     * @param context
     */
    public void openFile(File f, Context context) {
        Log.i(LOGTAG, "正在打开附件打--------" + f.getName()+"。附件大小为"+f.length());
        try {
            String end = f.getName().substring(f.getName().lastIndexOf(".")
                    + 1, f.getName().length()).toLowerCase();
            if(end.equals("amr")){
                AudioRecoderUtils.getInstance().playerStart(f.getAbsolutePath());
            }else {
                Intent intent = new Intent();
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.setAction(android.content.Intent.ACTION_VIEW);
                intent.addCategory("android.intent.category.DEFAULT");
      /* 调用getMIMEType()来取得MimeType */
                String type = getMIMEType(f);
      /* 设置intent的file与MimeType */
                if(Build.VERSION.SDK_INT>=24){
                    Uri contenturi=FileProvider.getUriForFile(context, "com.mintu.dcdb.fileprovider",f);
                    intent.setDataAndType(contenturi,type);
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, contenturi);
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }else{
                    intent.setDataAndType(Uri.fromFile(f), type);
                }
                context.startActivity(intent);
            }
        } catch (Exception e) {
//            Toast.makeText(context,"打开附件---"+f.getName()+",发生了错误",Toast.LENGTH_SHORT).show();
            Log.e(LOGTAG, "打开附件" + f.getName() + "报错了,错误是-----" + e.getMessage());
        }
    }

项目中全量更新源码:

package com.mintu.dcdb.util.updateAppUtil;

import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.gson.Gson;
import com.mintu.dcdb.R;
import com.mintu.dcdb.config.Constant;
import com.mintu.dcdb.config.RequestUrl;
import com.mintu.dcdb.main.bean.UpdateBean;
import com.mintu.dcdb.main.view.UpdateDialog;
import com.mintu.dcdb.util.LogUtil;
import com.wusy.wusylibrary.base.BaseActivity;
import com.wusy.wusylibrary.util.CommonUtil;
import com.wusy.wusylibrary.util.OkHttpUtil;
import com.wusy.wusylibrary.util.SharedPreferencesUtil;

import java.io.IOException;
import java.util.ArrayList;

import okhttp3.Call;

/**
 * Created by XIAO RONG on 2018/7/19.
 */

public class UpdateAppUtil {
    private SharedPreferencesUtil sharedPreferencesUtil;
    private BaseActivity activity;
    private  ProgressDialog m_pDialog;
    private boolean isAutoUpdate=false;

    public UpdateAppUtil(BaseActivity activity,boolean isAutoUpdate){
        sharedPreferencesUtil=SharedPreferencesUtil.getInstance(activity);
        this.activity=activity;
        this.isAutoUpdate=isAutoUpdate;
    }

    public void updateApp(){
        String url = RequestUrl.getInstance().getUpdateUrl((String) SharedPreferencesUtil.getInstance(activity).getData(Constant.VERSION_CODE, ""));
        OkHttpUtil.getInstance().asynGet(url, new OkHttpUtil.ResultCallBack() {
            @Override
            public void successListener(Call call, String responseStr) {
                LogUtil.i("update result str--"+responseStr);
                Gson gson = new Gson();
                UpdateBean bean = gson.fromJson(responseStr, UpdateBean.class);
                sharedPreferencesUtil.saveData(Constant.VERSION_CODE,bean.getVersion());
                sharedPreferencesUtil.saveData(Constant.VERSION_URL,bean.getUrl());
                sharedPreferencesUtil.saveData(Constant.VERSION_CONTENT,bean.getContent());
                sharedPreferencesUtil.saveData(Constant.VERSION_PATCH_FILE_PATH,bean.getPatchFilePath());
                sharedPreferencesUtil.saveData(Constant.VERSION_IS_PATCH_FILE_PATH,bean.getIsPatchFilePath());
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        checkVersion();
                    }
                });
            }

            @Override
            public void failListener(Call call, IOException e) {

            }
        });
    }
    /***
     * 检查是否更新版本
     */

    private void checkVersion() {

        if (Integer.parseInt((String) sharedPreferencesUtil.getData(Constant.VERSION_CODE_LOCAL,"")) < Integer
                .parseInt(CommonUtil.getInstance().isNull(sharedPreferencesUtil.getData(Constant.VERSION_CODE,"")) ? "0"
                        : (String) sharedPreferencesUtil.getData(Constant.VERSION_CODE,""))) {
            showDialog(new DownLoadBroadCastReceiver());
        }else{
            if(!isAutoUpdate) Toast.makeText(activity, "未检查到新版本", Toast.LENGTH_SHORT).show();
        }
    }
    private void showDialog(final BroadcastReceiver receiver) {
        final Dialog alert = new UpdateDialog(activity, R.style.MyDialogStyle);
        alert.setContentView(R.layout.upgrade_dialog);
        TextView tvView = (TextView) alert.findViewById(R.id.upgradeText);
        if (!CommonUtil.getInstance().isNull(sharedPreferencesUtil.getData(Constant.VERSION_CONTENT, ""))) {
            tvView.setText(Html.fromHtml((String) sharedPreferencesUtil.getData(Constant.VERSION_CONTENT, "")));
        }

        tvView.setWidth(activity.getWindowManager().getDefaultDisplay().getWidth() * 3 / 4);
        Button sureBtn = (Button) alert.findViewById(R.id.btn_sure);
        Button cancleBtn = (Button) alert.findViewById(R.id.btn_cancle);

        sureBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {


                ArrayList<String> actionList = new ArrayList<>();
                actionList.add(Constant.DOWNLOAD_ACTION);
                activity.addBroadcastAction(actionList, receiver);
                Intent updateIntent = new Intent(activity,
                        UpdateService.class);
                updateIntent.putExtra("app_name", activity.getResources()
                        .getString(R.string.app));

                activity.startService(updateIntent);
                alert.dismiss();
            }
        });
        cancleBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                alert.dismiss();
            }
        });
        alert.show();
    }
    class DownLoadBroadCastReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                case Constant.DOWNLOAD_ACTION:
                    int progress = intent.getIntExtra("download", 0);
                    showProgress(progress);
                    break;

                default:
                    break;
            }

        }
    }
    private void showProgress(Integer progress) {
        if (m_pDialog == null) {
            createProgressDialog("正在更新请稍后...");
        }
        m_pDialog.setProgress(progress);
        if (progress == 100) {
            m_pDialog.dismiss();
        }
    }
    private void createProgressDialog(String title) {

        // 创建ProgressDialog对象
        m_pDialog = new ProgressDialog(activity);

        // 设置进度条风格,风格为长形
        m_pDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

        // 设置ProgressDialog 标题
        m_pDialog.setTitle(title);
        // 设置ProgressDialog 进度条进度
        m_pDialog.setProgress(100);

        // 设置ProgressDialog 的进度条是否不明确
        m_pDialog.setIndeterminate(false);

        // 设置ProgressDialog 是否可以按退回按键取消
        m_pDialog.setCancelable(true);
        // 设置点击进度对话框外的区域对话框不消失
        m_pDialog.setCanceledOnTouchOutside(false);
        // 让ProgressDialog显示
        m_pDialog.show();
    }
}
package com.mintu.dcdb.util.updateAppUtil;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import com.mintu.dcdb.config.Constant;
import com.mintu.dcdb.util.LogUtil;
import com.mintu.dcdb.util.SystemUtils;
import com.wusy.wusylibrary.util.CommonUtil;
import com.wusy.wusylibrary.util.OkHttpUtil;
import com.wusy.wusylibrary.util.SharedPreferencesUtil;

import java.io.File;

/**
 *
 * Filename: UpdateService.java Description: 今天修改了当增量包合成失败的时候,重新下载整个最新的apk
 * Company: minto
 *
 * @author: chjr
 * @version: 2.8.0 Create at: 2016年5月20日 下午3:25:52
 *
 */
public class UpdateService extends Service {
    private final int TIMEOUT = 10 * 1000;// 超时
    private final int DOWN_OK = 1;
    private final int DOWN_ERROR = 0;
    private String app_name;
    private String file_name;

    private NotificationManager notificationManager;
    private Notification notification;

    private Intent updateIntent;
    private PendingIntent pendingIntent;

    private int notification_id = 0;
    ProgressDialog m_pDialog;
    private File updateDir = new File(Constant.FILEDIR);
    File updateFile;
    SharedPreferencesUtil vsPreference;
    String packageName = "com.minto.workhi";
    String patchUrl = Constant.FILEDIR;
    String newApkUrl = Constant.FILEDIR;
    private String downUrl = "";
    private int flag = 2;
    private Intent broadCast;
    @Override
    public IBinder onBind(Intent arg0) {
        stopSelf();
        return null;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        vsPreference = SharedPreferencesUtil.getInstance(getApplicationContext());
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (CommonUtil.getInstance().isNull(intent)) {
            stopSelf();
            Log.d("UpService", intent + "--------------------");
        } else {

            app_name = intent.getStringExtra("app_name");
            file_name = app_name + ".apk";


            patchUrl = patchUrl + app_name + ".patch";
            // 创建文件
            CommonUtil.getInstance().createFile(app_name + ".patch");
            CommonUtil.getInstance().createFile(file_name);
            updateFile = new File(updateDir + "/" + app_name + ".patch");
            if(flag==1)flag=2;
            broadCast = new Intent();
            broadCast.setAction(Constant.DOWNLOAD_ACTION);
            broadCast.putExtra("download", 0);
            sendBroadcast(broadCast);
            downFile();
        }
        return super.onStartCommand(intent, flags, startId);
    }

    /***
     * 开线程下载
     */
    public void createThread(final String downUrl) {
        final Message message = new Message();
        if (SystemUtils.isNetworkAvailable(getApplicationContext())) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        OkHttpUtil.getInstance().download(downUrl, newApkUrl, file_name, new OkHttpUtil.OnDownloadListener() {
                            @Override
                            public void onDownloadSuccess(File downfile, File file) {
                                hand();
                                stopSelf();
                            }
                            @Override
                            public void onDownloading(int progress, File file) {
                                LogUtil.i("update progress---"+progress);
                                broadCast.putExtra("download", progress);
                                sendBroadcast(broadCast);
                            }
                            @Override
                            public void onDownloadFailed(String error) {
                            }
                        });
                    } catch (Exception e) {
                    }
                }
            }).start();
        } else {
            Toast.makeText(getApplicationContext(), "网络无连接,请稍后下载!",
                    Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 根据类型下载不同的文件
     *
     * @exception
     * @since 1.0.0
     */
    protected void downFile() {
        // 下载增量包路径
        if (1 == flag) {
            downUrl = (String) vsPreference.getData(Constant.VERSION_PATCH_FILE_PATH,"");
        } else if (2 == flag) {
            downUrl = (String) vsPreference.getData(Constant.VERSION_URL,"");
        }
        createThread(downUrl);
    }

    /**
     * 根据不同的包安装
     *
     * @exception
     * @since 1.0.0
     */
    protected void hand() {
        // 增量合成方法
        if (1 == flag) {
//          ApkUpdate update = new ApkUpdate(getApplicationContext(),
//                  packageName, patchUrl, newApkUrl);
//          // 检查低版本的apk 是否存在
//          if (update.isOldApkExist()) {
//              // 判断是否成功合成新的apk
//              if (update.newApkGet()) {
//                  update.installApk();
//                  delData();
//              } else {
//                  // Toast.makeText(getApplicationContext(),
//                  // "下载失败!:" + newApkUrl + "增量包地址:" + patchUrl,
//                  // Toast.LENGTH_SHORT).show();
//                  flag = 2;
//                  downFile();
//              }
//          }
        }
        // 整包安装方法
        else if (2 == flag) {
            File apkFile = new File(newApkUrl, file_name);
            OkHttpUtil.getInstance().openFile(apkFile,this);
        }
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张高兴的博客

张高兴的 Xamarin.Android 学习笔记:(三)活动生命周期

37711
来自专栏编程思想之路

Android四大组件完全解析(二)---Service

Service两大功能 : 当应用程序不与用户交互时,运行一些需要长时间运行的操作 为其他应用提供一些功能(提供能够跨进程调用的功能) Service的配置:...

3918
来自专栏刘望舒

探究RemoteViews的作用和原理

RmoteViews是一个能显示在其他进程的视图。同样也提供了一些基本的操作方法来修改视图的内容。

1481
来自专栏androidBlog

ARouter 使用教程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/de...

3321
来自专栏张善友的专栏

使用ASP.NET实现Model View Presenter(MVP)

作者:Billy McCafferty 翻译:张善友 原文地址:http://www.codeproject.com/useritems/ModelViewPr...

2318
来自专栏编程思想之路

Android6.0源码分析之蓝牙显示接收到的文件

在蓝牙界面有个menu:显示接收到的文件。本文分析显示接收到的文件 chapter one---显示接收到的文件 /android/packages/app...

2426
来自专栏Android知识点总结

[番外]理一理Android多文件上传那点事

1791
来自专栏编程之路

羊皮书APP(Android版)开发系列(三)APP引导页启动控制类

1233
来自专栏潇涧技术专栏

Art of Android Development Reading Notes 8

《Android开发艺术探索》读书笔记 (8) 第8章 理解Window和WindowManager

871
来自专栏双十二技术哥

Android AsyncLayoutInflater 限制及改进

上一篇文章中我们介绍了 AsyncLayoutInflater 的用法及源码实现,那么本文来分析下 AsyncLayoutInflater 使用的注意事项及改进...

3022

扫码关注云+社区

领取腾讯云代金券