1. 概述
在我们的Android开发过程中,版本的迭代更新是很重要的一步。产品需求提出和后续迭代的开发都需要版本的迭代更新。一般的App上传到应用商店以后,开发人员只要将新的App上传到应用市场,然后应用市场会提供App升级的操作,但是很多的App除了第三方的应用商店升级之后。我们都会有自己的版本升级操作。
2. 版本更新的思维导图
版本更新的思维导图3.版本更新的思路
抛开安卓这个概念,普通的更新,我们要做的就是先拿到新事物,然后将新事物替换掉旧事物,这样我们就能完成一次简单的更新了。安卓的更新也很简单。
- 首先我们需要得到当前的版本号或者版本名 (versionCode或者versionName)
- 请求服务器的更新接口,一般服务器的更新接口里面的数据,必定包含版本号的值。
- 将请求更新的接口最好放在启动的地方,或者主页,看自己的产品设计。
- 通过对比产品的版本号的比较,确定是否弹窗更新。
- 有时候我们的产品会要求强制更新,这是不值得推荐的,不更新就不能用,用户的体验性太差,一般后台接口中就会有是否强制更新的字符。
- 比较完了之后,如果服务器中的接口版本号比本地版本号大,那么我们就需要进行版本更新。
- 然后是对弹窗的更新数据进行优化,因为我们下载的是字节数,给用户展示的最好还是百分比这样比较直观一点。
4. 实现版本更新
因为没有对外的服务器接口,而且不同后台的服务器返回接口不一样。我只是举个例子。
- 版本接口 json数据
{
versionId : 2
versionNum : v10.0.2 版本编号
status : 0 //0是不强制 ,1 是强制
delFlag : 0
created : 1491790427
updated : 1491790427
downloadUrl : "http://27.221.81.15/dd.myapp.com/16891/63C4DA61823B87026BBC8C22BBBE212F.apk?mkey=575e443c53406290&f=8b5d&c=0&fsname=com.daimajia.gold_3.2.0_80.apk&p=.apk"
content : "来测测看版本更新的内容"
}
创建一个关于版本接口的bean类
public class UpdateAppBean implements Serializable{
/**
* versionId : 2
* versionNum : v10.0.2
* status : 0
* delFlag : 0
* created : 1491790427
* updated : 1491790427
* downloadUrl : www.baidu.com
* content : djfkdkkkk
*/
private int versionId;
private String versionNum;
private int status;
private int delFlag;
private int created;
private int updated;
private String downloadUrl;
private String content;
public int getVersionId() {
return versionId;
}
public void setVersionId(int versionId) {
this.versionId = versionId;
}
public String getVersionNum() {
return versionNum;
}
public void setVersionNum(String versionNum) {
this.versionNum = versionNum;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public int getDelFlag() {
return delFlag;
}
public void setDelFlag(int delFlag) {
this.delFlag = delFlag;
}
public int getCreated() {
return created;
}
public void setCreated(int created) {
this.created = created;
}
public int getUpdated() {
return updated;
}
public void setUpdated(int updated) {
this.updated = updated;
}
public String getDownloadUrl() {
return downloadUrl;
}
public void setDownloadUrl(String downloadUrl) {
this.downloadUrl = downloadUrl;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
- 创建一个工具类去获取版本号或版本名,也可以写在自定义的Application中。
public class AppUtils {
private AppUtils() {
throw new UnsupportedOperationException("你不能对我进行实例化操作");
}
//获取VersionCode值
public static int getVersionCode(Context mContext){
if(mContext!=null){
try {
return mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
return 0;
}
//获取Version Name
public static String getVersionName(Context mContext){
if(mContext!=null){
try {
return mContext.getPackageManager().getPackageInfo(mContext.getPackageName(),0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
return "";
}
}
- 接下来是创建一个自定义的弹窗CommonProgressDialog
public class CommonProgressDialog extends AlertDialog {
private static final String TAG = "CommonProgressDialog";
private ProgressBar mProgress;
private TextView mProgressNumber;
private TextView mProgressPercent;
private TextView mProgressMessage;
private Handler mViewUpdateHandler;
private int mMax;
private CharSequence mMessage;
private boolean mHasStarted;
private int mProgressVal;
private String mProgressNumberFormat;
private NumberFormat mProgressPercentFormat;
protected CommonProgressDialog(Context context) {
super(context);
initFormats();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.common_progress_dialog);
mProgress = (ProgressBar) findViewById(R.id.progress);
mProgressNumber = (TextView) findViewById(R.id.progress_number);
mProgressPercent = (TextView) findViewById(R.id.progress_percent);
mProgressMessage = (TextView) findViewById(R.id.progress_message);
mViewUpdateHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int progress = mProgress.getProgress();
int max = mProgress.getMax();
double dProgress = (double)progress/(double)(1024*1024);
double dMax = (double)max/(double)(1024 * 1024);
if(mProgressNumberFormat !=null){
String format = mProgressNumberFormat;
mProgressNumber.setText(String.format(format,dProgress,dMax));
} else {
mProgressNumber.setText("");
}
if(mProgressNumberFormat !=null){
double percent = (double) progress/(double) max;
SpannableString tmp = new SpannableString(
mProgressPercentFormat.format(percent));
tmp.setSpan(new StyleSpan(Typeface.BOLD),0,tmp.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mProgressPercent.setText(tmp);
} else {
mProgressPercent.setText("");
}
}
};
onProgressChanged();
if(mMessage != null){
setMessage(mMessage);
}
if(mMax>0){
setMax(mMax);
}
if(mProgressVal>0){
setProgress(mProgressVal);
}
}
private void initFormats(){
mProgressNumberFormat = "%1.2fM/%2.2fM";
mProgressPercentFormat = NumberFormat.getPercentInstance();
mProgressPercentFormat.setMaximumFractionDigits(0);
}
private void onProgressChanged() {
mViewUpdateHandler.sendEmptyMessage(0);
}
public void setMax(int max){
if(mProgress !=null){
mProgress.setMax(max);
onProgressChanged();
}else{
mMax = max;
}
}
public void setProgress(int value){
if(mHasStarted){
mProgress.setProgress(value);
onProgressChanged();
} else{
mProgressVal = value;
}
}
public void setProgressStyle(int style){
// mProgressStyle = style;
}
public void setIndeterminate(boolean indeterminate){
if(mProgress !=null){
mProgress.setIndeterminate(indeterminate);
}
}
@Override
public void setMessage(CharSequence message) {
if(mProgressMessage !=null){
mProgressMessage.setText(message);
} else {
mMessage = message ;
}
}
@Override
protected void onStart() {
super.onStart();
mHasStarted = true;
}
@Override
protected void onStop() {
super.onStop();
mHasStarted =false ;
}
}
- 下面是对代码的处理,因为没有服务器,接口,所以这里自己模拟下载的环境。考虑到Android 6.0的权限处理问题,所以我们对权限做了请求。Android 7.0文件下载的问题。加了权限判断。
public class MainActivity extends AppCompatActivity {
private CommonProgressDialog commonProgressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//进入这里之后,获取版本号是否更新
int versionCode = AppUtils.getVersionCode(this);
getUpdate(versionCode);
}
//版本更新
private void getUpdate(int versionCode) {
//一般情况下是在这里拿本地的版本号和服务器的版本号进行比较的,我没有服务器就模拟拿数据了
String newVersion="2.1";
String content ="\n"+
"就不告诉你我们更新了什么-。-\n" +
"\n" +
"----------万能的分割线-----------\n" +
"\n" +
"1.新产品上线了,界面全新改版\n" +
"2.修复了若干bug,还杀了一个程序员祭天 \n";//更新内容
String url = "http://openbox.mobilem.360.cn/index/d/sid/3429345"; //安装包下载地址
double newVersionCode = Double.parseDouble(newVersion);
int cc= (int)(newVersionCode);
if(cc!=versionCode){
if(cc>versionCode){
//版本号不同,这时候我们需要开始弹窗了
//需要强制更新的在这里选择不同的弹窗方式
ShowDialog(url,content);
}
}
}
/**
* 升级版本
* @param url
* @param content
*/
private void ShowDialog(final String url, String content) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("版本更新");
builder.setMessage(content);
builder.setPositiveButton("更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//这里进行更新的操作
dialogInterface.dismiss();
//提示版本更新的信息消失之后,开始在弹出本地下载的进度条。这里自定义一个dialog
commonProgressDialog = new CommonProgressDialog(MainActivity.this);
commonProgressDialog.setCanceledOnTouchOutside(false);
commonProgressDialog.setTitle("正在下载");
commonProgressDialog.setCustomTitle(
LayoutInflater.from(MainActivity.this).inflate(R.layout.title_dialog,null)
);
commonProgressDialog.setIndeterminate(true);
commonProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
commonProgressDialog.setCancelable(true);
//downFile(URLData.DOWNLOAD_URL);
final DownloadTask downloadTask = new DownloadTask(MainActivity.this);
downloadTask.execute(url);
commonProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
downloadTask.cancel(true);
}
});
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
builder.create().show();
}
// 下载存储的文件名
private static final String DOWNLOAD_NAME = "channelWe";
/**
* 下载应用
*
* @author Administrator
*/
class DownloadTask extends AsyncTask<String, Integer, String> {
private Context context;
private PowerManager.WakeLock mWakeLock;
public DownloadTask(Context context) {
this.context = context;
}
@Override
protected String doInBackground(String... sUrl) {
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
File file = null;
try {
URL url = new URL(sUrl[0]);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
// expect HTTP 200 OK, so we don't mistakenly save error
// report
// instead of the file
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return "Server returned HTTP "
+ connection.getResponseCode() + " "
+ connection.getResponseMessage();
}
int fileLength = connection.getContentLength();
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
file = new File(Environment.getExternalStorageDirectory(),
DOWNLOAD_NAME);
if (!file.exists()) {
// 判断父文件夹是否存在
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
}
} else {
Toast.makeText(MainActivity.this, "sd卡未挂载",
Toast.LENGTH_LONG).show();
}
input = connection.getInputStream();
output = new FileOutputStream(file);
byte data[] = new byte[4096];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
if (isCancelled()) {
input.close();
return null;
}
total += count;
if (fileLength > 0) // only if total length is known
publishProgress((int) (total * 100 / fileLength));
output.write(data, 0, count);
}
} catch (Exception e) {
return e.toString();
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
} catch (IOException ignored) {
}
if (connection != null)
connection.disconnect();
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
PowerManager pm = (PowerManager) context
.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
mWakeLock.acquire();
commonProgressDialog.show();
}
@Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
commonProgressDialog.setIndeterminate(false);
commonProgressDialog.setMax(100);
commonProgressDialog.setProgress(progress[0]);
}
@Override
protected void onPostExecute(String result) {
mWakeLock.release();
commonProgressDialog.dismiss();
if (result != null) {
// 申请多个权限。
AndPermission.with(MainActivity.this)
.requestCode(REQUEST_CODE_PERMISSION_SD)
.permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
// rationale作用是:用户拒绝一次权限,再次申请时先征求用户同意,再打开授权对话框,避免用户勾选不再提示。
.rationale(rationaleListener
)
.send();
Toast.makeText(context, "您未打开SD卡权限" + result, Toast.LENGTH_LONG).show();
} else {
update();
}
}
}
private static final int REQUEST_CODE_PERMISSION_SD = 101;
private static final int REQUEST_CODE_SETTING = 300;
private RationaleListener rationaleListener = new RationaleListener() {
@Override
public void showRequestPermissionRationale(int requestCode, final Rationale rationale) {
// 这里使用自定义对话框,如果不想自定义,用AndPermission默认对话框:
// AndPermission.rationaleDialog(Context, Rationale).show();
// 自定义对话框。
com.yanzhenjie.alertdialog.AlertDialog.build(MainActivity.this)
.setTitle(R.string.title_dialog)
.setMessage(R.string.message_permission_rationale)
.setPositiveButton(R.string.btn_dialog_yes_permission, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
rationale.resume();
}
})
.setNegativeButton(R.string.btn_dialog_no_permission, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
rationale.cancel();
}
})
.show();
}
};
//----------------------------------SD权限----------------------------------//
@PermissionYes(REQUEST_CODE_PERMISSION_SD)
private void getMultiYes(List<String> grantedPermissions) {
Toast.makeText(this, R.string.message_post_succeed, Toast.LENGTH_SHORT).show();
}
@PermissionNo(REQUEST_CODE_PERMISSION_SD)
private void getMultiNo(List<String> deniedPermissions) {
Toast.makeText(this, R.string.message_post_failed, Toast.LENGTH_SHORT).show();
// 用户否勾选了不再提示并且拒绝了权限,那么提示用户到设置中授权。
if (AndPermission.hasAlwaysDeniedPermission(this, deniedPermissions)) {
AndPermission.defaultSettingDialog(this, REQUEST_CODE_SETTING)
.setTitle(R.string.title_dialog)
.setMessage(R.string.message_permission_failed)
.setPositiveButton(R.string.btn_dialog_yes_permission)
.setNegativeButton(R.string.btn_dialog_no_permission, null)
.show();
// 更多自定dialog,请看上面。
}
}
//----------------------------------权限回调处理----------------------------------//
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
/**
* 转给AndPermission分析结果。
*
* @param object 要接受结果的Activity、Fragment。
* @param requestCode 请求码。
* @param permissions 权限数组,一个或者多个。
* @param grantResults 请求结果。
*/
AndPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_SETTING: {
Toast.makeText(this, R.string.message_setting_back, Toast.LENGTH_LONG).show();
//设置成功,再次请求更新
getUpdate(AppUtils.getVersionCode(MainActivity.this));
break;
}
}
}
private void update() {
//安装应用
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(MainActivity.this, "com.demo.mymobilephonedemo.fileprovider",new File(Environment
.getExternalStorageDirectory(), DOWNLOAD_NAME));
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(new File(Environment
.getExternalStorageDirectory(), DOWNLOAD_NAME)), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
MainActivity.this.startActivity(intent);
}
}
显示效果图:
下载安装github地址:https://github.com/wangxin3119/UpdateDemo
5. 通过第三方的library来更新数据
现在有很多开源的包,站在巨人的肩膀上可能做不到,但是借把力还是可以的。我在自己的项目中用了这个包,来进行版本更新,有兴趣的朋友可以自己去看看。使用起来也很简单。
- 首先在Android Studio 项目中的build.gradle文件中配置
allprojects {
repositories {
maven { url "https://www.jitpack.io" }
}
}
...
dependencies {
compile 'com.github.yaming116:UpdateApp:1.0.2'
...
}
- 然后在代码中使用
UpdateService.Builder.create(URL)
.setStoreDir("update")
.setIcoResId(R.mipmap.ic_launcher)
.setIsSendBroadcast(true)
.setDownloadSuccessNotificationFlag(Notification.DEFAULT_SOUND)
.setDownloadErrorNotificationFlag(Notification.DEFAULT_SOUND)
.setUpdateProgress(1)
.build(this);
参数 | 描述 |
---|---|
downloadUrl | 下载地址 |
icoResId | Notification 的icon,默认应用的icon |
icoSmallResId | Notification 右下角的icon,默认应用的icon |
storeDir | 保存在sdcard路径,默认在sdcard/Android/package/update |
updateProgress | 刷新notification 进度条,默认每次下载1%更新一次 |
downloadNotificationFlag | 下载进行中的Notification Flag |
downloadErrorNotificationFlag | 下载失败的Notification Flag |
downloadSuccessNotificationFlag | 下载成功的Notification Flag |
isSendBroadcast | 是否会发送下载状态广播 |
网友评论