直接拿来用的版本更新(Android 兼容7.0、8.0)

1、需求:

版本更新,一个经常用到的功能。写一个工具类,拿去直接用。

2、实现思路:

  • 1、请求数据 拿到后台的apk的版本号、版本名、更新内容、下载地址(可能还会包括是否强制更新的标志位)。这里有一点需要注意,如果有强制更新的概念,这时候请求数据的时候要给服务器你当前的版本号。如果你现在的版本号是1 ,用户很久没有操作,期间版本更新了2(强制更新)、3(不强制更新)。这时候用户打开版本号为1的版本,应该是要强制更新的。
  • 2、和本地的apk版本号对比时候需要更新
  • 3、这里用户可能会忽略此版本,这时候我要存储一个版本号,用户忽略的版本号,如果用户的是主动更新的,都要提示。如果不是主动更新的判断一下,判断一下。
  • 4、在步骤2以后 如果需要更新,先判断步骤3里边存储的版本号,比较大小, 如果用户忽略更新这个版本,那就不更新了;如果用户没有忽略过此版本,继续往下
  • 5、这时候给用户一个提示,展示内容是有新版本是否更新,更新内容
  • 5.1 这里可能涉及到一个强制更新,如果是强制更新的,那就要求,用户没有更新,就退出程序;不是强制更新的,可以忽略此版本
  • 6、启动下载服务
  • 7、下载完成之后的自动安装
    • 8、不要忘记兼容7.0 在res/xml文件中添加 file_paths.xml 文件,并且在AndroidManifest.xml文件中添加

3、代码实现

这里使用DownLoadManager来做下载,在UpdateAppService类中。
在UpdateAppUtils检测是否要下载

UpdateAppUtils.java


import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;

import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;


import com.example.lql.updateappdemo.message.EventMessage;
import com.example.lql.updateappdemo.service.UpdateAppService;
import com.example.lql.updateappdemo.utils.FinalData;
import com.example.lql.updateappdemo.utils.PreferenceUtils;
import com.example.lql.updateappdemo.utils.T;

import org.greenrobot.eventbus.EventBus;

import java.lang.ref.WeakReference;


/**
 * 类描述:版本更新工具类<br>
 * 这里使用DownLoadManager来做下载,在UpdateAppService类中<br>
 * 在UpdateAppUtils检测是否要下载<br>
 * 逻辑关系说明:<br>
 * 1、请求数据  拿到后台的apk的版本号、版本名、更新内容、下载地址(可能还会包括是否强制更新的标志位)<br>
 * 这里有一点需要注意,如果有强制更新的概念,这时候请求数据的时候要给服务器你当前的版本号,<br>
 * 如果你现在的版本号是1  ,用户很久没有操作,期间版本更新了2(强制更新)、3(不强制更新)。这时候用户打开版本号为1的版本,应该是要强制更新的。<br>
 * 2、和本地的apk版本号对比时候需要更新<br>
 * 3、这里用户可能会忽略此版本,这时候我要存储一个版本号,用户忽略的版本号,如果用户的是主动更新的,都要提示。如果不是主动更新的判断一下,判断一下。<br>
 * 4、在步骤2以后   如果需要更新,先判断步骤3里边存储的版本号,比较大小,<br>
 * 如果用户忽略更新这个版本,那就不更新了;如果用户没有忽略过此版本,继续往下<br>
 * 5、这时候给用户一个提示,展示内容是有新版本是否更新,更新内容<br>
 * 5.1  这里可能涉及到一个强制更新,如果是强制更新的,那就要求,用户没有更新,就退出程序,不是强制更新的,可以忽略此版本<br>
 * 6、启动下载服务<br>
 * 7、下载完成之后的自动安装<br>
 * 8、不要忘记兼容7.0  在res/xml文件中添加    file_paths.xml 文件,并且在AndroidManifest.xml文件中添加
 * 使用说明:<br>
 * 在外部直接调用UpdateApp()方法<br>
 * 作  者:李清林<br>
 * 时  间:2017.5.12<br>
 * 更新时间:2018.5.2<br>
 * 修改备注:增加申请权限功能<br>
 * 更新时间:2018.7.23<br>
 * 修改备注:兼容8.0<br>
 */
