前言:
实现APP内应用更新功能思路,这里我给大家具体说明以下。
思路:
- 通过API接口获取服务器端版本号,与本地应用版本号作比较。
- 如果本地版本号小于服务器端版本号,则弹出一个对话框,提示用户更新APP,用户可以点击更新按钮,在APP内直接更新,安装。用户也可以选择去应用商店更新。
- 如果用户点击更新按钮,通过给这个按钮设置监听事件,开启Service服务,在后台服务中下载apk。
- 在Service服务中,通过上一个Activity传来的值,获取到最新APK的下载地址,通过IO流的知识,把最新APK下载到手机SD卡的指定路径。
- 最后通过调用系统安装界面,实现APK的安装。
接下来我们通过代码实现以上这些步骤
版本号对比,设置对话框,开启服务
//获取本地版本号 String vername = APKVersionCodeUtils.getVerName(this); //获取服务端版本号大小 final AppUpadeLog newapplog = new Gson().fromJson(resp, AppUpadeLog.class); //判断版本号的大小,大于网络上的版本号不提示更新 int verflag = compareVersion(vername, newapplog.AppVer); if (verflag == -1) { //对话框弹出 //弹出提醒对话框,提示用户登录成功 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("更新提示"); builder.setMessage("应用已有新版本发布,请前往应用商城进行更新,或者直接点击更新按钮,进行更新!"); builder.setPositiveButton("更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //启动后台服务,下载APK Intent intent = new Intent(MainActivity.this, UpdateService.class); intent.putExtra("FileSrc", newapplog.FileSrc); intent.putExtra("AppName", newapplog.AppName); startService(intent); } }); builder.setNeutralButton("下次提醒", null); AlertDialog alert = builder.create(); alert.show();
下载APK,设置通知栏,安装APK
public class UpdateService extends Service { private static final String TAG = "UpdateService"; private String appUrl; //应用升级地址 private String appName; //应用名称 private String updateFile; //最新APK的路径 private NotificationManager notificationManager; //声明系统的通知管理器 //定义notification实用的ID private static final String MESSAGES_CHANNEL = "messages"; private HttpURLConnection connection; private NotificationCompat.Builder builder; private final int NEW_MESSAGE_ID = 0; private static int HANDLER_LABEL = 0X00; //设置一个常量值来标记信息 Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(@NonNull Message msg) { if (msg.what == HANDLER_LABEL) { if (!TextUtils.isEmpty(updateFile)) { installAPK(); } } return false; } }); //安装 private void installAPK() { try { File cacheDir = new File(getApplicationContext().getExternalFilesDir(null).getPath()); File file = new File(cacheDir, appName + ".apk"); //使用隐式意图开启安装APK的Activity Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //添加这一句表示对目标应用临时授权该Uri所代表的文件 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //Android7.0 API24之后获取uri要用contentProvider intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Uri apkUri = FileProvider.getUriForFile(this, "com.run.xiao.FileProvider", file); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); Log.e(TAG, "installAPK: " + apkUri); } else { Uri apkUri = Uri.parse("file://" + updateFile); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); } startActivity(intent); notificationManager.cancel(0); //出现安装APK页面,取消通知 //结束服务 stopSelf(); } catch (Exception e) { e.printStackTrace(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { //获取上一个页面传来的包裹 Bundle bundle = intent.getExtras(); appUrl = bundle.getString("FileSrc"); appName = bundle.getString("AppName"); downloadUpdateFile(appUrl); return super.onStartCommand(intent, flags, startId); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } /** * 下载更新APK文件 * * @param appUrl * @return */ private int downloadUpdateFile(final String appUrl) { try { //创建通知渠道 createMessageNotificationChannel(); final int NEW_MESSAGE_ID = 0; builder = new NotificationCompat.Builder(this, MESSAGES_CHANNEL); builder.setSmallIcon(R.mipmap.lisen_lancher) //小图标 .setContentTitle("正在下载") //标题 .setContentText("正在更新APP") //描述性文本 .setAutoCancel(true) //点击通知后关闭通知 .setOnlyAlertOnce(true); //设置提示音只响一次 //StrictMode修改默认的策略 StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); //设置进度条操作 URL url = new URL(appUrl); //打开和URL之间的连接 connection = (HttpURLConnection) url.openConnection(); //设置网络请求 connection.setRequestMethod("GET"); //开始读取服务器端数据,到了指定时间还没有读到数据,则报超时异常 connection.setReadTimeout(50000); //要取得长度,要求http请求不要gzip压缩 connection.setRequestProperty("Accept-Encoding", "identity"); // 添加这行代码 //建立实际的连接 connection.connect(); new Thread(new Runnable() { @Override public void run() { int total_length = 0; BufferedOutputStream bos = null; BufferedInputStream bis = null; try { if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { InputStream is = connection.getInputStream(); bis = new BufferedInputStream(is); // File cacheDir = getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); //创建文件路径 File cacheDir = new File(getApplicationContext().getExternalFilesDir(null).getPath()); if (!cacheDir.exists()) { cacheDir.mkdirs(); } //创建文件夹 //通过输出流,下载到指定的本地文件目录 File file = new File(cacheDir, appName + ".apk"); if (!file.exists()) { file.createNewFile(); } //检查SD卡的状态是否可用 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { Toast.makeText(UpdateService.this, "SD卡不可用~", Toast.LENGTH_SHORT).show(); } FileOutputStream fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos); //获取文件流大小,更新进度 byte[] buffer = new byte[1024 * 8]; int len; int pro1 = 0; long file_length = connection.getContentLength(); while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); total_length += len; if (file_length > 0) { pro1 = (int) ((total_length / (float) file_length) * 100);//进度条传递进度 builder.setProgress(100, pro1, false); builder.setContentText("下载" + pro1 + "%"); notificationManager.notify(NEW_MESSAGE_ID, builder.build()); } } builder.setStyle(new NotificationCompat.BigTextStyle().bigText("下载完成,点击安装")); //显示多行文本 notificationManager.notify(NEW_MESSAGE_ID, builder.build()); updateFile = file.getAbsolutePath(); Log.e(TAG, "下载路径:" + updateFile); handler.sendEmptyMessage(HANDLER_LABEL); } } catch (Exception e) { e.printStackTrace(); } finally { //关闭资源,先关闭外层流,在关闭内层流 try { if (bis != null) { bis.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (bos != null) { bos.close(); } } catch (IOException e) { e.printStackTrace(); } } } }).start(); } catch (Exception e) { e.printStackTrace(); } return NEW_MESSAGE_ID; } //创建通知渠道 private void createMessageNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = this.getString(R.string.app_name); NotificationChannel channel = new NotificationChannel( MESSAGES_CHANNEL, name, NotificationManager.IMPORTANCE_HIGH ); notificationManager = this.getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } } }
下载完成之后,不需要用户点击通知栏安装,因为当下载完成后,会发送一条消息给handler,再由handler,调用系统安装界面,进行最新APK的安装。
在清单文件AndroidManifest.xml中注册服务,解决一个应用提供自身文件给其它应用使用时,如果给出一个file://格式的URI的话,应用会抛出FileUriExposedException
<service android:name=".service.UpdateService" android:enabled="true"/> <!-- 解决Android 7.0 以上报错FileUriExposedException --> <provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.soundproject.FileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
android:resource="@xml/file_paths"
这个资源是在res目录下,创建xml包,之后创建file_paths.xml
file_paths.xml代码如下:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="files" path="."/> </paths>
这样就完成了APP内应用更新功能~ 如果有不当之处,可以在评论区指出,一起进步。