首先服务类
java">public class UpdateService extends Service {private static final String NOTIFY_CHANNEL_ID = "com.jianke.api.UpdateService";public static final String BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK = "BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK";public static final String BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK_SUCCESS = "BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK_SUCCESS";public static boolean isRunning = false; //是否正在运行public static final String URL = "url"; //Tagpublic static final String ICON = "icon"; //Tagpublic static final String MD5 = "md5"; //Tagprivate NotificationCompat.Builder builder;private Handler handler;//Handler对象private int lastPercent = 0;private NotificationManager notificationManager;//Class to notify the user of events that happen.private AuthInstallApkBroadcastReceiver mAuthInstallApkBroadcastReceiver;private String fileName = String.valueOf(System.currentTimeMillis());private UpdateListener updateListener;private class AuthInstallApkBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {installApk();}}@Overridepublic void onCreate() {super.onCreate();mAuthInstallApkBroadcastReceiver = new AuthInstallApkBroadcastReceiver();IntentFilter intentFilter = new IntentFilter(UpdateService.BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK_SUCCESS);LocalBroadcastManager.getInstance(this).registerReceiver(mAuthInstallApkBroadcastReceiver, intentFilter);}@Overridepublic void onDestroy() {LocalBroadcastManager.getInstance(this).unregisterReceiver(mAuthInstallApkBroadcastReceiver);updateListener = null;super.onDestroy();isRunning = false;}@Overridepublic IBinder onBind(Intent intent) {return new UpdateServiceBinder();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {isRunning = true;if (intent == null) {return super.onStartCommand(intent, flags, startId);}String action = intent.getAction();if (!TextUtils.isEmpty(action)) {Toast.makeText(getApplicationContext(), "action is null", Toast.LENGTH_SHORT).show();} else {String url = intent.getStringExtra(URL);final String md5 = intent.getStringExtra(MD5);int icon = intent.getIntExtra(ICON, android.R.drawable.sym_def_app_icon);if (TextUtils.isEmpty(url)) {
// if (BuildConfig.DEBUG) {throw new RuntimeException("获取APK更新地址失败");
// }
// return super.onStartCommand(intent, flags, startId);} else {startUpdate(url, icon);}handler = new Handler(getMainLooper()) {@Overridepublic void handleMessage(Message msg) {if (builder != null) {switch (msg.what) {case 1:builder.setProgress(100, (Integer) msg.obj, false);builder.setContentText((Integer) msg.obj + "%");if (notificationManager == null || builder == null) {return;}notificationManager.notify(R.id.update_notification_id, builder.build());break;case 2:notificationManager.cancel(R.id.update_notification_id);getFileMD5(md5);break;case 3: //校验md5 结果处理 以及安装String fileHash = (String) msg.obj;LogUtils.d("md5===" + fileHash);// 校验hash 忽略 md5大小写if (msg.arg1 == 1 && (TextUtils.isEmpty(md5) || (!TextUtils.isEmpty(fileHash) && fileHash.equalsIgnoreCase(md5)))) {isRunning = false;if(updateListener != null) updateListener.onMd5Checked(getApkPath());installApk();} else {isRunning = false;if(updateListener != null) updateListener.onError("文件错误");ToastUtil.setToast("文件错误");stopSelf();}break;case 4:isRunning = false;if(updateListener != null) updateListener.onError(msg.obj + "");ToastUtil.setToast(msg.obj + "");notificationManager.cancel(R.id.update_notification_id);stopSelf();break;default:break;}}}};}return super.onStartCommand(intent, flags, startId);}public void setUpdateListener(UpdateListener updateListener) {this.updateListener = updateListener;}/*** 校验文件md5** @param md5*/private void getFileMD5(String md5) {if (!TextUtils.isEmpty(md5)) {new Thread(new Runnable() { //300M 耗时 ≈ 3S@Overridepublic void run() {try {File file = new File(getApkPath());String fileHash = MD5Utils.digestMD5(file);sendCheckFileMsg(true, fileHash);} catch (Exception e) {e.printStackTrace();sendCheckFileMsg(false, "");}}}).start();} else { //md5 空算成功sendCheckFileMsg(true, "");}}private void sendCheckFileMsg(boolean success, String hash) {Message msg = Message.obtain();msg.what = 3;msg.arg1 = success ? 1 : 0;msg.obj = hash;handler.sendMessage(msg);}private void installApk() {File file = new File(getApkPath());if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !getPackageManager().canRequestPackageInstalls()) {ToastUtil.setToast("请授权安装应用");requestAutoInstallApk();return;}Intent intent = new Intent();intent.setAction(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".updateprovider", file);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);intent.setDataAndType(apkUri, "application/vnd.android.package-archive");} else {intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");intent.addCategory("android.intent.category.DEFAULT");}startActivity(intent);stopSelf();}private void requestAutoInstallApk() {isRunning = false;LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK));}/*** 开始下载** @param url apk的url*/private void startUpdate(String url, int icon) {createNotification(icon); //创建通知栏进度startDownLoad(url);}/*** 创建通知栏进度*/private void createNotification(int icon) {notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel channel = new NotificationChannel(NOTIFY_CHANNEL_ID,"TAG",NotificationManager.IMPORTANCE_DEFAULT);channel.enableLights(true);channel.setShowBadge(true);channel.setDescription("update version");notificationManager.createNotificationChannel(channel);}notificationManager.cancel(R.id.update_notification_id);builder = new NotificationCompat.Builder(getApplicationContext(), NOTIFY_CHANNEL_ID);builder.setSmallIcon(icon).setContentTitle("正在下载 ...").setContentText("0%");builder.setPriority(2 | Notification.DEFAULT_ALL);builder.setProgress(100, 0, false);builder.setOnlyAlertOnce(true);builder.setOngoing(true);if (notificationManager == null) {return;}notificationManager.notify(R.id.update_notification_id, builder.build());}/*** 开始下载** @param url*/private void startDownLoad(String url) {DownLoadManager.getInstance().downLoad(this, url, new DownLoadManager.DownLoadListener() {@Overridepublic void onStartLoading(long totalSize) {// Do nothing because of auto-generatedif(updateListener != null) updateListener.onStart(totalSize);}@Overridepublic void onLoading(long currentSize, float percent, float speed) {int tempPercent = (int) (percent * 100);if (tempPercent >= 0 && lastPercent != tempPercent) { //避免频繁调用通知Message msg = Message.obtain();msg.what = 1;msg.obj = tempPercent;handler.sendMessage(msg);lastPercent = tempPercent;if(updateListener != null) updateListener.onLoading(currentSize, percent, speed);}}@Overridepublic void onLoadingFinish(long totalSize) {Message msg = Message.obtain();msg.what = 2;handler.sendMessage(msg);if(updateListener != null) updateListener.onLoadingFinish(totalSize);}@Overridepublic void onFailure(String error) {Message msg = Message.obtain();msg.what = 4;msg.obj = error;handler.sendMessage(msg);}}, new File(getApkPath()));}/*** 获取apk下载路径** @return*/private String getApkPath() {boolean old = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;return old ?getFileStreamPath(fileName + ".apk").getAbsolutePath(): getAppRootDir() + fileName + ".apk";}/*** 获取sdcard的绝对路径** @return*/public String getSDcardDir() {String sdcardPath = null;if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {sdcardPath = Environment.getExternalStorageDirectory().getAbsolutePath();} else {sdcardPath = getApplicationContext().getFilesDir().getAbsolutePath();}return sdcardPath;}/*** 获取应用跟目录** @return*/public String getAppRootDir() {return getFilesDir().getAbsolutePath() + "/";}public class UpdateServiceBinder extends Binder {public UpdateService getService(){return UpdateService.this;}}
}
UpdateListener
java">public interface UpdateListener {/*** @description 开始升级* @param totalSize 安装包体积*/void onStart(long totalSize);/*** @description 正在下载*/void onLoading(long currentSize, float percent, float speed);/*** @description 下载完成* @param totalSize 安装包体积*/void onLoadingFinish(long totalSize);/*** @description md5校验成功*/void onMd5Checked(String path);/*** @description 升级失败* @param error*/void onError(String error);
}
DownLoadManager
java">public class DownLoadManager {private static final String MAIN = "main"; //Tagprivate static DownLoadManager instance = new DownLoadManager(); //单例对象/*** 对外公布的单例对象** @return*/public static DownLoadManager getInstance() {return instance;}/*** 下载** @param uri url* @param listener 下载DownLoadListener监听对象* @param targetFile 目标文件*/public void downLoad(final Context context, final String uri, final DownLoadListener listener,final File targetFile) {if (MAIN.equalsIgnoreCase(Thread.currentThread().getName())) {new Thread() {@Overridepublic void run() {downloadNewThread(context, uri, listener, targetFile);};}.start();} else {downloadNewThread(context, uri, listener, targetFile);}}/*** 新开一个线程执行下载操作** @param uri* @param listener* @param targetFile*/private void downloadNewThread(Context context, String uri, DownLoadListener listener,File targetFile) {FileOutputStream fileOutputStream = null;InputStream inputStream = null;//try {URL url = new URL(uri);//获取连接HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setConnectTimeout(60 * 1000);connection.setReadTimeout(60 * 1000);connection.setRequestProperty("Connection", "Keep-Alive");connection.setRequestProperty("Charset", "UTF-8");connection.setDoInput(true);connection.setUseCaches(false);//打开连接connection.connect();//获取内容长度int contentLength = connection.getContentLength();if (listener != null) {listener.onStartLoading(contentLength);}File parent = targetFile.getParentFile();if (!parent.exists()) {parent.mkdirs();} else if (!parent.isDirectory()) {if (parent.delete()) {parent.mkdirs();}}//输入流inputStream = connection.getInputStream();//输出流boolean old = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;fileOutputStream = old ?context.openFileOutput(targetFile.getName(), Context.MODE_WORLD_READABLE): new FileOutputStream(targetFile);byte[] bytes = new byte[1024];long totalReaded = 0;int temp_Len;long currentTime = System.currentTimeMillis();while ((temp_Len = inputStream.read(bytes)) != -1) {totalReaded += temp_Len;
// Log.i("XXXX", "run: totalReaded:" + totalReaded);
// long progress = totalReaded * 100 / contentLength;
// Log.i("XXXX", "run: progress:" + progress);fileOutputStream.write(bytes, 0, temp_Len);if (listener != null) {listener.onLoading(totalReaded, ((float) totalReaded) / contentLength,((float) temp_Len) / System.currentTimeMillis()- currentTime);}currentTime = System.currentTimeMillis();}if (listener != null) {listener.onLoadingFinish(contentLength);}} catch (Exception e) {e.printStackTrace();if (listener != null) {listener.onFailure(e.getMessage());}} finally {try {if (fileOutputStream != null) {fileOutputStream.close();}if (inputStream != null) {inputStream.close();}} catch (IOException e) {e.printStackTrace();}}}/*** 声明DownLoadListener监听器*/public interface DownLoadListener {/*** 开始下载** @param totalSize*/void onStartLoading(long totalSize);/*** 下载中** @param currentSize byte* @param percent* @param speed byte/second*/void onLoading(long currentSize, float percent, float speed);/*** 下载完成** @param totalSize*/void onLoadingFinish(long totalSize);/*** 下载失败** @param error*/void onFailure(String error);}
}
使用
var intent = Intent(this@MainActivity, UpdateService::class.java).apply {putExtra(UpdateService.URL, it.downloadUrl)putExtra(UpdateService.ICON, R.drawable.ic_launcher)}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {this@MainActivity.startForegroundService(intent)} else {this@MainActivity.startService(intent)}