简单实现安卓app自动更新功能

677 阅读8分钟

简单实现安卓自动更新

一般的安卓app都有自动更新功能,实现app的更新,以让用户体验新版本的功能,这里也是项目中用到的,今天就来总结一下,代码应该有点多,还请耐心点哈。 安卓应用实现自动更新比较简单,这里跟大家介绍下: ####第一步 服务器端:

  • 服务端提供一个借口,或者网址,我这里就用的服务器是tomcat,这里提供一个网址如下:
//也就是一个json数据接口
  public static final String UPDATE_URL = "http://192.168.1.103:8080/update.json";

我们来看下json数据参数:

{
//app名字
appname: "爱新闻1.1",
//服务器版本号
serverVersion: "2",
//服务器标志
serverFlag: "1",
//是否强制更新
lastForce: "1",
//apk下载地址,这里我已经下载了官方的apk,放到了服务器里面
updateurl: "http://192.168.1.103:8080/36Kr.apk",
//版本的更新的描述
upgradeinfo: "V1.1版本更新,你想不想要试一下哈!!!"
}

好了以上的是服务器端的信息,在这里不需要多说了,我们来看下客户端的吧。 ####第二步 客户端需要实现: 首先我们要去解析服务端给的json,那么我们就要来创建一个model类了(代码过多,这里只有字段,getter和setter方法自己创建):

//app名字
	private String appname;
	//服务器版本
	private String serverVersion;
	//服务器标志
	private String serverFlag;
	//强制升级
	private String lastForce;
	//app最新版本地址
	private String updateurl;
	//升级信息
	private String upgradeinfo;

在这里使用了一个辅助类,基本和model字段差不多:

public class UpdateInformation {
	public static String appname = MyApplication.getInstance()
			.getResources().getString(R.string.app_name);
	public static int localVersion = 1;// 本地版本
	public static String versionName = ""; // 本地版本名
	public static int serverVersion = 1;// 服务器版本
	public static int serverFlag = 0;// 服务器标志
	public static int lastForce = 0;// 之前强制升级版本
	public static String updateurl = "";// 升级包获取地址
	public static String upgradeinfo = "";// 升级信息

	public static String downloadDir = "wuyinlei";// 下载目录
}

