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

相关文章

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

常用的jdk下载地址

jdk下载地址 安装方式可以看之前的博客: mac安装jdk oracle 版本:https://www.oracle.com/java/technologies/downloads/ Eclipse Temurin版本:https://adoptium.net/zh-CN/temurin/releases/ 阿里版本: github:https://github.com/

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

hdu 1754 I Hate It(线段树,单点更新,区间最值)

题意是求一个线段中的最大数。 线段树的模板题,试用了一下交大的模板。效率有点略低。 代码: #include <stdio.h>#include <string.h>#define TREE_SIZE (1 << (20))//const int TREE_SIZE = 200000 + 10;int max(int a, int b){return a > b ? a :

AI行业应用(不定期更新)

ChatPDF 可以让你上传一个 PDF 文件,然后针对这个 PDF 进行小结和提问。你可以把各种各样你要研究的分析报告交给它,快速获取到想要知道的信息。https://www.chatpdf.com/