public class UpdateAppUtils {
    /**
     * 当前版本号
     */
    private static int mVersionCode = 0;
    private static Activity mActivity;
    private static Fragment mFragment;
    /**
     * 下载地址
     */
    private static String mDownloadUrl = "";
    /**
     * 写SD卡权限
     */
    public static int REQUEST_PERMISSION_SDCARD_6_0 = 0x56;
    /**
     * 允许安装未知来源权限
     */
    public static int REQUEST_PERMISSION_SDCARD_8_0 = 0x58;
    /**
     * 去设置页面
     */
    public static int REQUEST_PERMISSION_SDCARD_SETTING = 0x57;
    /**
     * 服务器的版本号码
     */
    private static int serviceVersionCode = 0;
    /**
     * 服务器的版本号名称
     */
    private static String serviceVersionName = "";
    /**
     * 是否强制更新
     */
    private static boolean IsUpdate = false;
    /**
     * 更新说明
     */
    private static String content = "";

    public UpdateAppUtils() {
    }

    /**
     * @param activity       上下文
     * @param newVersionCode 服务器的版本号
     * @param newVersionName 服务器的版本名称
     * @param content        更新了的内容
     * @param downUrl        下载地址
     * @param IsUpdate       是否强制更新
     * @param IsToast        是否提示用户当前已经是最新版本
     */
    public static void UpdateApp(Activity activity, Fragment fragment, int newVersionCode, String newVersionName,
                                 String content, String downUrl, boolean IsUpdate, boolean IsToast) {

        UpdateAppUtils.mActivity = activity;
        UpdateAppUtils.mFragment = fragment;
        UpdateAppUtils.mDownloadUrl = downUrl;
        UpdateAppUtils.serviceVersionCode = newVersionCode;
        UpdateAppUtils.serviceVersionName = newVersionName;
        UpdateAppUtils.IsUpdate = IsUpdate;
        UpdateAppUtils.content = content;
        //首先拿到当前的版本号和版本名
        try {
            UpdateAppUtils.mActivity = activity;
            PackageManager pm = mActivity.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(mActivity.getPackageName(), 0);
            mVersionCode = pi.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        if (mVersionCode < serviceVersionCode) {
            //第2步骤
            if (PreferenceUtils.getInt(FinalData.VERSIONCODE, 0) < serviceVersionCode || IsToast) {
                //第3步骤
                //这时候要去更新,展示下载的对话框
                showDownLoadDialog();
            }
        } else {
            if (IsToast) {
                T.shortToast(mActivity, "当前已是最新版本");
            }
        }
    }


    /**
     * 下载对话框,并且请求权限
     */
    private static void showDownLoadDialog() {
        AlertDialog dialog = new AlertDialog.Builder(mActivity).
                setCancelable(false).
                setTitle("更新到 " + serviceVersionName).
                setMessage(content).
                setPositiveButton("下载", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface arg0, int arg1) {//第6步骤,下载
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                                checkPermission();
                            } else {
                                downLoad();
                            }
                        } else {
                            downLoad();
                        }
                    }
                }).
                setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //这里涉及到下载的强制更新,是不是强制更新   强制更新,点取消按钮,退出程序
                        if (IsUpdate) {
                            T.shortToast(mActivity, "此版本需要更新,程序即将退出");
                            MyHandler myHandler = new MyHandler(new UpdateAppUtils());
                            myHandler.sendEmptyMessageDelayed(0, 1000 * 3);
                        } else {
                            PreferenceUtils.setInt(FinalData.VERSIONCODE, serviceVersionCode);
                            dialog.dismiss();
                        }
                    }
                }).
                create();
        dialog.show();
    }

    /**
     * 检查6.0权限
     */
    private static void checkPermission() {
        if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                checkInstallPermission();
            } else {
                //有权限去下载
                downLoad();
            }
        } else {
            // 该方法在用户上次拒绝后调用,因为已经拒绝了这次你还要申请授权你得给用户解释一波
            // 一般建议弹个对话框告诉用户 该方法在6.0之前的版本永远返回的是fasle
            if (ActivityCompat.shouldShowRequestPermissionRationale(mActivity, Manifest.permission.READ_EXTERNAL_STORAGE)) {
                getPermissionDialog(0);
            } else {// 申请授权
                if (mFragment != null) {
                    mFragment.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_SDCARD_6_0);
                } else {
                    ActivityCompat.requestPermissions(mActivity,
                            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_SDCARD_6_0);
                }
            }
        }
    }

    /**
     * 检查8.0权限
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    private static void checkInstallPermission() {
        boolean b = mActivity.getPackageManager().canRequestPackageInstalls();
        if (b) {
            downLoad();
        } else {
            getPermissionDialog(1);
        }
    }

    /**
     * 正式去下载
     */
    private static void downLoad() {
        T.shortToast(mActivity, "正在下载...");
        //如果要更新,并且是强制更新,这里发一个事件,不让其他的页面做操作了
        EventBus.getDefault().post(new EventMessage(EventMessage.CheckApp, true));
        new Thread(new Runnable() {
            @Override
            public void run() {
                //启动服务
                Intent service = new Intent(mActivity, UpdateAppService.class);
                service.putExtra("downLoadUrl", mDownloadUrl);
                mActivity.startService(service);
            }
        }).start();
    }


    /**
     * 申请权限的对话框
     *
     * @param type 0:6.0   1:8.0
     */
    private static void getPermissionDialog(int type) {
        String message = "更新软件需要您允许我们获取您的数据读写权限,否则将无法更新";
        if (type == 1) {
            message = "为了正常升级APP,请点击设置-高级设置-允许安装未知来源应用,本功能只限用于APP版本升级";
        }
        AlertDialog dialog = new AlertDialog.Builder(mActivity).
                setCancelable(false).
                setTitle("权限提醒").
                setMessage(message).
                setPositiveButton("权限设置", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface arg0, int arg1) {//第6步骤,下载

                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        Uri uri = Uri.fromParts("package", mActivity.getPackageName(), null);

                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                            //注意这个是8.0新API,直接跳转到允许安装位置来源的页面
                            intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, uri);
                        } else {
                            intent.setData(uri);
                        }

                        if (mFragment != null) {
                            mFragment.startActivityForResult(intent, REQUEST_PERMISSION_SDCARD_SETTING);
                        } else {
                            mActivity.startActivityForResult(intent, REQUEST_PERMISSION_SDCARD_SETTING);
                        }
                    }
                }).
                setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //这里涉及到下载的强制更新,是不是强制更新   强制更新,点取消按钮,退出程序
                        if (IsUpdate) {
                            T.shortToast(mActivity, "此版本需要更新,程序即将退出");
                            MyHandler myHandler = new MyHandler(new UpdateAppUtils());
                            myHandler.sendEmptyMessageDelayed(0, 3000);
                        } else {
                            PreferenceUtils.setInt(FinalData.VERSIONCODE, serviceVersionCode);
                            dialog.dismiss();
                        }
                    }
                }).
                create();
        dialog.show();
    }


    /**
     * 申请权限返回结果时调用,用户是否同意
     *
     * @param requestCode  之前申请权限的请求码
     * @param permissions  申请的权限
     * @param grantResults 依次申请的结果
     */
    public static void onActRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                                     @NonNull int[] grantResults, Activity activity, Fragment fragment) {
        mActivity = activity;
        mFragment = fragment;
        if (requestCode == REQUEST_PERMISSION_SDCARD_6_0) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    checkInstallPermission();
                } else {
                    //有权限去下载
                    downLoad();
                }
            } else {
                getPermissionDialog(0);
            }
        } else if (requestCode == REQUEST_PERMISSION_SDCARD_8_0) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                downLoad();
            } else {
                getPermissionDialog(1);
            }
        } else if (requestCode == REQUEST_PERMISSION_SDCARD_SETTING) {
            //设置页面
            checkPermission();
        }
    }

    static class MyHandler extends Handler {
        WeakReference<UpdateAppUtils> mWeakReference;

        public MyHandler(UpdateAppUtils mUpdateAppUtils) {
            mWeakReference = new WeakReference<UpdateAppUtils>(mUpdateAppUtils);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            exitApp();
        }
    }

    /**
     * 这里使用EventBus像Activity发送消息,当然你也可以使用广播
     */
    private static void exitApp() {
        EventBus.getDefault().post(new EventMessage(EventMessage.Exitapp));
    }
}

