天天看点

Android 版本更新(非热更新) 适配7.0更新 以及三星 note系列读取内存相关目录无权限问题

目录

[TOC]

个人认为热更新虽然是好 但是 像ios直接拒绝使用热更新 也不无道理 本人一直使用传统的版本升级方式 请求接口 返回下载链接 然后下载文件 用Intent吊起安装 android7.0之前是没有问题的 也没有遇到三星note系列的问题(测试机是三星note4 5.0的机器好像。。。) 后来使用note系列的更新版本都有问题 这里分享给大家 避免大家踩坑

  • 使用系统自带的Api(DownloadManager)
  • 请求服务器接口然后配置一些相关的参数(网上百度有很多关于这个Api的介绍)
  • 设置File路径(这里坑坑的 基本上主要的问题都在这)
  • 判断版本>=7.0必须自定义FileProvider(网上解释好像说7.0为了什么什么安全 具体没有了解)
  • <7.0正常使用Intent安装即可(这里的坑就是三星note系列 其他的比如vivo,魅族,华为,非三星note系 列,小米。。没有遇到此类问题)

代码块

/**
 * //
 * 版本更新下载类
 */

public class DownloadController {
    private DownloadManager mDownManager;
    private Context mCtx;
    private String substr;
    private static DownloadController instance;
    String path_apk = "";
    private List<Long> mIdList = new ArrayList<Long>();

    private DownloadController(Context context) {
        this.mCtx = context;
        mDownManager = (DownloadManager) mCtx.getSystemService(Context.DOWNLOAD_SERVICE);
    }

    public static DownloadController getInstance(Context context) {
        if (instance == null) {
            instance = new DownloadController(context);
        }
        return instance;
    }

    public long startLauncherDownLoader(String url, long downId) {
        if (TextUtils.isEmpty(url)) {
            return -1;
        }
        if (mIdList.contains(downId)) {
            Toast.makeText(mCtx, "正在下载...", Toast.LENGTH_SHORT).show();
            return downId;
        }
        Toast.makeText(mCtx, "开始下载", Toast.LENGTH_SHORT).show();
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
        request.setVisibleInDownloadsUi(true);
        String appNmae = mCtx.getResources().getString(R.string.app_name);
        request.setTitle(appNmae);
        request.setDescription(appNmae + "正在下载...");
        request.setAllowedOverRoaming(true);
        // 设置文件存放目录
        substr = url.substring(url.lastIndexOf("/") + );
        File file = getDownloadFile();
        path_apk = file.getPath() + "/" + substr;
        Log.i("apkpatch", substr + "================" + path_apk);
        if (file != null) {
            request.setDestinationUri(Uri.fromFile(new File(file.getPath() + "/" + substr)));
        } else {
            request.setDestinationInExternalFilesDir(mCtx, "downloadqzxq",substr);
        }
        long id = mDownManager.enqueue(request);
        mIdList.add(id);
        return id;
    }

    /**
     * 安装apk
     *
     * @param id
     */
    public void installApk(Context context, Long id) {
        try {
            Intent install = new Intent(Intent.ACTION_VIEW);
            Uri downloadFileUri = mDownManager.getUriForDownloadedFile(id);
            install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");
            install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(install);
        } catch (Exception e) {
        }
    }

