当产品有BUG需要被修复的时候,我们可以使用DownloadManager 系统提供的下载类来实现 下载新版本的APK,并通过静默安装的方式,将APK神不知鬼不觉的安装到手机中,静默安装分为两种方式,第一种为root过的手机,第二种为非root过的手机,非root过的手机往往不会使用静默安装的方式来更新你的App,而root过的手机实现静默安装是我们今天讨论的前提。之前在网上搜到的很多静默安装都无法正常实现下载后的后台安装,不知道是自己理解有误还是就是有人没有经过验证而把错误的版本传遍开来,毕竟root的手机实现流氓的静默安装,研究的意义还没有到亲测的地步... 如下代码是自己亲测过的,但安卓版本在安卓7.0之后,貌似7.0版本略有不同?下面进入主题:
首先讲一下我下面代码的流程:1.在你应用开始进入的第一个界面的OnCreate方法中,首先从服务端获取服务器中的当前版本号,当然直接在后台写一个接口显示新版本的版本号或是在服务器中写一个含有版本号的文档也可以啦..2.然后通过getVersionName方法获取当前应用的版本号与网络获取的版本号进行对比,3.当版本低于服务端的版本就开启下载安装的服务。
下面的代码为获取手机端当前版本的版本号的方法,很简单是固定的操作:
/** * 获取当前版本的版本号 */ private int getVersionName() throws Exception { // 获取packageManager的实例 PackageManager packageManager = getPackageManager(); // getPackageName()是你当前类的包名,0代表的是获取版本信息 PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0); Log.d("MMM", "packInfo.versionCode:" + packInfo.versionCode); return packInfo.versionCode; }
下面是自己写的简单的使用OkHttp网络异步向网络请求数据:
(如果对OkHttp网络请求方面有疑惑可以参考这里的教学网址,讲解的很详细并且是免费的,里面对同步异步请求,文件下载上传,表单图片的上传下载,和模拟服务器的搭建都有基本的讲解,当初自己在学习OkHttp网络这里很疑惑的是对后台服务端不了解,并不知道自己移动端的请求服务器到底收没收到,或是不知道服务端如何去写能测试从服务端的下载,毕竟平时的工作两者都是区分开的,这对测试结果和学习带来不便,这套视频也能很好的实现移动端和服务器端的代码测试,为初学者带来方便:http://www.imooc.com/learn/764)
// 异步下载文件 mOkHttpClient = new OkHttpClient(); Request.Builder builder = new Request.Builder(); Request request = builder.get().url(url).build(); Call call = mOkHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d("LLL", "请求失败"); } @Override public void onResponse(Call call, okhttp3.Response response) throws IOException { string = response.body().string(); } });
当然如果觉得麻烦写个更新按钮点击来实现下载安装也行... ...
接下来是最主要的服务类来实现下载安装功能的代码(我把注释尽可能的写在了代码上):
public class UpdateService extends Service { private Uri uriForDownloadedFile; public UpdateService() { } File file; /** * 安卓系统下载类 **/ DownloadManager manager; /** * 接收下载完的广播 **/ DownloadCompleteReceiver receiver; /** * 初始化下载器 **/ private void initDownManager() { manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); receiver = new DownloadCompleteReceiver(); // 设置下载地址 String urlPath = "http://192.168.1.111:8080/app-release.apk"; Uri parse = Uri.parse(urlPath); DownloadManager.Request down = new DownloadManager.Request(parse); // 设置允许使用的网络类型,这里是移动网络和wifi都可以 down.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI); // 下载时通知栏显示进度 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { down.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); } // 显示下载页面 down.setVisibleInDownloadsUi(true); // 设置下载后文件存放位置 Environment.DIRECTORY_DOWNLOADS String apkName = parse.getLastPathSegment(); down.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, apkName); // 将下载请求放入队列 manager.enqueue(down); // 注册下载广播 registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 调用下载 initDownManager(); return super.onStartCommand(intent, flags, startId); } // 需要显示进度条时再次进行设置 @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { // 注销下载广播 if (receiver != null) { unregisterReceiver(receiver); } super.onDestroy(); } // 接受下载完后的intent class DownloadCompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 判断是否下载完成的广播 if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { // 获取下载的文件id long downId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); Log.d("DownloadCompleteReceive", "id = " + downId); // 自动安装apk if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { uriForDownloadedFile = manager.getUriForDownloadedFile(downId); Log.d("CCC", "uri = " + uriForDownloadedFile); installApkNew(); } // 停止服务并关闭广播 UpdateService.this.stopSelf(); } } // 安装apk protected void installApkNew() { try { String str = "/Android/data/sq.test_updata/files/Download/app-release.apk"; String fileName = Environment.getExternalStorageDirectory() + str; Log.d("MMM", fileName); Process process = Runtime.getRuntime().exec("su"); DataOutputStream dos = new DataOutputStream(process.getOutputStream()); dos.write(("pm install -r " + fileName).getBytes()); dos.writeBytes("\n"); dos.flush(); }catch (Exception e){ } } } }
以上需要注意的是静默安装需要调取root权限,Runtime.getRuntime().exec("su"); 再通过执行命令行的形式,先查找到你APK下载到的默认路径,在使用pm install -r + “路径”的形式来安装APK;(这里网上有很多的版本的写法,开始觉得一样,后来才发现里面存在不同的差异,有的写法实现不了自动的安装功能,仅仅就能跳转到用户确认或取消安装的界面为止,后来查找资料和尝试写法的区别在于,dos.write(xxx).getBytes()之后在dos.writeBytes("\n") 最后在flush,而很多错误的版本是 dos.writeBytes(commmand)了,这是有本质区别的),思路流程就是在服务中利用下载完发送广播,在利用广播接收器实现安装功能,如果想要添加进度条等可以利用服务的bind方法中书写我就不累赘的写太多跟本文章标题不一致的内容了。
下载的APK可以安装到SDK中,当然也可以安装在手机的默认目录下,如果不知道可以使用RE管理器去查找(RE管理器需要手机Root权限),所以说到这就别忘记加权限啦~
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"/>
再就是在Activity的调用这里写一个开启这个下载服务的方法:
public void update() { new Thread(new Runnable() { @Override public void run() { // 启动服务 Intent service = new Intent(MainActivity.this, UpdateService.class); startService(service); } }).start(); }
合理的调用这个方法就好啦~在就是提示一下,我好久没有写服务了手抖把startService写成了startActivity了...就会报Activity没有注册的错误提示,记得注意下哦。然后就是我们文章开始阶段说的,先通过网络获取服务端的版本号,在于自己当前版本号作比较,判断是否加载update方法实现自动下载安装。如果想要测试一下代码能否实现推荐一个比较常用的软件 上网搜索apache-tomcat模拟服务端,因为DownloadManager只能访问http//的协议地址,测试的时候记得更改版本号...打包的签名需要一致,界面随意更改下背景颜色来做老版本和新版本的区分。
结语:好久没写博客了,感觉最近收获有限,不能分享什么特别牛逼的技术,还是把基础练好偶尔分享下一些有意思的东西吧~~ 我不知道自己离架构师的路还有多久,但是很庆幸一直在走未曾停过。