Android 检查更新
首先服务类
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"; //Tag
public static final String ICON = "icon"; //Tag
public static final String MD5 = "md5"; //Tag
private 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 {
@Override
public void onReceive(Context context, Intent intent) {
installApk();
}
}
@Override
public 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);
}
@Override
public void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mAuthInstallApkBroadcastReceiver);
updateListener = null;
super.onDestroy();
isRunning = false;
}
@Override
public IBinder onBind(Intent intent) {
return new UpdateServiceBinder();
}
@Override
public 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()) {
@Override
public 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
@Override
public 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() {
@Override
public void onStartLoading(long totalSize) {
// Do nothing because of auto-generated
if(updateListener != null) updateListener.onStart(totalSize);
}
@Override
public 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);
}
}
@Override
public void onLoadingFinish(long totalSize) {
Message msg = Message.obtain();
msg.what = 2;
handler.sendMessage(msg);
if(updateListener != null) updateListener.onLoadingFinish(totalSize);
}
@Override
public 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
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
public class DownLoadManager {
private static final String MAIN = "main"; //Tag
private 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() {
@Override
public 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)
}