前言
應用更新應該是現在每個應用必備的一個功能。正是通過不斷的更新,不斷的調優,才使我們的應用更完善。當然在各大應用市場中,它們已經幫我們實現了這項功能,但是有一個問題,當我們的應用是在某度市場下載的應用,如果那天我們不在使用某度市場,而是用別的市場,之前的發佈的市場無法通知我們的應用,那麼是不是我們就無法更新了。所以封裝一個自己的應用自動更新還是比較有必要的。那麼今天我們就來學習一下,如何封裝自己的應用自動更新功能。
自動更新的意義
- 能及時告知所有用戶有新的版本
- 對用戶來說,更新更加簡單,無須打開第三方應用(避免應用來回切換,同時減少打開其他應用後用戶不再回到本應用)
- 可以強制用戶更新(一切特定的場景下)
- 更多的自動控制權
分析原理
- apk安裝包文件下載
- 利用Notification通知用戶更新進度
- 文件下載後調用系統安裝應用
其實說白了就是下載更新的apk然後安裝。如果對斷電續傳和通知不瞭解的話先看先這個小項目後臺異步斷電續傳文件下載這個例子下載(已經合併2個例子放到一個工程中了)這個小項目是我學習第一行代碼時寫的,在寫這篇文章突然想起來,現在回頭看看,即使是入門,代碼寫的也是真心好。條例清晰,接口回調,方法封裝,雖然小但是邏輯很清晰。
實踐
我們先開下大體的思路流程:
大致流程就是這樣。其實說白了就是下載任務然後安裝。這裏核心是下載部分那麼我就可以用後臺異步斷電續傳文件下載這個例子下載(已經合併2個例子放到一個工程中了)。在這裏我在提供例外一種方法。
- UpdateDownLoadListener這個類就是下載回調的監聽
public interface UpdateDownLoadListener {
/**
* 下載請求開始回調
*/
public void onStarted();
/**
* 進度更新回調
*
* @param progress
* @param downloadUrl
*/
public void onProgressChanged(int progress, String downloadUrl);
/**
* 下載完成回調
*
* @param completeSize
* @param downloadUrl
*/
public void onFinished(int completeSize, String downloadUrl);
/**
* 下載失敗回調
*/
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();//阻塞我們當前的線程
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();
}
}
/**
* 包含了下載過程中所有可能出現的異常情況
*/
public enum FailureCode {
UnknownHost, Socket, SocketTimeout, ConnectTimeout, IO, HttpResponse, Json, Interrupted
}
/**
* 文件下載 將消息傳遞個主線程
*/
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);
}
}
/**
* 獲取一個消息對象
*
* @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);
}
}
}
}
}
這段代碼有點長,簡單來看就開啓線程下載任務,根據現在的狀態利用handle發送各種狀態的消息,然後利用接口回調,調用接口,再讓啓動下載的類也就是我們後臺下載的服務類去實現接口並處理相應的邏輯。
- UpdateDownManager 下載調度管理器,調用我們的UpdateDownLoadRequest,也是下載任務的入口,在這裏我們爲了爲了健壯性加入一切判斷。並將下載任務設置單例模式,並用線程池,方便管理閒扯避免殭屍線程。
- UpdateDownService這裏就是我們啓動下載任務的地方
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來告知用戶下載進度
*
* @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()方法中啓動下載,下載完成結束當前服務。然後用Notification通知用戶,在用系統自帶的api安裝。最後就是在Activity啓動服務下載任務就能進行了。篇幅較長Activity的代碼我就不粘貼出來了。
結束
相比在第一行代碼中的,這段代碼多了做了一些邏輯上的處理,是代碼更健壯性。原理都是相同的,如果你是在小範圍應用或是自己做的練手應用想加入自動更新功能,就可以將這些代碼封裝到自己的工具類中,當然距離成熟框架還是有很大的距離,比如我們更新要和服務器版本對比。服務器推送新版本功能等等,但是思路都是這樣的。在這裏我只是拋磚引玉。身爲小白的我,還需努力。
寫的不好大家多多諒解。如有錯誤真心希望大家提出來。最後希望大家一起進步。加油!!!
源碼地址。