笔者最近接到个新需求 是使用RN框架(react-native) 来结合android 开发的APP。要实现用户点击检查更新,可以自动更新安装的功能。
好了,我们先整理下逻辑,第一,先判断笔者自定义的下载目录下有没有包名跟当前APP包名一样的APK包,如果有的话获取该包的一些信息 (包括包名,版本号,版本名,图标,名称等一系列信息,这里我贴上我找了很久的一个大神写的代码,有需要可以自己copy)

如果需要versionCode 就写
这个方法的参数:只需要传入上下文和未安装的APK的位置 记住了 要标明未安装的APK的名称后缀 如下图
// apkPath=Environment.getExternalStorageDirectory()+"/"+"TestB.apk"
然后接上面所说,如果找到了 就判断版本号是否大于当前APP的版本号(如果大于就直接启动更新安装,否则就重新下载在进行更新安装),获取当前APP的版本号的方法如下(传入的参数 :上下文和包名 )
// 獲取版本號 public static int getVersionCode(Context context, String paname) { int version_code = 0; PackageManager pm = context.getPackageManager(); try { version_code = pm.getPackageInfo(paname, 0).versionCode; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); version_code = 0; } return version_code; }
为什么这么做的原因是 这个一般是用户点击下载了 然后因为什么原因取消安装了或者放弃了安装,导致下载的APK已存在,如果用户在此点击检测更新 又重新下载 很影响用户体验和浪费用户流量(不过流量浪费不浪费也不管俺的事哈哈).现在讲讲 这里遇到的问题。
private void getUninstallAPKInfo(Context ctx, String archiveFilePath, String url, String Pname, Uri uri) { // archiveFilePath=Environment.getExternalStorageDirectory()+"/"+"TestB.apk" String versionName = null; //未安装APK 版本名 String versionCode = null; //未安装APK 版本号 String appName = null; //未安装APK 名字 String pakName = null; //未安装APK 包名 if (archiveFilePath == null || archiveFilePath.equals("")) { } else { Log.i("APK包路径", archiveFilePath); } PackageManager pm = ctx.getPackageManager(); PackageInfo pakinfo = pm.getPackageArchiveInfo(archiveFilePath, PackageManager.GET_ACTIVITIES); if (pakinfo != null) { Log.i("获取APK信息", "文件夾内有包----第一层"); ApplicationInfo appinfo = pakinfo.applicationInfo; versionName = pakinfo.versionName; versionCode = String.valueOf(pakinfo.versionCode); Drawable icon = pm.getApplicationIcon(appinfo); appName = (String) pm.getApplicationLabel(appinfo); pakName = appinfo.packageName; String text = "versionName = " + versionName + " , appName = " + appName + " , pakName = " + pakName + ",versionCode=" + versionCode; Log.i("APK信息", text); int i = Integer.parseInt(versionCode); int a = i / 10000; int c = i % 100; int b = i / 100 % 100; int realCode = Integer.parseInt("" + a + b + c); // 未安裝 int versioncode = Utils.getVersionCode(mContext, Pname);//該APP版本號 if (realCode > versioncode) { Log.i("获取APK信息比較", "未安裝APK版本號大于当前APP版本号"); if (Build.VERSION.SDK_INT >= 24) { Log.i("android版本号", "7.0"); // MainActivity.getInstance().install(uri); Intent intent2 = new Intent(Intent.ACTION_VIEW); intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent2.setAction(Intent.ACTION_VIEW); intent2.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); intent2.setDataAndType(Uri.parse(uri.toString()), "application/vnd.android.package-archive"); Log.i("安裝地址", uri.toString()); mContext.startActivity(intent2); } else { Log.i("android版本号", "小于7.0"); // MainActivity.getInstance().install(uri); Intent install = new Intent(Intent.ACTION_VIEW); //表明不是未知来源 install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); install.setDataAndType(Uri.parse("file://" + uri.toString()), "application/vnd.android.package-archive"); Log.i("安裝地址", "file://" + uri.toString()); mContext.startActivity(install); } } else { Log.i("获取APK信息比較", "未安裝APK版本號小于或等于当前APP版本号,需刪除未安裝APK"); if (Build.VERSION.SDK_INT >= 24) { File file1 = new File(archiveFilePath); Boolean delete = file1.delete(); Log.i("7.0APK安装包刪除--", delete.toString()); if (delete) { initData(url); } else { } } else { File file = new File(archiveFilePath); Boolean delete1 = file.delete(); Log.i("7.0以下APK安装包刪除--", delete1.toString()); if (delete1) { initData(url); } else { } } } } else { Log.i("获取APK信息", "文件夾内沒包----第一层"); initData(url); } }
第一,文件位置问题 想必大家肯定知道如何调用系统的安装页面了吧
代码如下
Intent install = new Intent(Intent.ACTION_VIEW); //表明不是未知来源 install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); install.setDataAndType(Uri.parse("file://" + uri.toString()), "application/vnd.android.package-archive"); Log.i("安裝地址", "file://" + uri.toString()); mContext.startActivity(install);
这个有一个问题
也是笔者卡了挺久的地方,就是这个异常,度娘了半天,也没找到我需要的答案,后面在跟一个朋友探讨的时候,他跟我说了是不是文件路径有问题 你这个路径不对 ,我log日志答应出来后 果然发现是文件路径问题
1. 如果出现no activity found handle intent异常,一般是因为intent跳转失败。在进行以下语句跳转时,忽略了在apk路径前添加"file://",导致跳转失败,注意添加即可。
这就完美解决了我的第一个问题,后面发现进行android各系统测试 发现7.0报异常了,然后回想起,需要做适配。
利用fileProvider 来获取文件地址,代码如下
if (Build.VERSION.SDK_INT >= 24) { Log.i("android版本号", "7.0"); // MainActivity.getInstance().install(uri); Intent intent2 = new Intent(Intent.ACTION_VIEW); intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent2.setAction(Intent.ACTION_VIEW); intent2.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); intent2.setDataAndType(Uri.parse(uri.toString()), "application/vnd.android.package-archive"); Log.i("安裝地址", uri.toString()); mContext.startActivity(intent2); }
注意:这里setDataAndType 这里的路径拿的是fileprovider获取到的URL 不用拼接 file://
7.0的URL
-
fileUri = FileProvider.getUriForFile(BaseApplication.getApplication(), BaseApplication.getApplication().getPackageName() + ".FileProvider",
-
new File(filePath));
这句话就能拿到 至于怎么使用fileprovider获取文件地址这个我贴个链接 大家自行去学习观摩下,这个链接包含了解决android7.0 调用相机,播放视频,P图,安装APK详细的DEMO 和如何实现,大家自行观摩 感觉那位博友分享
2.如果出现下图的异常,就基本断定是7.0的路径问题。按照我上面给的方法就能解决
第二个问题,就是用户根本没有下载APK更新包的情况下,这时候需要我们给他下载,我这边调用的是系统的一个downloadManager
代码如下
private void initData(final String url1) { if (isPackageManagerDownloadEnabled()) { downloadPackage(url1); } else { openPackageManagerDownloadEnabledSettings(); } } public void openPackageManagerDownloadEnabledSettings() { try { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:com.android.providers.downloads")); mContext.startActivity(intent); } catch (ActivityNotFoundException e) { Log.e("RZMC", e.getMessage()); mContext.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)); } catch (Exception ex) { Log.e("RZMC", ex.getMessage()); } } public boolean isPackageManagerDownloadEnabled() { boolean bOk = true; try { int state = mContext.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads"); if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { bOk = false; } } catch (Exception e) { Log.e("RZMC", e.getMessage()); bOk = false; } return bOk; } /** * 下载前先移除前一个任务,防止重复下载 * * @param downloadId */ public void clearCurrentTask(long downloadId) { DownloadManager dm = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); try { dm.remove(downloadId); } catch (IllegalArgumentException ex) { ex.printStackTrace(); } }
public void downloadPackage(String strUri) { if (downloadID != 0) { clearCurrentTask(downloadID); } Uri uri = Uri.parse(strUri); DownloadManager downloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); String strFileName = uri.getLastPathSegment(); DownloadManager.Request request = new DownloadManager.Request(uri); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); request.setDestinationInExternalFilesDir(mContext, Environment.DIRECTORY_DOWNLOADS, strFileName); request.setTitle(strFileName); request.setVisibleInDownloadsUi(true); downloadID = downloadManager.enqueue(request); boolean bDownloading = false; try { DownloadManager.Query query = new DownloadManager.Query(); query.setFilterByStatus(DownloadManager.STATUS_RUNNING); query.setFilterById(downloadID); Cursor cursor = downloadManager.query(query); while (cursor.moveToNext()) { if (cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI)).equalsIgnoreCase(strUri)) { bDownloading = true; break; } } if (cursor != null) { cursor.close(); } } catch (Exception ex) { Log.e("RZMC", ex.getMessage()); } if (bDownloading) { return; } File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); if (!(dir.exists() && dir.isDirectory())) { dir.mkdirs(); } try { DownloadReceiver.addDownloadID(downloadID); } catch (Exception ex) { Log.e("RZMC", ex.getMessage()); } }
这段代码我是封装好的 只要调接口接收一个下载的URL地址 即可下载
这里面还有一点小小的影响用户体验的问题,不知道大家有没有看出来,就是我这边做了个移除操作,
/** * 下载前先移除前一个任务,防止重复下载 * * @param downloadId */ public void clearCurrentTask(long downloadId) { DownloadManager dm = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); try { dm.remove(downloadId); } catch (IllegalArgumentException ex) { ex.printStackTrace(); } }
这个方法就是当正在下载APK的时候,如果用户再次点击下载APK的时候,如果没加这个方法的时候 是会同时下载2个一模一样的APK文件,这就导致了用户体验不佳。但是现在我这个方式是用户下载到一半,发现再次点击后 原先下载的没了 被我强制终止了,又重新开始下载 ,这个我希望大家能帮我提出点建议。
package com.rzdtapp.Receiver; import android.Manifest; import android.app.DownloadManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v4.content.FileProvider; import android.util.Log; import android.widget.Toast; import java.io.File; import java.util.ArrayList; public class DownloadReceiver extends BroadcastReceiver { private static ArrayList<Long> _downloadIDList = new ArrayList<Long>(); public static void addDownloadID(long downloadID) { if (!_downloadIDList.contains(downloadID)) { Log.i("是否执行添加数据操作", "已添加数据"); _downloadIDList.add(new Long(downloadID)); } } @Override public void onReceive(Context context, Intent intent) { DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) { long downloadID = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); Log.i("downloadID++++++++++", String.valueOf(downloadID)); Log.i("_downloadIDList++++++++", String.valueOf(_downloadIDList)); if (!_downloadIDList.contains(downloadID)) { return; } DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(downloadID); Cursor cursor = manager.query(query); if (cursor.moveToFirst()) { String strFileName = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); Log.i("RZJY", "下载成功:" + strFileName); Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show(); Uri uri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { uri = Uri.parse(strFileName); strFileName = uri.getLastPathSegment(); Log.i("RZJY", "new path: " + uri.toString()); Intent intent2 = new Intent(Intent.ACTION_VIEW); intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent2.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Uri uri1 = Uri.parse("content://com.rzdtapp.fileprovider/files/" + Environment.DIRECTORY_DOWNLOADS + "/" + strFileName); String apkPath = Environment.getExternalStorageDirectory() + File.separator + "Android/data/你的APP/files/Download" + File.separator; Log.i("RZJY", "7.0适配后的apkpath: " + apkPath); File file = new File(apkPath + strFileName); // File file = new File(String.valueOf(uri)); Uri apkUri = FileProvider.getUriForFile(context, "你的APP.fileprovider", file); Log.i("RZJY", "7.0适配后的apkUrl: " + apkUri.toString()); //表明不是未知来源 intent2.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); intent2.setDataAndType(apkUri, "application/vnd.android.package-archive"); context.startActivity(intent2); } else { uri = Uri.parse(strFileName); Log.i("RZJY", "old path: " + uri.toString()); Intent install = new Intent(Intent.ACTION_VIEW); //表明不是未知来源 install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); install.setDataAndType(uri, "application/vnd.android.package-archive"); context.startActivity(install); } } else { Log.i("RZJY", "下载失败"); Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show(); } if (cursor != null) { cursor.close(); } _downloadIDList.remove(downloadID); } else if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(intent.getAction())) { Intent intent1 = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent1); } } }
我写了个广播来接收并执行回调,这里来接收下载完后的回调,安装APK, 同样这里做了对android7.0的适配 这样到这就结束了,希望能帮到大家,关于冷更新的功能实现和异常修复之路。