編輯:關於Android編程
一般的安卓app都有自動更新功能,實現app的更新,以讓用戶體驗新版本的功能,這裡也是項目中用到的,今天就來總結一下,代碼應該有點多,還請耐心點哈。
安卓應用實現自動更新比較簡單,這裡跟大家介紹下:
//也就是一個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();
}
}
/**
* 版本更新升級 廣播接受者
*
*/
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 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", "升級包不存在,不用刪除升級包");
}
}
}
/**
* 不要忘記注冊,在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 {
@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版本。
進程狀態轉換,同樣可用於線程的狀態轉移一、進程狀態進程的生命周期內,有5種狀態,分別為new, runnable, running, blocked, de
Android N 除了提供諸多新特性和功能外,還對系統和 API 行為做出了各種變更。 本文重點介紹您應該了解並在開發應用時加以考慮的一些重要變更。如果您之前發布過 A
在Android上查看圖片或者浏覽網頁時,我們往往有把圖片或者網頁放大或者縮小的的需求,這樣就能夠獲得更多的細節信息 或者獲得更多的全貌信息,多點觸摸與綻放功能正是滿足這
打開/dev/graphics/fb0節點的過程:打開/dev/graphics/fb0這個設備的調用過程如下:1.在HWComposer中,加載moduleHWComp