各位同学,早上好,今天为大家推荐的是Teprinciple同学投稿的文章,文章的内容是介绍了他的开源库实现在线下载的思路,以及期间需要注意考虑的一些问题,值得对这块不熟悉的同学学习一下,作者在最后还总结了当前方案的一些不足。当然,如果你觉得这个方案还有更好的建议,也欢迎在下方留言。
作者简书:https://www.jianshu.com/p/9c91bb984c85
UpdateAppUtils
一行代码,快速实现app在线下载更新,先看效果图:
快速使用
先来看看怎样一行代码实现更新:
dependencies { compile 'com.teprinciple:updateapputils:1.3.1'} UpdateAppUtils.from(this) .checkBy(UpdateAppUtils.CHECK_BY_VERSION_NAME) //更新检测方式,默认为VersionCode .serverVersionCode(2) .serverVersionName("2.0") .apkPath(apkPath) .showNotification(false) //是否显示下载进度到通知栏,默认为true .updateInfo(info) //更新日志信息 String .downloadBy(UpdateAppUtils.DOWNLOAD_BY_BROWSER) //下载方式:app下载、手机浏览器下载。默认app下载 .isForce(true) //是否强制更新,默认false 强制更新情况下用户不同意更新则不能使用app .update(); 实现原理
使用很简单吧,其实实现过程也很简单,大致分为三步:
1、根据初入参数判断是否需要更新
3、安装最新apk
下面我们来看看源码:
第一步:初始化参数并判断。根据传入的服务器版本号与本地版本号做出判断是否需要更新,并配置好下载地址,下载方式等参数。
/** * Created by Teprinciple on 2016/11/15. */public class UpdateAppUtils { private final String TAG = "UpdateAppUtils"; public static final int CHECK_BY_VERSION_NAME = 1001; public static final int CHECK_BY_VERSION_CODE = 1002; public static final int DOWNLOAD_BY_APP = 1003; public static final int DOWNLOAD_BY_BROWSER = 1004; private Activity activity; private int checkBy = CHECK_BY_VERSION_CODE; private int downloadBy = DOWNLOAD_BY_APP; private int serverVersionCode = 0; private String apkPath=""; private String serverVersionName=""; private boolean isForce = false; //是否强制更新 private int localVersionCode = 0; private String localVersionName=""; public static boolean showNotification = true; private String updateInfo = ""; private UpdateAppUtils(Activity activity) { this.activity = activity; getAPPLocalVersion(activity); } public static UpdateAppUtils from(Activity activity){ return new UpdateAppUtils(activity); } public UpdateAppUtils checkBy(int checkBy){ this.checkBy = checkBy; return this; } public UpdateAppUtils apkPath(String apkPath){ this.apkPath = apkPath; return this; } public UpdateAppUtils downloadBy(int downloadBy){ this.downloadBy = downloadBy; return this; } public UpdateAppUtils showNotification(boolean showNotification){ this.showNotification = showNotification; return this; } public UpdateAppUtils updateInfo(String updateInfo){ this.updateInfo = updateInfo; return this; } public UpdateAppUtils serverVersionCode(int serverVersionCode){ this.serverVersionCode = serverVersionCode; return this; } public UpdateAppUtils serverVersionName(String serverVersionName){ this.serverVersionName = serverVersionName; return this; } public UpdateAppUtils isForce(boolean isForce){ this.isForce = isForce; return this; } //获取apk的版本号 currentVersionCode private void getAPPLocalVersion(Context ctx) { PackageManager manager = ctx.getPackageManager(); try { PackageInfo info = manager.getPackageInfo(ctx.getPackageName(), 0); localVersionName = info.versionName; // 版本名 localVersionCode = info.versionCode; // 版本号 } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } public void update(){ switch (checkBy){ case CHECK_BY_VERSION_CODE: if (serverVersionCode >localVersionCode){ toUpdate(); }else { Log.i(TAG,"当前版本是最新版本"+serverVersionCode+"/"+serverVersionName); } break; case CHECK_BY_VERSION_NAME: if (!serverVersionName.equals(localVersionName)){ toUpdate(); }else { Log.i(TAG,"当前版本是最新版本"+serverVersionCode+"/"+serverVersionName); } break; } } private void toUpdate() { realUpdate(); } private void realUpdate() { ConfirmDialog dialog = new ConfirmDialog(activity, new Callback() { @Override public void callback(int position) { switch (position){ case 0: //cancle if (isForce)System.exit(0); break; case 1: //sure if (downloadBy == DOWNLOAD_BY_APP) { if (isWifiConnected(activity)){ DownloadAppUtils.downloadForAutoInstall(activity, apkPath, "demo.apk", serverVersionName); }else { new ConfirmDialog(activity, new Callback() { @Override public void callback(int position) { if (position==1){ DownloadAppUtils.downloadForAutoInstall(activity, apkPath, "demo.apk", serverVersionName); }else { if (isForce)activity.finish(); } } }).setContent("目前手机不是WiFi状态n确认是否继续下载更新?").show(); } }else if (downloadBy == DOWNLOAD_BY_BROWSER){ DownloadAppUtils.downloadForWebView(activity,apkPath); } break; } } }); String content = "发现新版本:"+serverVersionName+"n是否下载更新?"; if (!TextUtils.isEmpty(updateInfo)){ content = "发现新版本:"+serverVersionName+"是否下载更新?nn"+updateInfo; } dialog .setContent(content); dialog.setCancelable(false); dialog.show(); } //检测wifi是否连接 public static boolean isWifiConnected(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (cm != null) { NetworkInfo networkInfo = cm.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { return true; } } return false; }} /** *Created by Teprinciple on 2016/12/13. */public class DownloadAppUtils { private static final String TAG = DownloadAppUtils.class.getSimpleName(); public static long downloadUpdateApkId = -1;//下载更新Apk 下载任务对应的Id public static String downloadUpdateApkFilePath;//下载更新Apk 文件路径 /** * 通过浏览器下载APK包 * @param context * @param url */ public static void downloadForWebView(Context context, String url) { Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } /** * 下载更新apk包 * 权限:1,<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> * @param context * @param url */ public static void downloadForAutoInstall(Context context, String url, String fileName, String title) { if (TextUtils.isEmpty(url)) { return; } try { Uri uri = Uri.parse(url); DownloadManager downloadManager = (DownloadManager) context .getSystemService(Context.DOWNLOAD_SERVICE); DownloadManager.Request request = new DownloadManager.Request(uri); //在通知栏中显示 request.setVisibleInDownloadsUi(true); request.setTitle(title); // VISIBILITY_VISIBLE: 下载过程中可见, 下载完后自动消失 (默认) // VISIBILITY_VISIBLE_NOTIFY_COMPLETED: 下载过程中和下载完成后均可见 // VISIBILITY_HIDDEN: 始终不显示通知 if (!UpdateAppUtils.showNotification) request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); String filePath = null; if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {//外部存储卡 filePath = Environment.getExternalStorageDirectory().getAbsolutePath(); } else { Log.i(TAG,"没有SD卡"); return; } downloadUpdateApkFilePath = filePath + File.separator + fileName; deleteFile(downloadUpdateApkFilePath);// 若存在,则删除 Uri fileUri = Uri.fromFile(new File(downloadUpdateApkFilePath)); request.setDestinationUri(fileUri); downloadUpdateApkId = downloadManager.enqueue(request); } catch (Exception e) { e.printStackTrace(); downloadForWebView(context, url); } } private static boolean deleteFile(String fileStr) { File file = new File(fileStr); return file.delete(); }}
第三步、安装apk。DownloadManager下载完成后,会发送通知。我们在UpdateAppReceiver ,接受到通知后执行安装操作。
/** * 注册 * <action android:name="android.intent.action.DOWNLOAD_COMPLETE" /> * <action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"/> */public class UpdateAppReceiver extends BroadcastReceiver { public UpdateAppReceiver() { } @Override public void onReceive(Context context, Intent intent) { // 处理下载完成 Cursor c = null; if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) { if (DownloadAppUtils.downloadUpdateApkId >= 0) { long downloadId = DownloadAppUtils.downloadUpdateApkId; DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(downloadId); DownloadManager downloadManager = (DownloadManager) context .getSystemService(Context.DOWNLOAD_SERVICE); c = downloadManager.query(query); if (c.moveToFirst()) { int status = c.getInt(c .getColumnIndex(DownloadManager.COLUMN_STATUS)); if (status == DownloadManager.STATUS_FAILED) { downloadManager.remove(downloadId); } else if (status == DownloadManager.STATUS_SUCCESSFUL) { if (DownloadAppUtils.downloadUpdateApkFilePath != null) { Intent i = new Intent(Intent.ACTION_VIEW); File apkFile = new File(DownloadAppUtils.downloadUpdateApkFilePath); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile( context, context.getPackageName() + ".fileprovider", apkFile); i.setDataAndType(contentUri, "application/vnd.android.package-archive"); } else { i.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); } i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i); } } } c.close(); } } }}
通过上面三个类就可以实现在线下载更新app了。下面我们看看关于Android6.0以及Android7.0的适配。
适配Android7.0
安卓官方为了提高私有文件的安全性,对于Android 7.0 及更高版本的应用私有目录被限制访问。因此,在使用Intent方式安装时,尝试传递 file:// URI 会触发 FileUriExposedException。解决方法是使用 FileProvider,如下:
1、注册provider
<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> /provider>
2、新建file_paths.xml文件
<?xml version="1.0" encoding="utf-8"?><paths> <external-path path="Android/data/包名/" name="files_root" /> <external-path path="." name="external_storage_root" /></paths> 适配Android6.0
关于6.0适配,请自行在调用API时申请WRITE_EXTERNAL_STORAGE权限,可以参考demo中的代码
目前不足之处
UpdateAppUtils很简单方便实现了app的在线下载更新。但是本库目前有存在有一些不足之处:
1、目前使用DownloadManager作为下载模块,但是国内部分手机DownloadManager功能已被阉割,造成不能下载。
2、目前更新弹窗暂时没提供自定义UI接口。
3、目前每次检查需更新后都执行下载,没有判断本地是否已有最新apk文件。
这些问题我会在后面进行完善。如果你发现本库有其他的不足,或者对本库有好的建议都可以issue我。希望能通过大家的力量,一起把UpdateAppUtils做的更好。
具体原理及源码可见 https://github.com/teprinciple/UpdateAppDemo
觉得文章不错,欢迎转发,点赞,赞赏支持作者。更多精彩,欢迎持续关注“Android程序员”。
标签: 升级2.0安卓版