app版本更新,通知形式显示安装包下载进度

2024-09-05 16:18

本文主要是介绍app版本更新,通知形式显示安装包下载进度,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

也是公司的项目需要,就稍微研究了下,参考网上一些不错的思路,但其适用版本都比较早,所以通知做了适配了Android 8.0,及权限问题等问题。

原理;下载apk过程中,发起一个通知,并不断发起最新进度的相同ID的通知,覆盖上一个通知,达到显示当前下载进度的效果。

demo已上传:https://download.csdn.net/download/u013370255/10603681

下面简单贴一下代码,及一些需要注意和说明的地方。

1.运行时权限:permissionsdispatcher
用法百度下,多的泛,我就不多说了,

// Permissionimplementation 'com.github.hotchemi:permissionsdispatcher:3.1.0'annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:3.1.0'implementation 'com.android.support:support-v4:27.1.1'

同时AndroidManifast申请以下权限:

    <!--允许程序设置内置sd卡的写权限--><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!--允许程序获取网络状态--><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><!--允许程序访问WiFi网络信息--><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><!--允许程序打开网络套接字--><uses-permission android:name="android.permission.INTERNET" /><!--android8.0安装apk权限--><uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

获取文件存储权限,本当提示弹窗确认权限,这里偷个懒,直接通过了

@RuntimePermissions
public class BaseActivity extends Activity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Overrideprotected void onResume() {super.onResume();BaseActivityPermissionsDispatcher.storageWithPermissionCheck(this);}@Overrideprotected void onPause() {super.onPause();}@Overrideprotected void onDestroy() {super.onDestroy();}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);// NOTE: delegate the permission handling to generated methodBaseActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);}@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)void storage() {//动态权限}@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)void showRationaleForStorage(final PermissionRequest request) {// TODO: 2018/8/16 当提示弹窗确认权限,这里偷个懒,直接通过了 request.proceed();}@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)void showDeniedForStorage() {}@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)void showNeverAskForStorage() {}
}
  1. MainActivity 调用,传入你的下载路径,保存的文件名
public class MainActivity extends BaseActivity {String path = "你的下载路径";String name = "下载后的文件名";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Intent intent = new Intent(this, UpdateService.class);Bundle bundle = new Bundle();bundle.putString("path",path);bundle.putString("name",name);intent.putExtras(bundle);startService(intent);}
}

3.主要服务UpdateService ,避免手机应用进程划掉就不下载了,并在AndroidManifast进行注册

