这个问题困扰了我整整一天时间,所以决定记录下来!
我要说的是这个错误其实并不是因为7.0私密文件夹权限引起的错误,因为AndroidN出了这么久,大家也都应该对FileProvider有所了解了。
1.下载
我采用的自然是简单的系统自带的DownloadManager方式来下载
/**
* 下载APK文件
*/
private void downloadApk(String downloadUrl) {
clearApk(getString(R.string.app_name_en)+".apk");
//TODO 1.封装下载请求
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));
//TODO 2.下载标题,描述
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle(getString(R.string.app_name_en));
request.setDescription(getString(R.string.app_name_en)+"下载");
// TODO 3.设置下载文件的保存位置
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,getString(R.string.app_name_en)+".apk");
// TODO 4.获取下载管理器服务的实例, 添加下载任务
DownloadManager manager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);// 将下载请求加入下载队列, 返回一个下载ID
}
public static File clearApk(String apkName) {
File apkFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), apkName);
if (apkFile.exists()) {
apkFile.delete();
}
return apkFile;
}
2.注册广播
3.广播通知下载完毕,并开始安装
public class DownloadManagerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(action)) {
L.i("用户点击了通知");
// 点击下载进度通知时, 对应的下载ID以数组的方式传递
long[] ids = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
L.i("ids: " + Arrays.toString(ids));
} else if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
/*
* 获取下载完成对应的下载ID, 这里下载完成指的不是下载成功, 下载失败也算是下载完成,
* 所以接收到下载完成广播后, 还需要根据 id 手动查询对应下载请求的成功与失败.
*/
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
L.i("id: " + downloadId);
// 根据获取到的ID,使用上面第3步的方法查询是否下载成功
DownloadManager manager = (DownloadManager) MyApplication.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor cursor = manager.query(query);
if (!cursor.moveToFirst()) {
cursor.close();
return;
}
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
String localFilename;
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
localFilename = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
}else{
localFilename = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
}
if (status == DownloadManager.STATUS_SUCCESSFUL) {
T.showLong(context,"下载完成,点击通知栏安装!");
System.out.println("下载成功, 打开文件, 文件路径: " + localFilename);
// setPermission(localFilename);
installApk(context,localFilename);
}
}
}
/**
* 安装APK
*/
private void installApk(Context context,String filePath) {
Intent intent = new Intent(Intent.ACTION_VIEW);
File file = new File(Environment.getExternalStorageDirectory()+"/"+Environment.DIRECTORY_DOWNLOADS,context.getString(R.string.app_name_en)+".apk");
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
// File file = new File(context.getExternalFilesDir(null)+"/"+ Environment.DIRECTORY_DOWNLOADS,context.getString(R.string.app_name_en)+".apk");
Uri apkUri= FileProvider.getUriForFile(context, "cq.cake.shopingmarket.three.fileprovider", file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
L.i("要安装的apk路径为=="+apkUri.getPath());
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
}else{
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
context.startActivity(intent);
}
/**
* 提升读写权限
* @param filePath 文件路径
* @return
* @throws IOException
*/
// public static void setPermission(String filePath) {
// String command = "chmod " + "777" + " " + filePath;
// Runtime runtime = Runtime.getRuntime();
// try {
// runtime.exec(command);
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
}
由于今天主要是说出现错误的原因,所以三步就先把正确的代码贴了出来。这个问题也困扰了我一整天。其实最后证实主要原因还是路径不正确导致的,为什么会犯这么低级的错误呢!因为我把这个解析包错误的原因看得太宽泛了。
1.修正一,百度了这个错误,有的人说打的包不正确,应该都是debug包或正式包,但我试了试问题依旧
2.修正二,有的人说是因为AndroidStudio打v1和v2包不兼容,所以有这个问题,我想说我一直都是用的v1的方式打包
3.修正三,有的人说你的apk就不完整,但是我点击通知栏下载好的,却可以安装,说明包没有问题
4.修正四,有的人说你的7.0的系统需要fileprovider的方式来访问,我想说如果不用FileProvider,来访问我的应用直接就死了,所以也不是这个原因
5.修正五,有的说是权限不够,Linux需要提权,所以我在使用FileProvider前,调用了如下方法来提升权限,发现问题依旧
/**
* 提升读写权限
* @param filePath 文件路径
* @return
* @throws IOException
*/
// public static void setPermission(String filePath) {
// String command = "chmod " + "777" + " " + filePath;
// Runtime runtime = Runtime.getRuntime();
// try {
// runtime.exec(command);
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
6.修正六,最后只能确定是在路径上有问题,我就一步一步的打印路径,看看到底哪里有问题,下载的路径,广播后通知的路径,安装前的路径,最后发现安装的路径居然和广播的路径不同
修正.png
居然在安装的时候在前面多了一个download,这个是在file_paths里面的别名,意义就是隐藏掉前面的路径名称,最开始我以为是名称不是Download引起的,所以后面加个一个download的root-path,却发现这样毫无作用。
所以最关键的问题还是出现installApk这个方法里面,最开始我是通过广播得到的下载文件的路径和名称:
String localFilename;
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
localFilename = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
}else{
localFilename = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
}
然后把这个路径名称传递进入installApk方法,但是在这个安装方法里面由于要适配7.0,所以使用了FileProvider来获取Uri,就导致了路径被变更了,但是却不知道如何来修改这个路径,毕竟广播过来的这个路径确实是正确的,只是因为使用了FileProvider而导致的路径不正确(如上图所示)。最后在群里面询问了遇到同样问题的同学,才知道他是自己拼的File:
File file = new File(Environment.getExternalStorageDirectory()+"/"+Environment.DIRECTORY_DOWNLOADS,context.getString(R.string.app_name_en)+".apk");
这样拼接后果然出现了,期待已久的安装界面!到此这个问题总算解决了,所以广播后查询的下载目录localFilename也就失去了意思,也可以省略了!唯一有用的就是下载完成后是否真正成功的状态,因为下载完成并不代表下载成功,记录下来给遇到同样困惑的同学!
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
T.showLong(context,"下载完成,点击通知栏安装!");
installApk(context);
}