自动更新组建的意义
- 能最及时的告知所有用户有新的版本
- 对用户来说更新更加的简单,无需打开第三方应用
可以强制用户更新。(一些特定场景下)
自动更新原理
1、apk安装包文件下载
2、利用Notification通知用户进度等消息
3、文件下载成功后调用系统安装程序
所需知识点
1、异步http请求文件下载,线程间通信
2、Notification相关知识点
3、如何调用安装程序进行安装
一、概述
本篇文章主要实现在应用内发现新版本,用户点击下载apk,同时在通知栏下实现下载进度更新,下载完成后自动弹出安装窗口等等功能,这是apk常见的功能模块!代码亲测有效。
二、具体实现
1.下载监听回调接口UpdateDownloadListener.Java
package com.czhappy.appupdate.utils;
public interface UpdateDownloadListener {
public void onStarted();
public void onProgressChanged(int progress, String downloadUrl);
public void onFinished(float completeSize, String downloadUrl);
public void onFailure();
}
2.文件下载及线程通信UpdateDownloadRequest.java
package com.czhappy.appupdate.utils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.DecimalFormat;
public class UpdateDownloadRequest implements Runnable{
private String downloadUrl;
private String localFilePath;
private UpdateDownloadListener listener;
private boolean isDownloading = false;
private long currentLength;
private DownloadResponseHandler downloadResponseHandler;
public UpdateDownloadRequest(String downloadUrl, String localFilePath, UpdateDownloadListener listener){
this.downloadUrl = downloadUrl;
this.localFilePath = localFilePath;
this.listener = listener;
this.isDownloading = true;
downloadResponseHandler = new DownloadResponseHandler();
}
private String getTwoPointFloatStr(float value){
DecimalFormat df = new DecimalFormat("0.00000000000");
return df.format(value);
}
private void makeRequest() throws IOException, InterruptedIOException{
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();
currentLength = connection.getContentLength();
if(!Thread.currentThread().isInterrupted()){
downloadResponseHandler.sendResponseMessage(connection.getInputStream());
}
}catch (Exception e){
throw e;
}
}
}
@Override
public void run() {
try{
makeRequest();
}catch (IOException e){
}
}
/**
* 下载过程中的异常
*/
public enum FailureCode{
UnknownHost, Socket, SocketTimeout, connectionTimeout,IO, HttpResponse,
Json, Interrupted
}
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 float completeSize = 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}));
}
protected void sendMessage(Message msg){
if(handler!=null){
handler.sendMessage(msg);
}else{
handleSelfMessage(msg);
}
}
protected Message obtainMessage(int responseMessge, Object response){
Message msg = null;
if(handler!=null){
msg = handler.obtainMessage(responseMessge, response);
}else{
msg = Message.obtain();
msg.what = responseMessge;
msg.obj = response;
}
return 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){
listener.onProgressChanged(progress, downloadUrl);
}
protected void onFinish(){
listener.onFinished(completeSize, "");
}
private void handleFailureMessage(FailureCode failureCode){
onFailure(failureCode);
}
protected void onFailure(FailureCode failureCode){
listener.onFailure();
}
void sendResponseMessage(InputStream is){
RandomAccessFile randomAccessFile = null;
completeSize=0;
try{
byte[] buffer = new byte[1024];
int length=-1;//读写长度
int limit=0;
randomAccessFile = new RandomAccessFile(localFilePath, "rwd");
while((length = is.read(buffer))!=-1){
if(isDownloading){
randomAccessFile.write(buffer, 0 ,length);
completeSize += length;
if(completeSize < currentLength){
Log.e("tag", "completeSize="+completeSize);
Log.e("tag", "currentLength="+currentLength);
progress = (int)(Float.parseFloat(getTwoPointFloatStr(completeSize/currentLength))*100);
Log.e("tag", "下载进度:"+progress);
if(limit % 30==0 && progress <= 100){//隔30次更新一次notification
sendProgressChangedMessage(progress);
}
limit++;
}
}
}
sendFinishMessage();
}catch(IOException e){
sendFailureMessage(FailureCode.IO);
}finally{
try{
if(is!=null){
is.close();
}
if(randomAccessFile!=null){
randomAccessFile.close();
}
}catch(IOException e){
sendFailureMessage(FailureCode.IO);
}
}
}
}
}
3.文件下载对外方法UpdateManager.java
package com.czhappy.appupdate.utils;
import android.util.Log;
import java.io.File;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
public class UpdateManager {
private static UpdateManager updateManager;
private ThreadPoolExecutor threadPoolExecutor;
private UpdateDownloadRequest request;
private 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 path
*/
private void checkLocalFilePath(String path) {
Log.e("tag", path);
File dir = new File(path.substring(0, path.lastIndexOf("/")+1));
if(!dir.exists()){
dir.mkdir();
}
File file = new File(path);
if(!file.exists()){
try{
file.createNewFile();
}catch (Exception e){
}
}
}
}
4.通知栏进度更新进程UpdateService.java
package com.czhappy.appupdate.utils;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
import com.czhappy.appupdate.R;
import java.io.File;
public class UpdateService extends Service{
private String apkUrl;
private String filePath;
private NotificationManager notificationManager;
private Notification notification;
@Override
public void onCreate() {
Log.e("tag", "UpdateService onCreate()");
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
filePath = Environment.getExternalStorageDirectory()+"/AppUpdate/czhappy.apk";
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("tag", "UpdateService onStartCommand()");
if(intent==null){
notifyUser(getString(R.string.update_download_failed), getString(R.string.update_download_failed), 0);
stopSelf();
}
apkUrl = 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(apkUrl, filePath, new UpdateDownloadListener() {
@Override
public void onStarted() {
Log.e("tag", "onStarted()");
}
@Override
public void onProgressChanged(int progress, String downloadUrl) {
Log.e("onProgressChanged", progress+"");
notifyUser(getString(R.string.update_download_progressing), getString(R.string.update_download_progressing), progress);
}
@Override
public void onFinished(float completeSize, String downloadUrl) {
Log.e("tag", "onFinished()");
notifyUser(getString(R.string.update_download_finish), getString(R.string.update_download_finish), 100);
stopSelf();
}
@Override
public void onFailure() {
Log.e("tag", "onFailure()");
notifyUser(getString(R.string.update_download_failed), getString(R.string.update_download_failed), 0);
stopSelf();
}
});
}
/**
* 更新notification
* @param result
* @param msg
* @param progress
*/
private void notifyUser(String result, String msg, int progress){
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), 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);
}
/**
* 进入apk安装程序
* @return
*/
private PendingIntent getContentIntent() {
Log.e("tag", "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");
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
startActivity(intent);
return pendingIntent;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
5.测试类MainActivity.java
package com.czhappy.appupdate.activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.czhappy.appupdate.R;
import com.czhappy.appupdate.dialog.CommonDialog;
import com.czhappy.appupdate.utils.UpdateService;
public class MainActivity extends AppCompatActivity {
private Button update_btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
update_btn = (Button) this.findViewById(R.id.update_btn);
update_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
checkVersion();
}
});
checkVersion();
}
private void checkVersion(){
//这里不发送检测新版本网络请求,直接进入下载新版本安装
CommonDialog.Builder builder = new CommonDialog.Builder(this);
builder.setTitle("升级提示");
builder.setMessage("发现新版本,请及时更新");
builder.setPositiveButton("立即升级", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
Intent intent = new Intent(MainActivity.this, UpdateService.class);
intent.putExtra("apkUrl", "http://121.42.53.175:8080/hello_project/resources/upload/TianQiBao201605231.apk");
startService(intent);
}
});
builder.setNegativeButton("下次再说", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.create().show();
}
}
源码链接地址:
http://download.csdn.net/detail/chenzheng8975/9712326