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 pip下载包及所有依赖到指定文件夹的步骤说明

《Pythonpip下载包及所有依赖到指定文件夹的步骤说明》为了方便开发和部署,我们常常需要将Python项目所依赖的第三方包导出到本地文件夹中,:本文主要介绍Pythonpip下载包及所有依... 目录步骤说明命令格式示例参数说明离线安装方法注意事项总结要使用pip下载包及其所有依赖到指定文件夹,请按照以

使用jenv工具管理多个JDK版本的方法步骤

《使用jenv工具管理多个JDK版本的方法步骤》jenv是一个开源的Java环境管理工具,旨在帮助开发者在同一台机器上轻松管理和切换多个Java版本,:本文主要介绍使用jenv工具管理多个JD... 目录一、jenv到底是干啥的?二、jenv的核心功能(一)管理多个Java版本(二)支持插件扩展(三)环境隔

MySQL版本问题导致项目无法启动问题的解决方案

《MySQL版本问题导致项目无法启动问题的解决方案》本文记录了一次因MySQL版本不一致导致项目启动失败的经历,详细解析了连接错误的原因,并提供了两种解决方案:调整连接字符串禁用SSL或统一MySQL... 目录本地项目启动报错报错原因:解决方案第一个:第二种:容器启动mysql的坑两种修改时区的方法:本地

RedisTemplate默认序列化方式显示中文乱码的解决

《RedisTemplate默认序列化方式显示中文乱码的解决》本文主要介绍了SpringDataRedis默认使用JdkSerializationRedisSerializer导致数据乱码,文中通过示... 目录1. 问题原因2. 解决方案3. 配置类示例4. 配置说明5. 使用示例6. 验证存储结果7.

conda安装GPU版pytorch默认却是cpu版本

《conda安装GPU版pytorch默认却是cpu版本》本文主要介绍了遇到Conda安装PyTorchGPU版本却默认安装CPU的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录一、问题描述二、网上解决方案罗列【此节为反面方案罗列!!!】三、发现的根本原因[独家]3.1 p

Redis指南及6.2.x版本安装过程

《Redis指南及6.2.x版本安装过程》Redis是完全开源免费的,遵守BSD协议,是一个高性能(NOSQL)的key-value数据库,Redis是一个开源的使用ANSIC语言编写、支持网络、... 目录概述Redis特点Redis应用场景缓存缓存分布式会话分布式锁社交网络最新列表Redis各版本介绍旧

IIS 7.0 及更高版本中的 FTP 状态代码

《IIS7.0及更高版本中的FTP状态代码》本文介绍IIS7.0中的FTP状态代码,方便大家在使用iis中发现ftp的问题... 简介尝试使用 FTP 访问运行 Internet Information Services (IIS) 7.0 或更高版本的服务器上的内容时,IIS 将返回指示响应状态的数字代

如何关闭Mac的Safari通知? 3招教你关闭Safari浏览器网站通知的技巧

《如何关闭Mac的Safari通知?3招教你关闭Safari浏览器网站通知的技巧》当我们在使用Mac电脑专注做一件事情的时候,总是会被一些消息推送通知所打扰,这时候,我们就希望关闭这些烦人的Mac通... Safari 浏览器的「通知」功能本意是为了方便用户及时获取最新资讯,但很容易被一些网站滥用,导致我们

idea中project的显示问题及解决

《idea中project的显示问题及解决》:本文主要介绍idea中project的显示问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录idea中project的显示问题清除配置重China编程新生成配置总结idea中project的显示问题新建空的pr

Java 的 Condition 接口与等待通知机制详解

《Java的Condition接口与等待通知机制详解》在Java并发编程里,实现线程间的协作与同步是极为关键的任务,本文将深入探究Condition接口及其背后的等待通知机制,感兴趣的朋友一起看... 目录一、引言二、Condition 接口概述2.1 基本概念2.2 与 Object 类等待通知方法的区别