Android 下载安装APK踩坑纪录

前言:最近项目中需要有个静默下载,下载完成后弹框告诉用户是否下载的需求。开始觉得 so easy !,真的调研写demo的时候发现过程还是蛮曲折的。下面会详细述说。

注意

  1. 待下载安装的 APK 一定要是签过名的,我当时是写 demo 嘛,所以上传到测试服务器的 APK是未签名的,结果无法安装。
  2. 写demo的项目中,下载安装的 APK 一定要是此 DEMO 项目中打包生成的签名 APK。我一开始图方便,直接去生成服务器上下载公司的 APK,结果下载完成后,安装没有任何效果,IDE 上也没有报错。

上面的注意点说完了,直接开始步骤了。

一、在 AndroidManifest.xml 中加入相应的权限:
    <!--apk 安装 -->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
    <!--网络 -->
     <uses-permission android:name="android.permission.INTERNET" />

由于我是 APK 直接下载到应用的目录,所以没申请 SD 卡的读写权限,这个大家自行考虑。
因为设计到下载,网络权限肯定不要忘记添加。

二、下载的代码如下:
public void downloadAPK(File apkFile) {
        //准备用于保存APK文件的File对象 : /storage/sdcard/Android/package_name/files/xxx.apk
        if (apkFile.exists()) {
            apkFile.delete();
        }
        // 开启子线程, 请求下载APK文件
        new Thread(() -> {
            try {
                long start = System.currentTimeMillis();
                //1. 得到连接对象
                String path = "https://xxx_pro_v3330105.apk";
                URL url = new URL(path);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                //2. 设置              
                connection.setConnectTimeout(30_000);
                connection.setReadTimeout(3_0000);
                //3. 连接
                connection.connect();
                //4. 请求并得到响应码200
                int responseCode = connection.getResponseCode();
                if (responseCode == 200) {
                    //设置dialog的最大进度
                    //5. 得到包含APK文件数据的InputStream
                    InputStream is = connection.getInputStream();
                    //6. 创建指向apkFile的FileOutputStream
                    FileOutputStream fos = new FileOutputStream(apkFile);
                    //7. 边读边写
                    byte[] buffer = new byte[1024];
                    int len = -1;
                    int progress = 0;
                    while ((len = is.read(buffer)) != -1) {
                        fos.write(buffer, 0, len);
                        progress += len;
                        Log.e(TAG, " downloading progress = " + progress);
                    }
                    fos.close();
                    is.close();
                }
                //9. 下载完成, 关闭
                connection.disconnect();
                long end = System.currentTimeMillis();
                Log.e(TAG, "--------------下载完成-------------" + (end - start));

                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e(TAG, "--------------完成-------------" + (end - start));
                        Toast.makeText(context, "下载完成,耗时:" + (end - start), Toast.LENGTH_LONG).show();                     
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

下载方法的测试代码如下:

public void testDownLoadApk() {
    // xxx_pro_v3330105.apk 表示生产 apk 的名称
    File apkFile = new File(context.getExternalFilesDir(null), "xxx_pro_v3330105.apk");
    downloadAPK(apkFile);
  }

下载没啥好说的了,遇到的坑都是安装的坑。

三、apk 安装
public void installAPK(File apkFile) {
        if (apkFile.exists() && apkFile.canRead()) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            // 读取uri权限
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            // 安卓7.0及以上,需要使用 FileProvider 来访问私有文件
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile);
                intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
            } else {
                intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
            }
            context.startActivity(intent);
        } else {
            Toast.makeText(context, "安装文件不存在", Toast.LENGTH_LONG).show();
        }
    }

由于需要使用 FileProvider ,所以我们需要在 AndroidManifest.xml 文件中进行声明:

 <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="应用包名.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path" />
        </provider>

注意: android:exported 要设置为 false. file_path 需要在 res 目录中 新建 xml 文件夹,然后新进此文件,如下图:
Android 下载安装APK踩坑纪录_第1张图片
file_path 内容如下:

<paths>
    <external-path path="Android/data/应用包名/" name="files_root" />
    <external-path path="." name="external_storage_root" />
</paths>

此时,运行安装代码,报错,如下:
在这里插入图片描述
为啥报这个错误呢,因为我引用的第3方SDK中也声明了 FileProvider,怎么办呢?也很简单,自定义一个 FileProvider,如下:
Android 下载安装APK踩坑纪录_第2张图片
然后在 AndroidManifest 中使用自定义的 FileProvider:

        <provider
            android:name=".YKFileProvider"
            android:authorities="包名.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path" />
        </provider>

此问题得以解决。最后也顺利下载安装完成。
注意,我这里 使用的是 androidx.core.content.FileProvider

你可能感兴趣的:(移动开发,android,java,开发语言)