我们知道,我们在进入app的时候,这个时候如果检测到服务器端有了新的版本,就回弹出提示框,提示我们更新。这个我们在MainActivity里面处理逻辑(onCreate()方法里面):

 OkhttpManager.getAsync(Config.UPDATE_URL, new OkhttpManager.DataCallBack() {
            @Override
            public void requestFailure(Request request, Exception e) {

            }
            @Override
            public void requestSuccess(String result) {
                try {
                    Log.d("wuyiunlei",result);
                    JSONObject object = new JSONObject(result);
                    UpdateInfoModel model = new UpdateInfoModel();
                    model.setAppname(object.getString("appname"));
                    model.setLastForce(object.getString("lastForce"));
                    model.setServerFlag(object.getString("serverFlag"));
                    model.setServerVersion(object.getString("serverVersion"));
                    model.setUpdateurl(object.getString("updateurl"));
                    model.setUpgradeinfo(object.getString("upgradeinfo"));
                    tmpMap.put(DeliverConsts.KEY_APP_UPDATE, model);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                //发送广播
                sendBroadcast(new Intent(UpdateReceiver.UPDATE_ACTION));
            }
        });

当然了,我们也要注册和结束广播:

 /**
     * 广播注册
     */
    private void registerBroadcast() {
        mUpdateReceiver = new UpdateReceiver(false);
        mIntentFilter = new IntentFilter(UpdateReceiver.UPDATE_ACTION);
        this.registerReceiver(mUpdateReceiver, mIntentFilter);
    }
 /**
     * 广播卸载
     */
    private void unRegisterBroadcast() {
        try {
            this.unregisterReceiver(mUpdateReceiver);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

####好了,接下来我们看下我们自定义的广播接收者UpdateReceiver .java:


/**
 * 版本更新升级 广播接受者
 *
 */
public class UpdateReceiver extends BroadcastReceiver {
	private AlertDialog.Builder mDialog;
	public static final String UPDATE_ACTION = "wuyinlei_aixinwen";
	private SharedPreferencesHelper mSharedPreferencesHelper;
	private boolean isShowDialog;

	public UpdateReceiver() {
	}

	public UpdateReceiver(boolean isShowDialog) {
		super();
		this.isShowDialog = isShowDialog;
	}

	@Override
	public void onReceive(Context context, Intent intent) {
		mSharedPreferencesHelper = mSharedPreferencesHelper
				.getInstance(MyApplication.getInstance());
		//当然了,这里也可以直接new处hashmap
		HashMap<String, Object> tempMap = MyApplication.getInstance()
				.getTempMap();
		UpdateInfoModel model = (UpdateInfoModel) tempMap
				//就是一个标志
				.get(DeliverConsts.KEY_APP_UPDATE);
		try {

			/**
			 * 获取到当前的本地版本
			 */
			UpdateInformation.localVersion = MyApplication
					.getInstance()
					//包管理独享
					.getPackageManager()
					//包信息
					.getPackageInfo(
							MyApplication.getInstance()
									.getPackageName(), 0).versionCode;
			/**
			 * 获取到当前的版本名字
			 */
			UpdateInformation.versionName = MyApplication
					.getInstance()
					.getPackageManager()
					.getPackageInfo(
							MyApplication.getInstance()
									.getPackageName(), 0).versionName;
		} catch (Exception e) {
			e.printStackTrace();
		}
		//app名字
		UpdateInformation.appname = MyApplication.getInstance()
				.getResources().getString(R.string.app_name);
		//服务器版本
		UpdateInformation.serverVersion = Integer.parseInt(model
				.getServerVersion());
		//服务器标志
		UpdateInformation.serverFlag = Integer.parseInt(model.getServerFlag());
		//强制升级
		UpdateInformation.lastForce = Integer.parseInt(model.getLastForce());
		//升级地址
		UpdateInformation.updateurl = model.getUpdateurl();
		//升级信息
		UpdateInformation.upgradeinfo = model.getUpgradeinfo();

		//检查版本
		checkVersion(context);

	}

	/**
	 * 检查版本更新
	 * 
	 * @param context
     */
	public void checkVersion(Context context) {
		if (UpdateInformation.localVersion < UpdateInformation.serverVersion) {
			// 需要进行更新
			mSharedPreferencesHelper.putIntValue(
					//有新版本
					SharedPreferencesTag.IS_HAVE_NEW_VERSION, 1);
			//更新
			update(context);
		} else {
			mSharedPreferencesHelper.putIntValue(
					SharedPreferencesTag.IS_HAVE_NEW_VERSION, 0);
			if (isShowDialog) {
				//没有最新版本,不用升级
				noNewVersion(context);
			}
			clearUpateFile(context);
		}
	}

	/**
	 * 进行升级
	 * 
	 * @param context
	 */
	private void update(Context context) {
		if (UpdateInformation.serverFlag == 1) {
			// 官方推荐升级
			if (UpdateInformation.localVersion < UpdateInformation.lastForce) {
				//强制升级
				forceUpdate(context);
			} else {
				//正常升级
				normalUpdate(context);
			}

		} else if (UpdateInformation.serverFlag == 2) {
			// 官方强制升级
			forceUpdate(context);
		}
	}

	/**
	 * 没有新版本
	 * @param context
     */
	private void noNewVersion(final Context context) {
		mDialog = new AlertDialog.Builder(context);
		mDialog.setTitle("版本更新");
		mDialog.setMessage("当前为最新版本");
		mDialog.setNegativeButton("确定", new DialogInterface.OnClickListener() {

			@Override
			public void onClick(DialogInterface dialog, int which) {
				dialog.dismiss();
			}
		}).create().show();
	}

	/**
	 * 强制升级 ,如果不点击确定升级,直接退出应用
	 * 
	 * @param context
	 */
	private void forceUpdate(final Context context) {
		mDialog = new AlertDialog.Builder(context);
		mDialog.setTitle("版本更新");
		mDialog.setMessage(UpdateInformation.upgradeinfo);

		mDialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
				Intent mIntent = new Intent(context, UpdateService.class);
				mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				mIntent.putExtra("appname", UpdateInformation.appname);
				mIntent.putExtra("appurl", UpdateInformation.updateurl);
				//启动服务
				context.startService(mIntent);
			}
		}).setNegativeButton("退出", new DialogInterface.OnClickListener() {

			@Override
			public void onClick(DialogInterface dialog, int which) {
				// 直接退出应用
				//ManagerActivity.getInstance().finishActivity();
				System.exit(0);
			}
		}).setCancelable(false).create().show();
	}

	/**
	 * 正常升级,用户可以选择是否取消升级
	 * 
	 * @param context
	 */
	private void normalUpdate(final Context context) {
		mDialog = new AlertDialog.Builder(context);
		mDialog.setTitle("版本更新");
		mDialog.setMessage(UpdateInformation.upgradeinfo);
		mDialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
				Intent mIntent = new Intent(context, UpdateService.class);
				mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				//传递数据
				mIntent.putExtra("appname", UpdateInformation.appname);
				mIntent.putExtra("appurl", UpdateInformation.updateurl);
				context.startService(mIntent);
			}
		}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
				dialog.dismiss();
			}
		}).create().show();
	}

	/**
	 * 清理升级文件
	 * 
	 * @param context
	 */
	private void clearUpateFile(final Context context) {
		File updateDir;
		File updateFile;
		if (Environment.MEDIA_MOUNTED.equals(Environment
				.getExternalStorageState())) {
			updateDir = new File(Environment.getExternalStorageDirectory(),
					UpdateInformation.downloadDir);
		} else {
			updateDir = context.getFilesDir();
		}
		updateFile = new File(updateDir.getPath(), context.getResources()
				.getString(R.string.app_name) + ".apk");
		if (updateFile.exists()) {
			Log.d("update", "升级包存在,删除升级包");
			updateFile.delete();
		} else {
			Log.d("update", "升级包不存在,不用删除升级包");
		}
	}
}

