应用自动更新是现在每一个APP不可缺少的。
1. 应用自动更新的意义及途径
途径:各大应用市场。
意义:
1.及时告知用户最新版本;
2.用户更新简单,无需打开第三方应用;
3.可以强制用户更新(特定场景下,如有重大bug或用户使用版本过于陈旧);
4.更多的自主控制权
2. 应用自动更新原理
原理:apk文件的下载。
步骤:
1.apk安装包下载;
2.使用notification通知用户进度等信息;
3.apk下载完成后调用系统安装程序
想要成功的实现自动更新你需要知道异步HTTP请求文件的下载(HttpURLConnection或者OkHttp等其他网络请求来下载文件);线程间通信(便于通知notification进行更新);
Notification通知的使用;如何调用安装程序进行安装。
3. 核心代码
public interface UpdateDownloadListener {
/**
* 下载请求开始回调
*/
void onStarted();
/**
* 进度更新回调
*
* @param progress
* @param downloadUrl
*/
void onProgressChanged(int progress, String downloadUrl);
/**
* 下载完成回调
*
* @param completeSize
* @param downloadUrl
*/
void onFinished(int completeSize, String downloadUrl);
/**
* 下载失败回调
*/
void onFailure();
}
处理文件的下载和线程间的通信
/**
* Created by EWorld
*
* @description 负责处理文件的下载和线程间的通信
*/
public class UpdateDownloadRequest implements Runnable {
private String mDownloadUrl;//下载路径
private String mLocalFilePath;//
private UpdateDownloadListener mDownloadListener;
private boolean isDownloading = false;//标志位
private long mCurrentLength;
private DownloadResponseHandler mDownloadResponsedHandler;
//枚举类型的异常
public enum FailureCode {
UnknownHost, Socket, SocketTimeout, ConnectTimeout, IO, HttpResponse, Json, Interrupted
}
public UpdateDownloadRequest(String downloadUrl, String localFilePath,
UpdateDownloadListener downloadListener) {
this.mDownloadUrl = downloadUrl;
this.mDownloadListener = downloadListener;
this.mLocalFilePath = localFilePath;
this.isDownloading = true;
this.mDownloadResponsedHandler = new DownloadResponseHandler();
}
/**
* 格式化数据,保留到小数点后两位
*
* @param value
* @return
*/
private String getTwoPointFloatStringStr(float value) {
DecimalFormat decimalFormat = new DecimalFormat("0.00");
return decimalFormat.format(value);
}
@Override
public void run() {
try {
makeRequest();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* @throws IOException
* @throws InterruptedException
*/
private void makeRequest() throws IOException, InterruptedException {
//如果线程没有被中断,建立连接就
if (!Thread.currentThread().isInterrupted()) {
try {
//创建URL对象
URL url = new URL(mDownloadUrl);
//获取HttpURLConnection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
//强制打开为keep-alive
connection.setRequestProperty("Connection", "Keep-Alive");
//阻塞当前线程
connection.connect();
mCurrentLength = connection.getContentLength();
//连接至此已经建立,下面开始下载
if (!Thread.currentThread().isInterrupted()) {
//获取输入流
mDownloadResponsedHandler.sendResponseMessage(connection.getInputStream());
}
} catch (IOException e) {
throw e;
}
}
}
/**
* 用来真正下载文件,并发送消息和回调接口
*/
public class DownloadResponseHandler {
protected static final int SUCCESS_MESSAGE = 0;
protected static final int FAILURE_MESSAGE = 1;
protected static final int START_MESSAGE = 2;
protected static final int FINISH_MESSAGE = 3;
protected static final int NETWORK_OFF = 4;
private static final int PROGRESS_CHANGED = 5;
private int mCompleteSize = 0;
private int progress = 0;
private Handler handler;
public DownloadResponseHandler() {
handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
handleSelfMessage(msg);
}
};
}
/**
* 用来发送不同的消息对象
*/
protected void sendFinishMessage() {
sendMessage(obtainMessage(FINISH_MESSAGE, null));
}
private void sendProgressChangedMessage(int progress) {
sendMessage(obtainMessage(PROGRESS_CHANGED, new Object[]{progress}));
}
protected void sendFailureMessage(FailureCode failureCode) {
sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{failureCode}));
}
/**
* @param msg
*/
protected void sendMessage(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 = null;
if (handler != null) {
msg = handler.obtainMessage(responseMessage, response);
} else {
msg = Message.obtain();
msg.what = responseMessage;
msg.obj = response;
}
return msg;
}
/**
* @param msg
*/
protected void handleSelfMessage(Message msg) {
Object[] response;
switch (msg.what) {
case FAILURE_MESSAGE:
response = (Object[]) msg.obj;
sendFailureMessage((FailureCode) response[0]);
break;
case PROGRESS_CHANGED:
response = (Object[]) msg.obj;
handleProgressChangedMessage(((Integer) response[0]).intValue());
break;
case FINISH_MESSAGE:
onFinish();
break;
}
}
protected void handleProgressChangedMessage(int progress) {
mDownloadListener.onProgressChanged(progress, mDownloadUrl);
}
protected void handleFailureMessage(FailureCode failureCode){
onFailure(failureCode);
}
private void onFinish() {
mDownloadListener.onFinished(mCompleteSize, "");
}
private void onFailure(FailureCode failureCode) {
mDownloadListener.onFailure();
}
/**
* 文件下载方法
*
* @param inputStream
*/
protected void sendResponseMessage(InputStream inputStream) {
//建立文件读写流
RandomAccessFile randomAccessFile = null;
//完成大小
mCompleteSize = 0;
//开始读
try {
//缓存
byte[] buffer = new byte[1024];
//读写长度
int len = -1;
//
int limit = 0;
randomAccessFile = new RandomAccessFile(mLocalFilePath, "rwd");
while ((len = inputStream.read(buffer)) != -1) {
if (isDownloading) {
randomAccessFile.write(buffer, 0, len);
mCompleteSize += len;
if (mCompleteSize < mCurrentLength) {
progress = (int) Float.parseFloat
(getTwoPointFloatStringStr(mCompleteSize / mCurrentLength));
if (limit / 30 == 0 && progress <= 100) {
sendProgressChangedMessage(progress);
}
limit++;
}
}
}
sendFinishMessage();
} catch (Exception e) {
e.printStackTrace();
sendFailureMessage(FailureCode.IO);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
sendFailureMessage(FailureCode.IO);
}
}
}
}
}
/**
* Created by EWorld
*
* @description 下载调度管理器,调用我们的UpdateDownloadRequest
*/
public class UpdateManager {
private static UpdateManager updateManager;
private ThreadPoolExecutor threadPoolExecutor;
private UpdateDownloadRequest request;
public UpdateManager() {
threadPoolExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
}
static {
updateManager = new UpdateManager();
}
public static UpdateManager getInstance() {
return updateManager;
}
public void startDownloads(String downloadUrl, String localPath, UpdateDownloadListener listener) {
if (request != null) {
return;
}
checkLocalFilePath(localPath);
request = new UpdateDownloadRequest(downloadUrl, localPath, listener);
Future<?> future = threadPoolExecutor.submit(request);
}
/**
* 检查文件路径是否存在
*
* @param localPath
*/
private void checkLocalFilePath(String localPath) {
File dir = new File(localPath.substring(0, localPath.indexOf("/") + 1));
if (!dir.exists()) {
dir.mkdir();
}
File file = new File(localPath);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* Created by EWorld
*
* @description app更新下载后台服务
*/
public class UpdateService extends Service {
private String apkUlr;
private String filePath;
private NotificationManager notificationManager;
private Notification notification;
@Override
public void onCreate() {
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
filePath = Environment.getExternalStorageState() + "/78/seveneight.apk";
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
notifyUser(getString(R.string.update_download_failed), getString(R.string.update_download_failed_msg), 0);
stopSelf();
}
apkUlr = intent.getStringExtra("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() {
UpdateManager.getInstance().startDownloads(apkUlr, filePath, new UpdateDownloadListener() {
@Override
public void onStarted() {
}
@Override
public void onProgressChanged(int progress, String downloadUrl) {
notifyUser(getString(R.string.update_download_processing),
getString(R.string.update_download_processing), progress);
}
@Override
public void onFinished(int completeSize, String downloadUrl) {
notifyUser(getString(R.string.update_download_finish),
getString(R.string.update_download_finish), 100);
stopSelf();
}
@Override
public void onFailure() {
notifyUser(getString(R.string.update_download_failed),
getString(R.string.update_download_failed_msg), 0);
stopSelf();
}
});
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void notifyUser(String result, String reason, int progress) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(getString(R.string.app_name));
if (progress > 0 && progress < 100) {
builder.setProgress(100, progress, false);
} else {
builder.setProgress(0, 0, false);
}
builder.setAutoCancel(true);
builder.setWhen(System.currentTimeMillis());
builder.setTicker(result);
builder.setContentIntent(progress >= 100 ? getContentIntent() :
PendingIntent.getActivity(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT));
notification = builder.build();
notificationManager.notify(0, notification);
}
private PendingIntent getContentIntent() {
File file = new File(filePath);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + file.getAbsolutePath()), "application/vnd.android.package-archive");
PendingIntent pendingIntent = PendingIntent.getActivity
(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pendingIntent;
}
}
启动更新
private void autoUpdate() {
Intent intent = new Intent(this,UpdateService.class);
intent.putExtra("lastVersion","2.2.0");
startService(intent);
}