美文网首页高级UIandroid开发技巧
Android下载更新功能(含强制更新)完整实现

Android下载更新功能(含强制更新)完整实现

作者: Zig_Zag | 来源:发表于2019-08-19 11:45 被阅读0次

检查版本忽略具体的网络请求部分实现
已知服务端返回数据

{
        public String description;        //版本描述
        public String downloadLink;   //下载链接
        public boolean must;              //是否是强制更新
        public String versionString;    //版本号(如“v1.2.1”)
        public boolean want;              //是否是需要更新
 }
首先需要弹出一个弹窗,显示效果大致如图 1566186266(1).jpg

显示弹窗简单实现代码如下:

/**
     * 显示更新提示框
     */
    private void showUpDateDialog(boolean must) {
        if (updateDialog != null && updateDialog.isShowing()) {
            return;
        }
        updateDialog = new Dialog(context, R.style.custom_dialog);
        updateDialog.setOwnerActivity((Activity) context);
        View contentView = LayoutInflater.from(context).inflate(R.layout.app_upgrade_dialog, null);

        Button cancelBtn = contentView.findViewById(R.id.btn_cancel);
        Button confirmBtn = contentView.findViewById(R.id.btn_confirm);
        TextView versionName = contentView.findViewById(R.id.tv_version_name);

        versionName.setText("版本过低需要更新到最新版本" + versionString);

        //取消按钮点击事件
        cancelBtn.setOnClickListener(v -> {
                    if (must) {
                        //如果是强制更新,点击取消更新直接退出程序
                        android.os.Process.killProcess(android.os.Process.myPid());
                    } else {
                        //如果不是强制更新,点击取消更新关闭弹窗
                        updateDialog.dismiss();
                    }
                }
        );
         //确定按钮点击事件
        confirmBtn.setOnClickListener(v -> {
                    updateDialog.dismiss();
                    //得到安装包的下载路径
                    String filePath = getInstallFilePath();
                    File installFile = new File(filePath);
                    if (installFile.exists()) {
                        //在已经下载好文件的情况下,根据下载路径直接安装
                        installApp(filePath);
                    } else {
                        //发现在安装包下载路径没有文件,需要下载
                        DownloadNewApk();
                    }
                }
        );

        updateDialog.setContentView(contentView);
        updateDialog.setCancelable(false);
        updateDialog.setCanceledOnTouchOutside(false);
        updateDialog.show();
    }

代码中有一个style:

R.style.custom_dialog

<style name="custom_dialog" parent="@android:style/Theme.Dialog">
        <!-- 边框 -->
        <item name="android:windowFrame">@null</item>
        <!-- 是否浮现在activity之上 -->
        <item name="android:windowIsFloating">true</item>
        <!-- 半透明 -->
        <item name="android:windowIsTranslucent">true</item>
        <!-- 无标题 -->
        <item name="android:windowNoTitle">true</item>
        <item name="android:background">@android:color/transparent</item>
        <!-- 背景透明 -->
        <item name="android:windowBackground">@android:color/transparent</item>
        <!-- 模糊 -->
        <item name="android:backgroundDimEnabled">true</item>
        <!-- 遮罩层透明度50% -->
        <item name="android:backgroundDimAmount">0.5</item>
    </style>

和三个方法:

//得到安装包的下载路径
getInstallFilePath();
//在已经下载好文件的情况下,根据下载路径直接安装
installApp(filePath);
//发现在安装包下载路径没有文件,需要下载
DownloadNewApk();

代码分别如下:

private String getInstallFilePath() {
        String filePackagePath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/";
        String fileName = versionString + ".apk";
        Logger.wtf(filePackagePath + fileName);
        String installFilePath = filePackagePath + fileName;
        return installFilePath;
}

getInstallFilePath()方法获得路径是app内部路径,即
/Android/data/包名/files/Download
这样的话app卸载以后该文件夹也会清空
此方法最终返回的String路径是
/Android/data/包名/files/Download/文件名.apk

//普通安装
private void installApp(String apkPath) {
        try {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            //版本在7.0以上是不能直接通过uri访问的
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                File file = (new File(apkPath));
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                Uri contentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", file);
                intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
            } else {
                intent.setDataAndType(Uri.fromFile(new File(apkPath)),
                        "application/vnd.android.package-archive");
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            startActivity(intent);
            //必须加入,不然不会显示安装成功界面
            android.os.Process.killProcess(android.os.Process.myPid());
        } catch (Exception e) {
            e.printStackTrace();
        }
}