UpdateAppService.java

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.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.content.FileProvider;
import android.text.TextUtils;

import com.example.lql.updateappdemo.R;
import com.example.lql.updateappdemo.utils.T;

import java.io.File;

/**
 * 类描述:下载服务
 * 作  者:李清林
 * 时  间:
 * 修改备注:兼容7.0
 */
public class UpdateAppService extends Service {

    public UpdateAppService() {

    }

    /**
     * 安卓系统下载类
     **/
    DownloadManager manager;

    /**
     * 接收下载完的广播
     **/
    DownloadCompleteReceiver receiver;

    /**
     * 初始化下载器
     **/
    private void initDownManager(String downLoadUrl) {
        manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
        receiver = new DownloadCompleteReceiver();

        if (TextUtils.isEmpty(downLoadUrl)) {
            T.shortToast(UpdateAppService.this, "下载地址为空");
            return;
        }

        //设置下载地址
        Uri parse = Uri.parse(downLoadUrl);
        DownloadManager.Request down = new DownloadManager.Request(parse);
        down.setTitle(getResources().getString(R.string.app_name) + ".apk");
        // 设置允许使用的网络类型,这里是移动网络和wifi都可以
        down.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
        // 下载时,通知栏显示途中
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            down.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
        }
        // 显示下载界面
        down.setVisibleInDownloadsUi(true);
        // 设置下载后文件存放的位置
        String apkName = parse.getLastPathSegment();
        down.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, apkName);
        // 将下载请求放入队列
        manager.enqueue(down);
        //注册下载广播
        registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String downLoadUrl = intent.getStringExtra("downLoadUrl");
        // 调用下载
        initDownManager(downLoadUrl);
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        // 注销下载广播
        if (receiver != null) {
            unregisterReceiver(receiver);
        }
        super.onDestroy();
    }


    /**
     * 接受下载完成后的intent
     */
    class DownloadCompleteReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
            if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
                DownloadManager.Query query = new DownloadManager.Query();
                // 在广播中取出下载任务的id
                long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
                query.setFilterById(id);
                Cursor c = manager.query(query);
                if (c.moveToFirst()) {
                    int fileUriIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
                    String fileUri = c.getString(fileUriIdx);
                    String fileName = null;
                    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
                        if (fileUri != null) {
                            fileName = Uri.parse(fileUri).getPath();
                        }
                    } else {
                        //Android 7.0以上的方式:请求获取写入权限,这一步报错
                        int fileNameIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
                        fileName = c.getString(fileNameIdx);
                    }
                    if (null != fileName && !TextUtils.isEmpty(fileName)) {
                        install1(context, fileName);
                    }
                    //停止服务并关闭广播
                    UpdateAppService.this.stopSelf();
                }
            }
        }

        private boolean install1(Context context, String filePath) {
            Intent i = new Intent(Intent.ACTION_VIEW);
            File file = new File(filePath);
            if (file != null && file.length() > 0 && file.exists() && file.isFile()) {
                //判断是否是AndroidN以及更高的版本
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file);
                    i.setDataAndType(contentUri, "application/vnd.android.package-archive");
                } else {
                    i.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
                    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }
                context.startActivity(i);
                return true;
            }
            return false;
        }
    }

}

最后别忘了在清单文件中注册服务。

AndroidManifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.lql.updateappdemo">

    <!--读存储的权限-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!--写存储的权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <!--8.0未知来源的应用权限 更新App-->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

    <application
        android:name=".application.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <!--兼容7.x-->
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.lql.updateappdemo.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

        <activity android:name=".ui.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".service.UpdateAppService"
            android:enabled="true"></service>
    </application>
</manifest>

4、更新内容(2018.7.23):

这次更新为了兼容android8.0。在8.0中增加了允许安装位置来源应用的权限。使用时需要首先在AndroidManifest文件中添加响应权限,然后在去检查权限。整体过程和android6.0动态申请权限的过程相似,但是在具体代码中有所区别。具体使用请查看代码UpdateAppUtils .checkInstallPermission()方法。这里提一句,在申请权限的时候,不会像6.0权限那样有系统提示,这里需要自己去写一个dialog去提示用户。并且跳转到设置页面中手动开启。具体跳转页面逻辑请查看UpdateAppUtils.getPermissionDialog()方法。可以直接跳转到具体页面。

5、就这样结束啦。附上源码下载地址(demo 已更新):

https://github.com/LiQinglin007/UpdateAppDemo

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值