前言
應(yīng)用更新應(yīng)該是現(xiàn)在每個(gè)應(yīng)用必備的一個(gè)功能逊躁。正是通過不斷的更新茅信,不斷的調(diào)優(yōu)盾舌,才使我們的應(yīng)用更完善。當(dāng)然在各大應(yīng)用市場中蘸鲸,它們已經(jīng)幫我們實(shí)現(xiàn)了這項(xiàng)功能妖谴,但是有一個(gè)問題,當(dāng)我們的應(yīng)用是在某度市場下載的應(yīng)用酌摇,如果那天我們不在使用某度市場膝舅,而是用別的市場,之前的發(fā)布的市場無法通知我們的應(yīng)用窑多,那么是不是我們就無法更新了仍稀。所以封裝一個(gè)自己的應(yīng)用自動(dòng)更新還是比較有必要的。那么今天我們就來學(xué)習(xí)一下埂息,如何封裝自己的應(yīng)用自動(dòng)更新功能技潘。
自動(dòng)更新的意義
- 能及時(shí)告知所有用戶有新的版本
- 對用戶來說,更新更加簡單千康,無須打開第三方應(yīng)用(避免應(yīng)用來回切換享幽,同時(shí)減少打開其他應(yīng)用后用戶不再回到本應(yīng)用)
- 可以強(qiáng)制用戶更新(一切特定的場景下)
- 更多的自動(dòng)控制權(quán)
分析原理
- apk安裝包文件下載
- 利用Notification通知用戶更新進(jìn)度
- 文件下載后調(diào)用系統(tǒng)安裝應(yīng)用
其實(shí)說白了就是下載更新的apk然后安裝。如果對斷電續(xù)傳和通知不了解的話先看先這個(gè)小項(xiàng)目后臺異步斷電續(xù)傳文件下載這個(gè)小項(xiàng)目是我學(xué)習(xí)第一行代碼時(shí)寫的吧秕,在寫這篇文章突然想起來琉闪,現(xiàn)在回頭看看,即使是入門砸彬,代碼寫的也是真心好颠毙。條例清晰,接口回調(diào)砂碉,方法封裝蛀蜜,雖然小但是邏輯很清晰。
實(shí)踐
我們先開下大體的思路流程:
大致流程就是這樣增蹭。其實(shí)說白了就是下載任務(wù)然后安裝滴某。這里核心是下載部分那么我就可以用后臺異步斷電續(xù)傳文件下載這個(gè)例子下載(已經(jīng)合并2個(gè)例子放到一個(gè)工程中了)。在這里我在提供例外一種方法。
- UpdateDownLoadListener這個(gè)類就是下載回調(diào)的監(jiān)聽
public interface UpdateDownLoadListener {
/**
* 下載請求開始回調(diào)
*/
public void onStarted();
/**
* 進(jìn)度更新回調(diào)
*
* @param progress
* @param downloadUrl
*/
public void onProgressChanged(int progress, String downloadUrl);
/**
* 下載完成回調(diào)
*
* @param completeSize
* @param downloadUrl
*/
public void onFinished(int completeSize, String downloadUrl);
/**
* 下載失敗回調(diào)
*/
public void onFailure();
}
- UpdateDownLoadRequest 真正的處理文件下載和線程間的通信
public class UpdateDownLoadRequest implements Runnable {
private String downloadUrl;
private String downloadPath;
private UpdateDownLoadListener mLoadListener;
private long contentLength;
private boolean isDownloading = false;
private UpdateDownRequestHandle mHandle;
public UpdateDownLoadRequest(String downloadUrl, String downloadPath, UpdateDownLoadListener loadListener) {
this.downloadPath = downloadPath;
this.downloadUrl = downloadUrl;
this.mLoadListener = loadListener;
this.isDownloading = true;
this.mHandle = new UpdateDownRequestHandle();
}
//真正的建立連接
private void makeRequest() throws IOException {
if (!Thread.currentThread().isInterrupted()) {
try {
URL url = new URL(downloadUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setRequestProperty("Connection", "Keep-Alive");
connection.connect();//阻塞我們當(dāng)前的線程
contentLength = connection.getContentLength();
if (!Thread.currentThread().isInterrupted()) {
//完成文件的下載
mHandle.sendResponseMessage(connection.getInputStream());
}
} catch (IOException e) {
throw e;
}
}
}
@Override
public void run() {
try {
makeRequest();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 包含了下載過程中所有可能出現(xiàn)的異常情況
*/
public enum FailureCode {
UnknownHost, Socket, SocketTimeout, ConnectTimeout, IO, HttpResponse, Json, Interrupted
}
/**
* 文件下載 將消息傳遞個(gè)主線程
*/
public class UpdateDownRequestHandle {
private static final int SUCCESS_MESSAGE = 0;
private static final int FAILURE_MESSAGE = 1;
private static final int START_MESSAGE = 2;
private static final int FINISH_MESSAGE = 3;
private static final int NETWORK_MESSAGE = 4;
private static final int PROGRESS_CHANGED = 5;
private Handler handler;//完成線程見的通信
private int currentSize = 0;
private int progress = 0;
public UpdateDownRequestHandle() {
handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
handleSelfMessage(msg);
}
};
}
protected void handleSelfMessage(Message msg) {
Object[] response;
switch (msg.what) {
case FAILURE_MESSAGE:
response = (Object[]) msg.obj;
handlerFailureMessage((FailureCode) response[0]);
break;
case PROGRESS_CHANGED:
response = (Object[]) msg.obj;
int p = ((Integer) response[0]).intValue();
handlerProgressChangedMessage(p);
break;
case FINISH_MESSAGE:
onFinish();
break;
}
}
//各種消息的處理邏輯
protected void handlerProgressChangedMessage(int progress) {
mLoadListener.onProgressChanged(progress, "");
}
protected void handlerFailureMessage(FailureCode failureCode) {
onFailure(failureCode);
}
public void onFinish() {
mLoadListener.onFinished(currentSize, "");
}
public void onFailure(FailureCode failureCode) {
Log.d("TAG", "onFailure: " + failureCode);
mLoadListener.onFailure();
}
protected void sendFailureMsg(FailureCode code) {
sendMsg(obtainMessage(FAILURE_MESSAGE, new Object[]{code}));
}
protected void sendFinishMsg() {
sendMsg(obtainMessage(FINISH_MESSAGE, null));
}
protected void sendProgressChangedMsg(int progress) {
sendMsg(obtainMessage(PROGRESS_CHANGED, new Object[]{progress}));
}
protected void sendMsg(Message msg) {
if (handler != null) {
handler.sendMessage(msg);
} else {
handleSelfMessage(msg);
}
}
/**
* 獲取一個(gè)消息對象
*
* @param responseMessage
* @param response
* @return
*/
protected Message obtainMessage(int responseMessage, Object response) {
Message msg;
if (handler != null) {
msg = handler.obtainMessage(responseMessage, response);
} else {
msg = Message.obtain();
msg.what = responseMessage;
msg.obj = response;
}
return msg;
}
public void sendResponseMessage(InputStream inputStream) {
RandomAccessFile acesFile = null;
currentSize = 0;
try {
acesFile = new RandomAccessFile(downloadPath, "rwd");
int limit = 0;
int length = -1;
byte[] bs = new byte[1024];
while ((length = inputStream.read(bs)) != -1) {
if (isDownloading) {
acesFile.write(bs, 0, length);
currentSize += length;
if (currentSize < contentLength) {
progress = (int) (currentSize * 100 / contentLength);
if (limit % 30 == 0 && progress <= 100) {
//為了限制一下notification的更新頻率
sendProgressChangedMsg(progress);
}
if (progress >= 100) {
//下載完成
sendProgressChangedMsg(progress);
}
limit++;
}
}
}
sendFinishMsg();
} catch (IOException e) {
sendFailureMsg(FailureCode.IO);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (acesFile != null) {
acesFile.close();
}
} catch (IOException e) {
sendFailureMsg(FailureCode.IO);
}
}
}
}
}
這段代碼有點(diǎn)長霎奢,簡單來看就開啟線程下載任務(wù)户誓,根據(jù)現(xiàn)在的狀態(tài)利用handle發(fā)送各種狀態(tài)的消息,然后利用接口回調(diào)幕侠,調(diào)用接口帝美,再讓啟動(dòng)下載的類也就是我們后臺下載的服務(wù)類去實(shí)現(xiàn)接口并處理相應(yīng)的邏輯。
- UpdateDownManager 下載調(diào)度管理器晤硕,調(diào)用我們的UpdateDownLoadRequest悼潭,也是下載任務(wù)的入口,在這里我們?yōu)榱藶榱私研约尤胍磺信袛辔韫俊2⑾螺d任務(wù)設(shè)置單例模式舰褪,并用線程池,方便管理閑扯避免僵尸線程疏橄。
- UpdateDownService這里就是我們啟動(dòng)下載任務(wù)的地方
public class UpdateDownService extends Service {
private static final String TAG = "UpdateDownService";
private String apkUrl;
private String filePath;
private NotificationManager mNotificationManager;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
filePath = Environment.getExternalStorageDirectory() + "/testDownload/test.apk";
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
notifyUser("下載失敗", "下載失敗原因", 0);
stopSelf();
}
apkUrl = intent.getStringExtra("apkUrl");
Log.i("TAG", "下載地址: " + apkUrl);
notifyUser(getString(R.string.update_download_start), getString(R.string.update_download_start), 0);
startDownload();
return super.onStartCommand(intent, flags, startId);
}
private void startDownload() {
UpdateDownManager.getInstance().startDownloads(apkUrl, filePath, new UpdateDownLoadListener() {
@Override
public void onStarted() {
}
@Override
public void onProgressChanged(int progress, String downloadUrl) {
Log.d(TAG, "onProgressChanged: "+progress);
notifyUser(getString(R.string.update_download_processing), getString(R.string.update_download_processing), progress);
}
@Override
public void onFinished(int completeSize, String downloadUrl) {
Log.d(TAG, "onProgressChanged: "+completeSize);
notifyUser(getString(R.string.update_download_finish), getString(R.string.update_download_finish), 100);
stopSelf();
}
@Override
public void onFailure() {
Log.d(TAG, "onProgressChanged: ");
notifyUser(getString(R.string.update_download_failed), getString(R.string.update_download_failed_msg), 0);
stopSelf();
}
});
}
/**
* 更新notification來告知用戶下載進(jìn)度
*
* @param result
* @param reason
* @param progress
*/
private void notifyUser(String result, String reason, int progress) {
Notification mNotification;
NotificationCompat.Builder build = new NotificationCompat.Builder(this);
build.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentTitle(getString(R.string.app_name));
if (progress > 0 && progress < 100) {
build.setProgress(100, progress, false);
} else {
build.setProgress(0, 0, false);
}
build.setAutoCancel(false);
build.setWhen(System.currentTimeMillis());
build.setTicker(result);
build.setContentIntent(progress >= 100 ? getContentIntent() : PendingIntent.getActivity(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT));
mNotification = build.build();
mNotificationManager.notify(0, mNotification);
}
public PendingIntent getContentIntent() {
File apkFile = new File(filePath);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + apkFile.getAbsolutePath()), "application/vnd.android.package-archive");
return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public void onDestroy() {
super.onDestroy();
}
我們在onStartCommand()方法中啟動(dòng)下載占拍,下載完成結(jié)束當(dāng)前服務(wù)。然后用Notification通知用戶软族,在用系統(tǒng)自帶的api安裝刷喜。最后就是在Activity啟動(dòng)服務(wù)下載任務(wù)就能進(jìn)行了。篇幅較長Activity的代碼我就不粘貼出來了立砸。
結(jié)束
相比在第一行代碼中的掖疮,這段代碼多了做了一些邏輯上的處理,是代碼更健壯性颗祝。原理都是相同的浊闪,如果你是在小范圍應(yīng)用或是自己做的練手應(yīng)用想加入自動(dòng)更新功能,就可以將這些代碼封裝到自己的工具類中螺戳,當(dāng)然距離成熟框架還是有很大的距離搁宾,比如我們更新要和服務(wù)器版本對比。服務(wù)器推送新版本功能等等倔幼,但是思路都是這樣的盖腿。在這里我只是拋磚引玉。身為小白的我损同,還需努力翩腐。 后續(xù)會更新在線更新等熱修復(fù)的文章敬請期待。
寫的不好大家多多諒解膏燃。如有錯(cuò)誤真心希望大家提出來茂卦。最后希望大家一起進(jìn)步。加油W榱ā5攘处渣!
源碼地址。