installApp(String apkPath)方法中,需要注意两点:

1.为了提高私有目录的安全性,防止应用信息的泄漏,从 Android 7.0 开始,应用私有目录的访问权限被做限制。具体表现为,开发人员不能够再简单地通过 file:// URI 访问其他应用的私有目录文件或者让其他应用访问自己的私有目录文件。同时,也是从 7.0 开始,StrictMode 策略禁止开发人员在应用外部公开 file:// URI。具体表现为,当我们在应用中使用包含 file:// URI 的 Intent 离开自己的应用时,程序会发生故障。

开发中,如果我们在使用 file:// URI 时忽视了这两条规定,将导致用户在 7.0 及更高版本系统的设备中使用到相关功能时,出现 FileUriExposedException 异常,导致应用出现崩溃闪退问题。而这两个过程的替代解决方案便是使用 FileProvider

简单说来即:想要让系统的安装应用程序A获得当前需更新程序B的私有路径下的某文件,这就是跨应用间的资源共享,7.0以上对此做了保护,需要按照新的方式获取,具体实现可参考关于 Android 7.0 适配中 FileProvider 部分的总结

2.在打开系统安装程序应用后,一定要将当前app杀死,首先是防止在强制更新情况下,在安装界面用户点击了“取消”而没有更新,退回了当前app界面,如果此时app没有做出相应的拦截处理,那就会停留在app可以继续使用而无法达到“强制更新”的目的,再一个原因就是,如果当前app没有杀死,则不会出现如下安装成功的界面 系统安装成功界面.png
/**
     * 下载新apk
     */
    private void DownloadNewApk() {
        File installFile = new File(getInstallFilePath());
        DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        Uri uri = Uri.parse(downloadLink);
        DownloadManager.Request request = new DownloadManager.Request(uri);
        request.setDestinationUri(Uri.fromFile(installFile));
        long downloadId = downloadManager.enqueue(request);
        registerDownLoadFinishReceiver(downloadId);

        showDownloadDialog(downloadId);
    }
private void registerDownLoadFinishReceiver(long downloadId) {
        IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
        BroadcastReceiver receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                if (downloadId == reference) {
                    // 下载完成后取消监听
                    context.unregisterReceiver(this);
                    // 安装应用
                    installApp(getInstallFilePath());
                }
            }
        };
        context.registerReceiver(receiver, filter);
    }
private void showDownloadDialog(long downloadId) {
        Dialog dialog = new Dialog(context, R.style.custom_dialog);
        dialog.setOwnerActivity((Activity) context);
        View contentView = LayoutInflater.from(context).inflate(R.layout.app_download_dialog, null);

        ProgressBar progressBar = contentView.findViewById(R.id.pb_download_progress);

        dialog.setContentView(contentView);
        dialog.setCancelable(!mustUpdate);
        dialog.setCanceledOnTouchOutside(!mustUpdate);
        dialog.show();

        new Thread(new Runnable() {

            @Override
            public void run() {

                boolean downloading = true;

                while (downloading) {

                    DownloadManager.Query q = new DownloadManager.Query();
                    q.setFilterById(downloadId);

                    Cursor cursor = ((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE)).query(q);
                    cursor.moveToFirst();
                    int bytes_downloaded = cursor.getInt(cursor
                            .getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
                    int bytes_total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));

                    if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) {
                        downloading = false;
                        dialog.dismiss();
                    }
                    final int dl_progress = (int) ((bytes_downloaded * 100l) / bytes_total);
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            progressBar.setProgress(dl_progress);
                            Logger.wtf("dl_progress = " + dl_progress);
                        }
                    });
                    cursor.close();
                }
            }
        }).start();
    }

显示下载进度dialog 如图


下载进度dialog.png

截至这里,简单的下载安装完整流程就完毕了

留坑
下载同时在通知列表显示自定义下载进度通知条,还有下载完点击通知条继续安装

相关文章

网友评论

    本文标题:Android下载更新功能(含强制更新)完整实现

    本文链接:https://www.haomeiwen.com/subject/zykssctx.html