Android自动更新的实现
需求:
在我们开发App的过程中,总会遇到这样的需求。在App运行时检查是否有新版本出现,并对其进行更新。今天就来介绍一下,如何实现App的内部自动更新。
获取接口
首先,我们需要从后端获取关于版本信息数据,通过后端提供的接口返回的数据。数据格式采用Json,示例文本如下:
{
"appname":"Will.apk",
"serverVersion":"1.0.2",
"serverFlag":"1",
"lastForce":"1",
"updateurl":"http://www.baidu.com",
"upgrateinfo":"V1.0.2全新上线,赶快来体验吧!"
}
如果在学习过程中,你没有后端为你提供接口,那么参考我的另一篇文章,使用Jhipster生成一个简单的API接口。
Jhipster简单使用
实现过程
接下来开始进行我们Android端的实现。
思路:
1.实现bean用于对接后端接口实现app的更新
2.使用retrofit来请求版本更新的接口
3.下载apk
4.通过BroadcastReceiver来监听是否下载完成
步骤一:创建一个接受Json数据返回的类
我们从接口获取的Json数据需要一个类来解析,代码如下。
public class UpdateAppInfo {
private String appname;
private String serverVersion;
private String serverFlag;
private String lastForce;
private String updateurl;
private String upgradeinfo;
public String getAppname() {
return appname;
}
public void setAppname(String appname) {
this.appname = appname;
}
public String getServerVersion() {
return serverVersion;
}
public void setServerVersion(String serverVersion) {
this.serverVersion = serverVersion;
}
public String getServerFlag() {
return serverFlag;
}
public void setServerFlag(String serverFlag) {
this.serverFlag = serverFlag;
}
public String getLastForce() {
return lastForce;
}
public void setLastForce(String lastForce) {
this.lastForce = lastForce;
}
public String getUpdateurl() {
return updateurl;
}
public void setUpdateurl(String updateurl) {
this.updateurl = updateurl;
}
public String getUpgradeinfo() {
return upgradeinfo;
}
public void setUpgradeinfo(String upgradeinfo) {
this.upgradeinfo = upgradeinfo;
}
@Override
public String toString() {
return "UpdateAppInfo{" +
"appname='" + appname + '\'' +
", serverVersion='" + serverVersion + '\'' +
", serverFlag='" + serverFlag + '\'' +
", lastForce='" + lastForce + '\'' +
", updateurl='" + updateurl + '\'' +
", upgradeinfo='" + upgradeinfo + '\'' +
'}';
}
}
步骤二:Retrofit+RxJava实现网络接口
先加入依赖:
// Android 支持 Rxjava
// 此处一定要注意使用RxJava2的版本
compile 'io.reactivex.rxjava2:rxjava:2.0.1'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
// Android 支持 Retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
// 衔接 Retrofit & RxJava
// 此处一定要注意使用RxJava2的版本
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
接下来创建网络接口:
public interface NetService{
@GET("/api/update-infos/1")
Observable<UpdateAppInfo> getUpdateInfo();
}
通过工厂模式来创建NetService
public class ServiceFactory{
private static final String BaseUrl = "http://10.10.2.87:8080"
public static <T> T createServiceFrom(final Class<T> serviceClass){
Retrofit adapter = new Retrofit.Builder()
.baseUrl(BaseUrl)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx适配器
.addConverterFactory(GsonConverterFactory.create()) // 添加Gson转换器
.build();
return adapter.create(serviceClass);
}
}
步骤三:创建检测版本更新的接口
public class CheckUpdateUtils {
@SuppressWarnings("unused")
public static void checkUpdate(String appCode,String curVersion,final CheckCallBack updateCallback){
ApiService apiService = ServiceFactory.createServiceFrom(ApiService.class);
Log.e("check","开始这一步了吗1");
String appName = "hoolay";
apiService.getCall()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subject<UpdateAppInfo>() {
@Override
public boolean hasObservers() {
return false;
}
@Override
public boolean hasThrowable() {
return false;
}
@Override
public boolean hasComplete() {
return false;
}
@Override
public Throwable getThrowable() {
return null;
}
@Override
protected void subscribeActual(Observer<? super UpdateAppInfo> observer) {
}
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(UpdateAppInfo value) {
if(value.equals(null)||value.getUpdateurl().equals(null)){
Log.e("check","开始这一步了吗2");
updateCallback.onError();
}else {
Log.e("check","开始这一步了吗3");
updateCallback.onSuccess(value);
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
}
这里还需要结果回调监听:
//检测成功或失败的相关接口
public interface CheckAllBack{
void onSuccess(UpdateAppInfo updateInfo);
void onError();
}
步骤四:在Activity中完成的部分
- 创建一个获取当前版本号及版本名称的类
/**
* 获取当前的版本号及版本名称的工具类
*/
public class ApkVersionCodeUtils {
/**
* 获取当前本地apk的版本
*
* @param mContext
* @return
*/
public static int getVersionCode(Context mContext) {
int versionCode = 0;
try {
//获取软件版本号,对应AndroidManifest.xml下android:versionCode
versionCode = mContext.getPackageManager().
getPackageInfo(mContext.getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return versionCode;
}
/**
* 获取版本号名称
*
* @param context 上下文
* @return
*/
public static String getVerName(Context context) {
String verName = "";
try {
verName = context.getPackageManager().
getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return verName;
}
}
- 连接网络检查是否需要更新
CheckUpdateUtils.checkUpdate("apk", "1.0.0", new CheckCallBack() {
@Override
public void onSuccess(UpdateAppInfo updateAppInfo) {
String isForce = updateAppInfo.getLastForce();
String downUrl = updateAppInfo.getUpdateurl();
String updateinfo = updateAppInfo.getUpgradeinfo();
String appName = updateAppInfo.getAppname();
Log.e("aaa",appName);
if(isForce.equals("1")&& !TextUtils.isEmpty(updateinfo)){//强制更新
forceUpdate(MainActivity.this,appName,downUrl,updateinfo);
}else{//非强制更新
//正常升级
normalUpdate(MainActivity.this,appName,downUrl,updateinfo);
}
}
@Override
public void onError() {
noneUpdate(MainActivity.this);
}
});
- 更新对话框的使用:
private void forceUpdate(final Context context, final String appName, final String downUrl, final String updateinfo) {
mDialog = new AlertDialog.Builder(context);
mDialog.setTitle(appName+"又更新咯!");
mDialog.setMessage(updateinfo);
mDialog.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!canDownloadState()) {
showDownloadSetting();
return;
}
// DownLoadApk.download(MainActivity.this,downUrl,updateinfo,appName);
AppInnerDownLoder.downLoadApk(MainActivity.this,downUrl,appName);
}
}).setCancelable(false).create().show();
}
步骤五:使用HttpUrlConnection下载
public class AppInnerDownLoder {
public final static String SD_FOLDER = Environment.getExternalStorageDirectory()+ "/VersionChecker/";
private static final String TAG = AppInnerDownLoder.class.getSimpleName();
/**
* 从服务器中下载APK
*/
@SuppressWarnings("unused")
public static void downLoadApk(final Context mContext,final String downURL,final String appName ) {
final ProgressDialog pd; // 进度条对话框
pd = new ProgressDialog(mContext);
pd.setCancelable(false);// 必须一直下载完,不可取消
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMessage("正在下载安装包,请稍后");
pd.setTitle("版本升级");
pd.show();
new Thread() {
@Override
public void run() {
try {
File file = downloadFile(downURL,appName, pd);
sleep(3000);
installApk(mContext, file);
// 结束掉进度条对话框
pd.dismiss();
} catch (Exception e) {
pd.dismiss();
}
}
}.start();
}
/**
* 从服务器下载最新更新文件
*
* @param path
* 下载路径
* @param pd
* 进度条
* @return
* @throws Exception
*/
private static File downloadFile(String path,String appName ,ProgressDialog pd) throws Exception {
// 如果相等的话表示当前的sdcard挂载在手机上并且是可用的
if (Environment.MEDIA_MOUNTED.equals(Environment
.getExternalStorageState())) {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
// 获取到文件的大小
pd.setMax(conn.getContentLength());
InputStream is = conn.getInputStream();
String fileName = SD_FOLDER
+ appName+".apk";
File file = new File(fileName);
// 目录不存在创建目录
if (!file.getParentFile().exists())
file.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(file);
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buffer = new byte[1024];
int len;
int total = 0;
while ((len = bis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
total += len;
// 获取当前下载量
pd.setProgress(total);
}
fos.close();
bis.close();
is.close();
return file;
} else {
throw new IOException("未发现有SD卡");
}
}
/**
* 安装apk
*/
private static void installApk(Context mContext, File file) {
Uri fileUri = Uri.fromFile(file);
Intent it = new Intent();
it.setAction(Intent.ACTION_VIEW);
it.setDataAndType(fileUri, "application/vnd.android.package-archive");
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 防止打不开应用
mContext.startActivity(it);
}
/**
* 获取应用程序版本(versionName)
*
* @return 当前应用的版本号
*/
private static double getLocalVersion(Context context) {
PackageManager manager = context.getPackageManager();
PackageInfo info = null;
try {
info = manager.getPackageInfo(context.getPackageName(), 0);
} catch (NameNotFoundException e) {
Log.e(TAG, "获取应用程序版本失败,原因:" + e.getMessage());
return 0.0;
}
return Double.valueOf(info.versionName);
}
/**
* byte(字节)根据长度转成kb(千字节)和mb(兆字节)
*
* @param bytes
* @return
*/
public static String bytes2kb(long bytes) {
BigDecimal filesize = new BigDecimal(bytes);
BigDecimal megabyte = new BigDecimal(1024 * 1024);
float returnValue = filesize.divide(megabyte, 2, BigDecimal.ROUND_UP)
.floatValue();
if (returnValue > 1)
return (returnValue + "MB");
BigDecimal kilobyte = new BigDecimal(1024);
returnValue = filesize.divide(kilobyte, 2, BigDecimal.ROUND_UP)
.floatValue();
return (returnValue + "KB");
}
}
步骤六:监听App是否安装完成
public class ApkInstallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){
long downloadApkId =intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
installApk(context, downloadApkId);
}
}
/**
* 安装apk
*/
private void installApk(Context context,long downloadApkId) {
// 获取存储ID
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
long downId =sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L);
if(downloadApkId == downId){
DownloadManager downManager= (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Uri downloadFileUri = downManager.getUriForDownloadedFile(downloadApkId);
if (downloadFileUri != null) {
Intent install= new Intent(Intent.ACTION_VIEW);
install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}else{
Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
}
}
}
}
DownloadManager下载完成后会发出一个广播 android.intent.action.DOWNLOAD_COMPLETE 新建一个广播接收者即可,配置如下:
<receiver android:name=".ApkInstallReceiver">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
总结
本文介绍了Android如何简单实现更新的效果,主要参考了以下博文:
安卓开发实战之app之版本更新
更为详细的我没有提到的部分请详见原博。