    /**
     * 安装apk
     *
     * @paramid
     */
    public void installApkByFile(File file,Context mContext) {
        try {
            Intent install = new Intent(Intent.ACTION_VIEW);
            //7.0系统的安装
            if (Build.VERSION.SDK_INT>=){
                Uri apkUri = FileProvider.getUriForFile(mContext, "com.qizi.yqxq.fileprovider", file);
                install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                install.setDataAndType(apkUri, "application/vnd.android.package-archive");
            }
            //<24的安装
            else {
                Uri downloadFileUri = Uri.fromFile(file);
                install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");
                install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            mCtx.startActivity(install);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public File isExistFile(String url) {
        substr = url.substring(url.lastIndexOf("/") + );
        File file = getDownloadFile();
        if (file != null && !TextUtils.isEmpty(url)) {
            File apkFile = new File(file.getPath() + "/" + url.substring(url.lastIndexOf("/") + ));
            if (apkFile.exists()) {
                return apkFile;
            } }
        return null;
    }
    public void installAPK(Context mContext) {
        installApkByFile(new File(path_apk),mContext);
    }

    public File getDownloadFile() {
        File files = new File(Environment.getExternalStorageDirectory() + "/" + "downloadqzxq");
        File[] ffs = files.listFiles();
        File file2 = null;
        if (ffs != null) {
            for (int i = ; i < ffs.length; i++) {
                if (ffs[i].getName().equals(substr)) {
                    file2 = new File(substr);
                }
            }
        }
        if (file2 != null) {
            if (!file2.exists()) {
                file2 = files;
            }
        } else {
            file2 = files;
        }
        return file2;
    }
}

           

以上是我自己的一个下载安装类 咱们先看一下startLauncherDownLoader这个方法 代码中具体的设置 自己可以百度看一下配置 网上有很多介绍 我只选择重要的去解释

File file = getDownloadFile();
           

这个方法就是我先去检测我的下载目录是否有我需要更新的apk包,很多人会问 为什么要去检测,这里给大家解释一下 如果正常的安装当弹出安装窗口的时候 用户点击安装就行了 但是如果用户点击取消呢 下一次进来的时候 我们还是提示用户更新 当用户点击更新按钮的时候 我们难道还要让用户去下载吗 这个是肯定不行的 如果之前用户点击取消 第二次再进app的时候点击更新按钮 我们只要吊起本地我们之前下载好的apk安装就行了 因为用户第一次已经下载了 只不过是取消了安装。。。这个解释够清楚了吧接下来看一行代码

File files = new File(Environment.getExternalStorageDirectory() + "/" + "downloadqzxq");
           

我们看这个 downloadqzxq 这个目录是我手动设置的一个名字(之前用的Environment.DIRECTORY_DOWNLOADS 内存自带的Downloads文件夹) 这个地方就有坑了 三星note系列的手机直接提示我没有权限 可是我明明在minefist里面加内存的读取权限 也申请运行时权限了 结果是不行 换其他的手机都可以 只要三星note系列就不行 有兴趣的可以自己试一下 。。。。(这是一个坑)

7.0的适配

  • 自定义fileProvider
  • 安装的时候去判断版本
<!--适配版本升级>=的情况-->
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="包名.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <!--元数据-->
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
           

这里的最好写包名 容易区分 (以上代码在minefest里面配置)

exported:要求必须为false,为true则会报安全异常。grantUriPermissions:true,表示授予 URI 临时访问权限

file_paths如下

<resources>
    <paths>
        <external-path
            name="mydemo"
            path="downloadqzxq" />
    </paths>
</resources>
           

这段代码指定共享的目录(在res下面新建一个xml文件夹并且新建文件就行 这里的downloadqzxq 必须必须必须跟咱们之前设置的文件夹名字一样 否则会找不到对应的文件 进而抛出异常)

- 代表的根目录: Context.getFilesDir()

- 代表的根目录: Environment.getExternalStorageDirectory()

- 代表的根目录: getCacheDir()

public void installApkByFile(File file,Context mContext) {
        try {
            Intent install = new Intent(Intent.ACTION_VIEW);
            //系统的安装
            if (Build.VERSION.SDK_INT>=){
                Uri apkUri = FileProvider.getUriForFile(mContext, "com.qizi.yqxq.fileprovider", file);
                install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                install.setDataAndType(apkUri, "application/vnd.android.package-archive");
            }
            //<的安装
            else {
                Uri downloadFileUri = Uri.fromFile(file);
                install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");
                install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            mCtx.startActivity(install);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
           

这里的名字com.qizi.yqxq.fileprovider 必须有我们之前配置的名字一样

这里我们去判断版本更新就行了 下面的代码是我自己的广播

public class DownloadCompleteReceiver extends BroadcastReceiver {
    DownloadController downloadController;
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
            downloadController = DownloadController.getInstance(context);
            downloadController.installAPK(context);
        }
    }
}
//这是我在main里面自己的方法 弹窗去提示版本升级 主要是点击里面的代码  拿到下载的链接 然后调用封装好的下载类去下载   
  public void showUpdatDialog(final Boolean isForce, String version, final String url, String desc) {
        updateDialog = new VersionUpdateDialog(mContext);
        if (!MainActivity.this.isFinishing()) {
            flaotDialog.dismiss();
            updateDialog.show(version, desc, isForce, new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    downloadController = DownloadController.getInstance(mContext);
                    File apkFile = downloadController.isExistFile(url);
                    if (apkFile != null) {
                        downloadController.installApkByFile(apkFile, mContext);
                    } else {
                        downloadController.startLauncherDownLoader(url, -);
                    }
                    updateDialog.dismiss();
                    if (isForce) {
                        finish();
//                        exitMainApp();
                    }
                }
            }, new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    updateDialog.dismiss();
                    if (isForce) {
                        exitMainApp();
                    }
                }
            });
        }
    }
           

总结

  1. 7.0适配必须自定义FileProvider
  2. 三星note系列手机适配 不要使用系统自带的目录 为了统一我们就自定义一个目录
  3. 大家多多踩坑才能有收获啊 !!!!!!!!!!!!!! 大家有问题 可以留言一起交流