本文主要是介绍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() {}
}
- 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版本更新,通知形式显示安装包下载进度的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!