做移动软件开发,必然要涉及软件版本升级。版本检测什么的我就不多说了,网上一大堆,这里主要是在获取新版本APK地址后的下载操作。

第一步:判断任务是否已经存在如果存在,先清除原任务

if (downloadId != 0) {  //根据任务ID判断是否存在相同的下载任务,如果有则清除
            clearCurrentTask(mContext, downloadId);
        }
        downloadId = downLoadApk(mContext, url, describeStr);

第二步:开启下载任务

public static long downLoadApk(Context context, String url, String describeStr) {
        // 得到系统的下载管理
        DownloadManager manager = (DownloadManager) context.getSystemService(context.DOWNLOAD_SERVICE);
        Uri uri = Uri.parse(url);
        // 以下两行代码可以让下载的apk文件被直接安装而不用使用Fileprovider,系统7.0或者以上才启动。
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            StrictMode.VmPolicy.Builder localBuilder = new StrictMode.VmPolicy.Builder();
            StrictMode.setVmPolicy(localBuilder.build());
        }
        DownloadManager.Request requestApk = new DownloadManager.Request(uri);
        // 设置在什么网络下下载
        requestApk.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
        // 下载中和下载完后都显示通知栏
        requestApk.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        if (saveFile.exists()) {    //判断文件是否存在,存在的话先删除
            saveFile.delete();
        }
        requestApk.setDestinationUri(Uri.fromFile(saveFile));
        // 表示允许MediaScanner扫描到这个文件,默认不允许。
        requestApk.allowScanningByMediaScanner();
        // 设置下载中通知栏的提示消息
        requestApk.setTitle("XXXX更新下载");
        // 设置设置下载中通知栏提示的介绍
        requestApk.setDescription(describeStr);

        // 7.0以上的系统适配
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            requestApk.setRequiresDeviceIdle(false);
            requestApk.setRequiresCharging(false);
        }
        // 启动下载,该方法返回系统为当前下载请求分配的一个唯一的ID
        long downLoadId = manager.enqueue(requestApk);
        return downLoadId;
    }

开启下载任务后返回了任务ID,这是任务的唯一标识。

第三步:下载完成后调用广播,通知系统去执行安装操作点击任务栏时判断任务是否下载完成

if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
            DownloadApkUtils.installApk(context);
        } else if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {
            // 如果还未完成下载,用户点击Notification ,跳转到下载中心
            Intent viewDownloadIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
            viewDownloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(viewDownloadIntent);
        }

第四部:检测和安装APK

public static void installApk(Context context) {
        downloadId = 0;

        Intent intent = new Intent(Intent.ACTION_VIEW);
        try {
            String[] command = {"chmod", "777", saveFile.getAbsolutePath()};
            ProcessBuilder builder = new ProcessBuilder(command);
            builder.start();
        } catch (Exception ignored) {
            ignored.printStackTrace();
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(context, "<Your Package Name>.fileprovider", saveFile);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(saveFile), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        context.startActivity(intent);
    }

下面给出关键类的代码:

(1)DownloadApkUtils:

public class DownloadApkUtils {
    private static File saveFile;
    private static long downloadId = 0;

    public static void startDownload(Context mContext, String url, String describeStr) {
        initFile();
        if (downloadId != 0) {  //根据任务ID判断是否存在相同的下载任务,如果有则清除
            clearCurrentTask(mContext, downloadId);
        }
        downloadId = downLoadApk(mContext, url, describeStr);
    }

    private static void initFile() {
        if (saveFile == null)
            saveFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "XXXX.apk");
    }

    public static long downLoadApk(Context context, String url, String describeStr) {
        // 得到系统的下载管理
        DownloadManager manager = (DownloadManager) context.getSystemService(context.DOWNLOAD_SERVICE);
        Uri uri = Uri.parse(url);
        // 以下两行代码可以让下载的apk文件被直接安装而不用使用Fileprovider,系统7.0或者以上才启动。
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            StrictMode.VmPolicy.Builder localBuilder = new StrictMode.VmPolicy.Builder();
            StrictMode.setVmPolicy(localBuilder.build());
        }
        DownloadManager.Request requestApk = new DownloadManager.Request(uri);
        // 设置在什么网络下下载
        requestApk.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
        // 下载中和下载完后都显示通知栏
        requestApk.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        if (saveFile.exists()) {    //判断文件是否存在,存在的话先删除
            saveFile.delete();
        }
        requestApk.setDestinationUri(Uri.fromFile(saveFile));
        // 表示允许MediaScanner扫描到这个文件,默认不允许。
        requestApk.allowScanningByMediaScanner();
        // 设置下载中通知栏的提示消息
        requestApk.setTitle("XXXX更新下载");
        // 设置设置下载中通知栏提示的介绍
        requestApk.setDescription(describeStr);

        // 7.0以上的系统适配
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            requestApk.setRequiresDeviceIdle(false);
            requestApk.setRequiresCharging(false);
        }
        // 启动下载,该方法返回系统为当前下载请求分配的一个唯一的ID
        long downLoadId = manager.enqueue(requestApk);
        return downLoadId;
    }

    /**
     * 下载前先移除前一个任务,防止重复下载
     *
     * @param downloadId
     */
    public static void clearCurrentTask(Context mContext, long downloadId) {
        DownloadManager dm = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
        try {
            dm.remove(downloadId);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }
    }

    public static void installApk(Context context) {
        downloadId = 0;

        Intent intent = new Intent(Intent.ACTION_VIEW);
        try {
            String[] command = {"chmod", "777", saveFile.getAbsolutePath()};
            ProcessBuilder builder = new ProcessBuilder(command);
            builder.start();
        } catch (Exception ignored) {
            ignored.printStackTrace();
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(context, "<Your Package Name>.fileprovider", saveFile);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(saveFile), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        context.startActivity(intent);
    }

}

要想安装要有权限,当然还要有读写权限以及网络请求权限,这里就不列出来了

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

(2)下载的广播接收者

public class DownloadReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
            DownloadApkUtils.installApk(context);
        } else if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {
            // 如果还未完成下载,用户点击Notification ,跳转到下载中心
            Intent viewDownloadIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
            viewDownloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(viewDownloadIntent);
        }
    }
}

既然用到了广播接收这,记得在配置文件中注册;

<receiver android:name=".receiver.DownloadReceiver">
      <intent-filter>
           <action android:name="android.intent.action.DOWNLOAD_SERVICE"/>
      </intent-filter>
</receiver>

执行下载操作的Activity中注册广播

private boolean isRegisterReceiver = false;
    /**
     * 注册下载成功的广播监听
     */
    private void setReceiver() {
        if (!isRegisterReceiver) {
            DownloadReceiver receiver = new DownloadReceiver();
            IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
            this.registerReceiver(receiver, intentFilter);
            isRegisterReceiver = true;
        }
    }

都知道android的适配是很烦人的,但是不得不适配。下载过程中涉及了文件的读写,那就必须要适配7.0 使用Provider。

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.retrofitdemo.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

资源文件夹下创建xml文件夹,创建file_paths。内容如下

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>

最后就是调用了,由于前面准备工作做得很好,这一步就非常简单了,只要一行代码

DownloadApkUtils.startDownload(MainActivity.this,"下载地址","下载任务描述");

到此版本更新时APK的下载及自动安装操作完成。如果对你有帮助,点个赞再走吧。(手动滑稽)