<service android:name=".Download.UpdateService" />
public class UpdateService extends Service {UpdateManagerNotification mUpdateManagerNotification;/*** 首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。* 如果服务已在运行,则不会调用此方法。该方法只被调用一次*/@Overridepublic void onCreate() {Log.i("bb","onCreate invoke");//下载情况通知,点击通知的操作,这里表示跳转到MainActivitymUpdateManagerNotification = new UpdateManagerNotification(getApplicationContext());Intent intentLoGo = new Intent(getApplicationContext(), MainActivity.class);PendingIntent piLoGo = PendingIntent.getActivity(getApplicationContext(), 0, intentLoGo, 0);mUpdateManagerNotification.showNotification(getApplicationContext(),piLoGo,"软件更新","软件更新",getApplicationContext().getString(R.string.app_name),"软件更新",getApplicationContext().getString(R.string.app_name));super.onCreate();}/*** 绑定服务时才会调用* 必须要实现的方法* @param intent* @return*/@Nullable@Overridepublic IBinder onBind(Intent intent) {Log.i("bb","onBind invoke");return null;}/*** 每次通过startService()方法启动Service时都会被回调。* @param intent* @param flags* @param startId* @return*/@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Bundle bundle = intent.getExtras();if(bundle != null){String path = bundle.getString("path","");String name = bundle.getString("name","");UpdateDownloadRequest mUpdateDownloadRequest = new UpdateDownloadRequest(new UpdateDownloadListener() {@Overridepublic void onStarted() {}@Overridepublic void onProgressChanged(int progress, String downloadUrl) {Message message = new Message();message.what = progress;mUpdateManagerNotification.handler.sendMessage(message);Log.i("bb","progress = " + progress);}@Overridepublic void onFinished(float completeSize, String downloadUrl) {Message message = new Message();message.what = 100;mUpdateManagerNotification.handler.sendMessage(message);}@Overridepublic void onFailure(Exception e) {Log.e("Exception:/","Update Download Exception = " + e.getMessage());}});mUpdateDownloadRequest.downLoadApk(getApplication(),path,name);}return super.onStartCommand(intent, flags, startId);}/*** 服务销毁时的回调*/@Overridepublic void onDestroy() {Log.i("bb","onDestroy invoke");super.onDestroy();}
}

4.通知实现,适配Android8.0,系统布局(大布局)
这里需要注意的是通知图标和提示文字需要你自己定,并且代码中的图标logo_alpha必须是32*32的白色切图,这个好像是Google的规范,注意下。
关键代码

这句代码可以去除Android8.0的声音和震动,不加的话,即使你不设置也会有声音或震动。
channel.setSound(null, null);
public class UpdateManagerNotification {private Context mContext;NotificationManager notificationManager;NotificationChannel channel;NotificationCompat.Builder builder;@SuppressLint ("HandlerLeak")public Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if(msg.what == 100){//下完安装,并清除通知//android O后必须传入NotificationChannelif(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){notificationManager.cancel(Constants.NOTIFICATIONID_APP);}else {NotificationManagerCompat managerCompat = NotificationManagerCompat.from(mContext);managerCompat.cancel(Constants.NOTIFICATIONID_APP);}}else if(msg.what >= 0 && msg.what < 100){//下载进度builder.setProgress(100, msg.what, false);builder.setContentText("下载进度:" + msg.what + "%");builder.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE);//android O后必须传入NotificationChannelif(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){notificationManager.notify(Constants.NOTIFICATIONID_APP, builder.build());}else {NotificationManagerCompat managerCompat = NotificationManagerCompat.from(mContext);managerCompat.notify(Constants.NOTIFICATIONID_APP, builder.build());}}}};public UpdateManagerNotification(Context context) {this.mContext = context;}/*** 生成通知* @param context* @param pi* 例如:* Intent intentYiChe = new Intent(context, YiCheRecordActivity.class);* Bundle bundle = new Bundle();* intentYiChe.putExtras(bundle);* PendingIntent piYiChe = PendingIntent.getActivity(context, 0, intentYiChe, 0);* @param ticker 			标题* @param contentTitle		标题* @param bigContentTitle	标题* @param summaryText		标题* @param Message	显示内容*/public void showNotification(Context context, PendingIntent pi,String ticker, String contentTitle, String bigContentTitle, String summaryText,String Message){//android O后必须传入NotificationChannelif(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);if(notificationManager != null){//ChannelId为"1",ChannelName为"Channel1"channel = new NotificationChannel("4","运维通更新通知通道", NotificationManager.IMPORTANCE_DEFAULT);channel.enableLights(true); //是否在桌面icon右上角展示小红点channel.setLightColor(Color.YELLOW); //小红点颜色channel.setShowBadge(false); //是否在久按桌面图标时显示此渠道的通知channel.setSound(null, null);notificationManager.createNotificationChannel(channel);builder = new NotificationCompat.Builder(context,"4");setNotification(builder,context,pi,ticker,contentTitle,bigContentTitle,summaryText,Message);notificationManager.notify(Constants.NOTIFICATIONID_APP, builder.build());}}else {builder = new NotificationCompat.Builder(context,null);setNotification(builder,context,pi,ticker,contentTitle,bigContentTitle,summaryText,Message);NotificationManagerCompat managerCompat = NotificationManagerCompat.from(context);managerCompat.notify(Constants.NOTIFICATIONID_APP, builder.build());}}/*** 设置大布局通知参数* @param builder* @param context* @param pi* @param ticker* @param contentTitle* @param bigContentTitle* @param summaryText* @param Message*/private void setNotification(NotificationCompat.Builder builder, Context context, PendingIntent pi,String ticker, String contentTitle, String bigContentTitle, String summaryText,String Message){builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher)).setTicker(ticker).setContentTitle(contentTitle).setWhen(System.currentTimeMillis()).setContentIntent(pi).setAutoCancel(false)//设置通知被点击一次是否自动取消.setOngoing(false).setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE).setProgress(100, 0, false);//大布局通知在4.1以后才能使用,BigTextStyleNotificationCompat.BigTextStyle textStyle = null;if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {textStyle = new NotificationCompat.BigTextStyle();textStyle.setBigContentTitle(bigContentTitle)// 标题.setSummaryText(summaryText).bigText(Message);// 内容builder.setStyle(textStyle);}builder.setContentText(Message);if(SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {builder.setSmallIcon(R.drawable.logo_alpha);} else {builder.setSmallIcon(R.mipmap.ic_launcher);}}
}

5.接口,简单的罗列下几个阶段,方便回调

public interface UpdateDownloadListener {void onStarted();void onProgressChanged(int progress, String downloadUrl);void onFinished(float completeSize, String downloadUrl);void onFailure(Exception e);
}

6.apk下载线程与安装方法

public class UpdateDownloadRequest{private UpdateDownloadListener listener;public UpdateDownloadRequest(UpdateDownloadListener listener) {this.listener = listener;}/*** 下载APK文件* @param context* @param url* @param filename*/public void downLoadApk(final Context context,final String url, final String filename) {new Thread() {@Overridepublic void run() {try {File file = getFileFromServer(context,url,filename);sleep(1000);installApk(file, context);} catch (Exception e) {listener.onFailure(e);e.printStackTrace();}}}.start();}/*** 安装软件包* @param file* @param context*/private void installApk(File file, final Context context) {Intent intent = new Intent();intent.setAction(Intent.ACTION_VIEW);intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");context.startActivity(intent);}/*** 下载文件* @param context* @param path* @param filename* @return* @throws Exception*/private File getFileFromServer(Context context, String path,String filename) throws Exception {// 单线程从服务器下载软件包if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(10000);long beforeTime = System.currentTimeMillis();int count = conn.getContentLength(); //文件总大小 字节InputStream is = conn.getInputStream();File file = new File(PathManger.getApkDir().getAbsoluteFile(),filename);FileOutputStream fos = new FileOutputStream(file);BufferedInputStream bis = new BufferedInputStream(is);byte[] buffer = new byte[1024];int len;int total = 0;while ((len = bis.read(buffer)) != -1) {fos.write(buffer, 0, len);total += len;//1秒 更新2次进度 非常重要 否则 系统会慢慢卡死if (System.currentTimeMillis() - beforeTime > 500) {listener.onProgressChanged((int) (((double) total / (double) count) * 100), path);}}fos.close();bis.close();is.close();listener.onFinished(100,path);return file;} else {return null;}}
}

总结:
A.有些次要代码就不贴了,看demo就好了,都是一些公共的方法。
B.这里没有考虑重复下载的问题(就是下载过程中有调起了服务的问题),建议在业务逻辑上去避免吧,或者你也可以优化一下这个下载线程。

这篇关于app版本更新,通知形式显示安装包下载进度的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1139454

相关文章

Python一次性将指定版本所有包上传PyPI镜像解决方案

《Python一次性将指定版本所有包上传PyPI镜像解决方案》本文主要介绍了一个安全、完整、可离线部署的解决方案,用于一次性准备指定Python版本的所有包,然后导出到内网环境,感兴趣的小伙伴可以跟随... 目录为什么需要这个方案完整解决方案1. 项目目录结构2. 创建智能下载脚本3. 创建包清单生成脚本4

Ubuntu如何升级Python版本

《Ubuntu如何升级Python版本》Ubuntu22.04Docker中,安装Python3.11后,使用update-alternatives设置为默认版本,最后用python3-V验证... 目China编程录问题描述前提环境解决方法总结问题描述Ubuntu22.04系统自带python3.10,想升级

Python多线程实现大文件快速下载的代码实现

《Python多线程实现大文件快速下载的代码实现》在互联网时代,文件下载是日常操作之一,尤其是大文件,然而,网络条件不稳定或带宽有限时,下载速度会变得很慢,本文将介绍如何使用Python实现多线程下载... 目录引言一、多线程下载原理二、python实现多线程下载代码说明:三、实战案例四、注意事项五、总结引

MySQL 数据库表操作完全指南:创建、读取、更新与删除实战

《MySQL数据库表操作完全指南:创建、读取、更新与删除实战》本文系统讲解MySQL表的增删查改(CURD)操作,涵盖创建、更新、查询、删除及插入查询结果,也是贯穿各类项目开发全流程的基础数据交互原... 目录mysql系列前言一、Create(创建)并插入数据1.1 单行数据 + 全列插入1.2 多行数据

linux安装、更新、卸载anaconda实践

《linux安装、更新、卸载anaconda实践》Anaconda是基于conda的科学计算环境,集成1400+包及依赖,安装需下载脚本、接受协议、设置路径、配置环境变量,更新与卸载通过conda命令... 目录随意找一个目录下载安装脚本检查许可证协议,ENTER就可以安装完毕之后激活anaconda安装更

更改linux系统的默认Python版本方式

《更改linux系统的默认Python版本方式》通过删除原Python软链接并创建指向python3.6的新链接,可切换系统默认Python版本,需注意版本冲突、环境混乱及维护问题,建议使用pyenv... 目录更改系统的默认python版本软链接软链接的特点创建软链接的命令使用场景注意事项总结更改系统的默

Linux升级或者切换python版本实现方式

《Linux升级或者切换python版本实现方式》本文介绍在Ubuntu/Debian系统升级Python至3.11或更高版本的方法,通过查看版本列表并选择新版本进行全局修改,需注意自动与手动模式的选... 目录升级系统python版本 (适用于全局修改)对于Ubuntu/Debian系统安装后,验证Pyt

MySQL 升级到8.4版本的完整流程及操作方法

《MySQL升级到8.4版本的完整流程及操作方法》本文详细说明了MySQL升级至8.4的完整流程,涵盖升级前准备(备份、兼容性检查)、支持路径(原地、逻辑导出、复制)、关键变更(空间索引、保留关键字... 目录一、升级前准备 (3.1 Before You Begin)二、升级路径 (3.2 Upgrade

Nginx进行平滑升级的实战指南(不中断服务版本更新)

《Nginx进行平滑升级的实战指南(不中断服务版本更新)》Nginx的平滑升级(也称为热升级)是一种在不停止服务的情况下更新Nginx版本或添加模块的方法,这种升级方式确保了服务的高可用性,避免了因升... 目录一.下载并编译新版Nginx1.下载解压2.编译二.替换可执行文件,并平滑升级1.替换可执行文件

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录