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

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

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

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

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

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

// Permission
    implementation '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 {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    protected void onResume() {
        super.onResume();
        BaseActivityPermissionsDispatcher.storageWithPermissionCheck(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // NOTE: delegate the permission handling to generated method
        BaseActivityPermissionsDispatcher.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 = "下载后的文件名";

	@Override
	protected 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() 之前)。
	 * 如果服务已在运行,则不会调用此方法。该方法只被调用一次
	 */
	@Override
	public void onCreate() {
		Log.i("bb","onCreate invoke");

		//下载情况通知,点击通知的操作,这里表示跳转到MainActivity
		mUpdateManagerNotification = 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
	@Override
	public IBinder onBind(Intent intent) {
		Log.i("bb","onBind invoke");
		return null;
	}

	/**
	 * 每次通过startService()方法启动Service时都会被回调。
	 * @param intent
	 * @param flags
	 * @param startId
	 * @return
	 */
	@Override
	public 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() {
				@Override
				public void onStarted() {

				}

				@Override
				public void onProgressChanged(int progress, String downloadUrl) {
					Message message = new Message();
					message.what = progress;
					mUpdateManagerNotification.handler.sendMessage(message);

					Log.i("bb","progress = " + progress);
				}

				@Override
				public void onFinished(float completeSize, String downloadUrl) {
					Message message = new Message();
					message.what = 100;
					mUpdateManagerNotification.handler.sendMessage(message);
				}

				@Override
				public void onFailure(Exception e) {
					Log.e("Exception:/","Update Download Exception = " + e.getMessage());
				}
			});
			mUpdateDownloadRequest.downLoadApk(getApplication(),path,name);
		}
		return super.onStartCommand(intent, flags, startId);
	}

	/**
	 * 服务销毁时的回调
	 */
	@Override
	public 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() {
		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			if(msg.what == 100){//下完安装,并清除通知
				//android O后必须传入NotificationChannel
				if(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后必须传入NotificationChannel
				if(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后必须传入NotificationChannel
		if(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以后才能使用,BigTextStyle
		NotificationCompat.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() {
			@Override
			public 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.这里没有考虑重复下载的问题(就是下载过程中有调起了服务的问题),建议在业务逻辑上去避免吧,或者你也可以优化一下这个下载线程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值