####接下最后我们来看下服务吧UpdateService .java:

/**
 * 不要忘记注册,在mainfest文件中
*/
public class UpdateService extends Service {
	// BT字节参考量
	private static final float SIZE_BT = 1024L;
	// KB字节参考量
	private static final float SIZE_KB = SIZE_BT * 1024.0f;
	// MB字节参考量
	private static final float SIZE_MB = SIZE_KB * 1024.0f;

	private final static int DOWNLOAD_COMPLETE = 1;// 完成
	private final static int DOWNLOAD_NOMEMORY = -1;// 内存异常
	private final static int DOWNLOAD_FAIL = -2;// 失败

	private String appName = null;// 应用名字
	private String appUrl = null;// 应用升级地址
	private File updateDir = null;// 文件目录
	private File updateFile = null;// 升级文件

	// 通知栏
	private NotificationManager updateNotificationManager = null;
	private Notification updateNotification = null;

	private Intent updateIntent = null;// 下载完成
	private PendingIntent updatePendingIntent = null;// 在下载的时候

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}

	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		appName = intent.getStringExtra("appname");
		appUrl = intent.getStringExtra("appurl");
		updateNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		updateNotification = new Notification();
		//通知图标
		updateNotification.icon = R.mipmap.head;
		//通知信息描述
		updateNotification.tickerText = "正在下载 " + appName;
		updateNotification.when = System.currentTimeMillis();
		updateIntent = new Intent(this, MyApplication.class);
		updatePendingIntent = PendingIntent.getActivity(this, 0, updateIntent,
				0);
		updateNotification.contentIntent = updatePendingIntent;
		updateNotification.contentIntent.cancel();
		updateNotification.contentView = new RemoteViews(getPackageName(),
				//这个布局很简单,就是一个图片和两个textview,分别是正在下载和下载进度
				R.layout.download_notification);
		updateNotification.contentView.setTextViewText(
				R.id.download_notice_name_tv, appName + " 正在下载");
		updateNotification.contentView.setTextViewText(
				R.id.download_notice_speed_tv, "0MB (0%)");
		updateNotificationManager.notify(0, updateNotification);
		new UpdateThread().execute();
	}

	/**
	 * 在这里使用了asynctask异步任务来下载
	 */
	class UpdateThread extends AsyncTask<Void, Void, Integer> {
		@Override
		protected Integer doInBackground(Void... params) {
			return downloadUpdateFile(appUrl);
		}

		@Override
		protected void onPostExecute(Integer result) {
			super.onPostExecute(result);

			if (result == DOWNLOAD_COMPLETE) {
				Log.d("update", "下载成功");
				String cmd = "chmod 777 " + updateFile.getPath();
				try {
					Runtime.getRuntime().exec(cmd);
				} catch (IOException e) {
					e.printStackTrace();
				}
				Uri uri = Uri.fromFile(updateFile);
				//安装程序
				Intent installIntent = new Intent(Intent.ACTION_VIEW);
				installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				installIntent.setDataAndType(uri,
						"application/vnd.android.package-archive");
				updatePendingIntent = PendingIntent.getActivity(
						UpdateService.this, 0, installIntent, 0);
				updateNotification.contentIntent = updatePendingIntent;
				updateNotification.contentView.setTextViewText(
						R.id.download_notice_speed_tv,
						getString(R.string.update_notice_finish));
				updateNotification.tickerText = appName + "下载完成";
				updateNotification.when = System.currentTimeMillis();
				updateNotification.defaults = Notification.DEFAULT_SOUND;
				updateNotification.flags |= Notification.FLAG_AUTO_CANCEL;
				updateNotificationManager.notify(0, updateNotification);
				//启动安装程序
				UpdateService.this.startActivity(installIntent);
				stopSelf();
			} else if (result == DOWNLOAD_NOMEMORY) {
				//如果内存有问题
				updateNotification.tickerText = appName + "下载失败";
				updateNotification.when = System.currentTimeMillis();
				updateNotification.contentView.setTextViewText(
						R.id.download_notice_speed_tv,
						getString(R.string.update_notice_nomemory));
				updateNotification.flags |= Notification.FLAG_AUTO_CANCEL;
				updateNotification.defaults = Notification.DEFAULT_SOUND;
				updateNotificationManager.notify(0, updateNotification);
				stopSelf();
			} else if (result == DOWNLOAD_FAIL) {
				//下载失败
				updateNotification.tickerText = appName + "下载失败";
				updateNotification.when = System.currentTimeMillis();
				updateNotification.contentView.setTextViewText(
						R.id.download_notice_speed_tv,
						getString(R.string.update_notice_error));
				updateNotification.flags |= Notification.FLAG_AUTO_CANCEL;
				updateNotification.defaults = Notification.DEFAULT_SOUND;
				updateNotificationManager.notify(0, updateNotification);
				stopSelf();
			}
		}

	}

	/**
	 * 下载更新程序文件
	 * @param downloadUrl   下载地址
	 * @return
     */
	private int downloadUpdateFile(String downloadUrl) {
		int count = 0;
		long totalSize = 0;   //总大小
		long downloadSize = 0;   //下载的大小
		URI uri = null;

		//这个已经舍弃了,要用的话,就要加上org.apache.http.legacy.jar这个jar包
		HttpGet httpGet = null;
		try {
			uri = new URI(downloadUrl);
			httpGet = new HttpGet(uri);
		} catch (URISyntaxException e) {
			String encodedUrl = downloadUrl.replace(' ', '+');
			httpGet = new HttpGet(encodedUrl);
			e.printStackTrace();
		}
		HttpClient httpClient = new DefaultHttpClient();
		HttpResponse httpResponse = null;
		FileOutputStream fos = null;
		InputStream is = null;
		try {
			httpResponse = httpClient.execute(httpGet);
			if (httpResponse != null) {
				int stateCode = httpResponse.getStatusLine().getStatusCode();
				if (stateCode == HttpStatus.SC_OK) {
					HttpEntity entity = httpResponse.getEntity();
					if (entity != null) {
						totalSize = entity.getContentLength();
						//如果内存可用
						if (MemoryAvailable(totalSize)) {
							is = entity.getContent();
							if (is != null) {
								fos = new FileOutputStream(updateFile, false);
								byte buffer[] = new byte[4096];
								int readsize = 0;
								while ((readsize = is.read(buffer)) > 0) {
									fos.write(buffer, 0, readsize);
									downloadSize += readsize;
									if ((count == 0)
											|| (int) (downloadSize * 100 / totalSize) >= count) {
										count += 5;
										updateNotification.contentView
												.setTextViewText(
														R.id.download_notice_speed_tv,
														getMsgSpeed(downloadSize,totalSize));
										updateNotificationManager.notify(0,
												updateNotification);
									}
								}
								fos.flush();
								if (totalSize >= downloadSize) {
									return DOWNLOAD_COMPLETE;
								} else {
									return DOWNLOAD_FAIL;
								}
							}
						} else {
							if (httpGet != null) {
								httpGet.abort();
							}
							return DOWNLOAD_NOMEMORY;
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (fos != null) {
					fos.close();
				}
				if (is != null) {
					is.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			if (httpClient != null) {
				httpClient.getConnectionManager().shutdown();
			}
		}
		return DOWNLOAD_FAIL;
	}

	/**
	 * 可用内存大小
	 * @param fileSize
	 * @return
     */
	private boolean MemoryAvailable(long fileSize) {
		fileSize += (1024 << 10);
		if (MemoryStatus.externalMemoryAvailable()) {
			if ((MemoryStatus.getAvailableExternalMemorySize() <= fileSize)) {
				if ((MemoryStatus.getAvailableInternalMemorySize() > fileSize)) {
					createFile(false);
					return true;
				} else {
					return false;
				}
			} else {
				createFile(true);
				return true;
			}
		} else {
			if (MemoryStatus.getAvailableInternalMemorySize() <= fileSize) {
				return false;
			} else {
				createFile(false);
				return true;
			}
		}
	}

	/**
	 * 获取下载进度
	 * @param downSize
	 * @param allSize
     * @return
     */
	public static String getMsgSpeed(long downSize, long allSize) {
		StringBuffer sBuf = new StringBuffer();
		sBuf.append(getSize(downSize));
		sBuf.append("/");
		sBuf.append(getSize(allSize));
		sBuf.append(" ");
		sBuf.append(getPercentSize(downSize, allSize));
		return sBuf.toString();
	}

	/**
	 * 获取大小
	 * @param size
	 * @return
     */
	public static String getSize(long size) {
		if (size >= 0 && size < SIZE_BT) {
			return (double) (Math.round(size * 10) / 10.0) + "B";
		} else if (size >= SIZE_BT && size < SIZE_KB) {
			return (double) (Math.round((size / SIZE_BT) * 10) / 10.0) + "KB";
		} else if (size >= SIZE_KB && size < SIZE_MB) {
			return (double) (Math.round((size / SIZE_KB) * 10) / 10.0) + "MB";
		}
		return "";
	}

	/**
	 * 获取到当前的下载百分比
	 * @param downSize   下载大小
	 * @param allSize    总共大小
     * @return
     */
	public static String getPercentSize(long downSize, long allSize) {
		String percent = (allSize == 0 ? "0.0" : new DecimalFormat("0.0")
				.format((double) downSize / (double) allSize * 100));
		return "(" + percent + "%)";
	}


	/**
	 * 创建file文件
	 * @param sd_available    sdcard是否可用
     */
	private void createFile(boolean sd_available) {
		if (sd_available) {
			updateDir = new File(Environment.getExternalStorageDirectory(),
					UpdateInformation.downloadDir);
		} else {
			updateDir = getFilesDir();
		}
		updateFile = new File(updateDir.getPath(), appName + ".apk");
		if (!updateDir.exists()) {
			updateDir.mkdirs();
		}
		if (!updateFile.exists()) {
			try {
				updateFile.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			updateFile.delete();
			try {
				updateFile.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

这个时候,可能看到服务怎么这么多代码啊,我头都大了,不要着急,我们一步一步说明一下,这里逻辑很简单,就是在通知栏中,用到了通知,这个时候我们有三种情况,造成了我们好多代码的重复,(你也可以不必考虑那么多情况),还有,里面有了几个工具类,没有提取出来,分别是获取sdcard大小是否可用(创建文件夹),获取当前下载进度,获取应用大小,下载文件,这里也可以使用第三方框架来下载。 里面的重要的地方都有注释,如果有疑问,可用互相讨论一下。 这里我们就简单的上几张图看看吧: 提示更新图:

这里写图片描述

更新下载通知:

这里写图片描述
下载完成后安装图:
这里写图片描述
最新版应用主界面图(这里我下载的是36kr官方的app,我在应用中心下载好的,嘿嘿):
这里写图片描述
当然了哈,这里我写的还是有点问题的,每次进入都会提示,如果有必要,也可以实现是否要自动更新,用服务,也就是点击是否自动更新,如果不是自动更新,就不会去触发服务端接口信息,如果是自动更新,就去触发,来获取最新的app版本。