App在開發過程中,隨著業務場景的不斷增多,功能的不斷完善,早期下載App的用戶便無法體驗最新的功能,為了能讓用戶更及時的體驗App最新版本,在App開發過程加入App自動更新功能便顯得尤為重要。更新apk主要分為二類: (1)用戶點擊更新後,前臺進行下載,下載過程中用戶無法操作 (2)後臺進行下載,下載完成後,service回調進行apk的安裝,下載過程中用戶可操作,本來講解第一種
1. 運行前準備
本文附帶實例: 實例中會下載一個應用程序(本公司的另一個產品:優鮮派 無病毒,請安心測試), 如果用戶需要體驗自動下載其它的產品,請自行配置apk下載路徑: 將下圖中的url路徑換成自己的apk路徑即可, apk的路徑可以到360或者應用寶的官方網站上獲取
2. 本實例運行效果圖:
2.1 啟動應用程序
2.2 點擊 更新 操作, 開始下載最新版本apk,進度條顯示下載進度
2.3 apk下載完成之後,自動安裝下載的apk包, 注意下圖的箭頭處的意思
3.代碼講解
3.1 程序啟動後, 調用後臺接口獲取最新apk版本信息(包括版本號與版本需要升級功能信息), 將最新版本號與當前apk版本號進行比較,如果版本號不一致,則彈出提示框,提示用戶新版本中包含哪些最新信息, 用戶根據自動需要選擇是否更新
獲得當前apk版本號:
public
static
int
getVersion(Context context) {
try
{
PackageManager manager = context.getPackageManager();
PackageInfo info = manager.getPackageInfo(context.getPackageName(),
0
);
String version = info.versionName;
int
versioncode = info.versionCode;
return
versioncode;
}
catch
(Exception e) {
e.printStackTrace();
}
return
0
;
}
由於無法模擬與後臺的交互,這裡將網絡版本號寫死,比當前apk版本號要大,模擬更新的場景
彈出對話框,提示新版本新功能
private
void
getVersion(
final
int
vision) {
// {"data":{"content":"其他bug修復。","id":"2","api_key":"android",
// // "version":"2.1"},"msg":"獲取成功","status":1}
String data =
""
;
//網絡請求獲取當前版本號和下載鏈接
//實際操作是從服務器獲取
//demo寫死了
String newversion =
"2.1"
;
//網絡版本號
String content =
"\n"
+
"就不告訴你我們更新了什麼-。-\n"
+
"\n"
+
"----------萬能的分割線-----------\n"
+
"\n"
+
"(ㄒoㄒ) 被老闆打了一頓,還是來告訴你吧:\n"
+
"1.下架商品誤買了?恩。。。我搞了點小動作就不會出現了\n"
+
"2.側邊欄、彈框優化 —— 這個你自己去探索吧,總得留點懸念嘛-。-\n"
;
//更新內容
double
newversioncode = Double
.parseDouble(newversion);
int
cc = (
int
) (newversioncode);
System.out.println(newversion +
"v"
+ vision +
",,"
+ cc);
if
(cc != vision) {
if
(vision < cc) {
System.out.println(newversion +
"v"
+ vision);
// 版本號不同
ShowDialog(vision, newversion, content, url);
}
}
}
3.2 在彈出的對話框,用戶點擊"更新"與"取消"操作
更新時構建進度條頁面
private
void
ShowDialog(
int
vision, String newversion, String content,
final
String url) {
new
android.app.AlertDialog.Builder(
this
)
.setTitle(
"版本更新"
)
.setMessage(content)
.setPositiveButton(
"更新"
,
new
DialogInterface.OnClickListener() {
@Override
public
void
onClick(DialogInterface dialog,
int
which) {
dialog.dismiss();
pBar =
new
CommonProgressDialog(MainActivity.
this
);
pBar.setCanceledOnTouchOutside(
false
);
pBar.setTitle(
"正在下載"
);
pBar.setCustomTitle(LayoutInflater.from(
MainActivity.
this
).inflate(
R.layout.title_dialog,
null
));
pBar.setMessage(
"正在下載"
);
pBar.setIndeterminate(
true
);
pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pBar.setCancelable(
true
);
// downFile(URLData.DOWNLOAD_URL);
final
DownloadTask downloadTask =
new
DownloadTask(
MainActivity.
this
);
downloadTask.execute(url);
pBar.setOnCancelListener(
new
DialogInterface.OnCancelListener() {
@Override
public
void
onCancel(DialogInterface dialog) {
downloadTask.cancel(
true
);
}
});
}
})
.setNegativeButton(
"取消"
,
new
DialogInterface.OnClickListener() {
@Override
public
void
onClick(DialogInterface dialog,
int
which) {
dialog.dismiss();
}
})
.show();
}
3.3 開啟線程類下載應用程序,將下載進度顯示到進度條上
線程啟動前調用: onPreExecute
線程下載apk過程: onPostExecute
進度條更新: onProgressUpdate
下載apk完成回調: onPostExecute
class
DownloadTask
extends
AsyncTask {
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();
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 = Environment.getExternalStorageDirectory();
if
(!file.exists()) {
// 判斷父文件夾是否存在
if
(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
}
file =
new
File(file, DOWNLOAD_NAME);
}
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
) {
// allow canceling with back button
if
(isCancelled()) {
input.close();
return
null
;
}
total += count;
// publishing the progress....
if
(fileLength >
0
)
// only if total length is known
publishProgress((
int
) (total *
100
/ fileLength));
output.write(data,
0
, count);
}
}
catch
(Exception e) {
System.out.println(e.toString());
return
e.toString();
}
finally
{
try
{
if
(output !=
null
)
output.close();
if
(input !=
null
)
input.close();
}
catch
(IOException ignored) {
ignored.toString();
}
if
(connection !=
null
)
connection.disconnect();
}
return
null
;
}
@Override
protected
void
onPreExecute() {
super
.onPreExecute();
// take CPU lock to prevent CPU from going off if the user
// presses the power button during download
PowerManager pm = (PowerManager) context
.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
mWakeLock.acquire();
pBar.show();
}
@Override
protected
void
onProgressUpdate(Integer... progress) {
super
.onProgressUpdate(progress);
// if we get here, length is known, now set indeterminate to false
pBar.setIndeterminate(
false
);
pBar.setMax(
100
);
pBar.setProgress(progress[
0
]);
}
@Override
protected
void
onPostExecute(String result) {
mWakeLock.release();
pBar.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(context);
}
}
}
4. 安裝下載完的apk包
private
void
update(Context context) {
//安裝應用
Log.i(
"lxl update->"
,
"update"
);
File apkFile =
new
File(Environment.getExternalStorageDirectory(), DOWNLOAD_NAME);
Intent intent =
new
Intent(Intent.ACTION_VIEW);
if
(apkFile ==
null
|| context ==
null
) {
Log.i(
"lxl update->"
,
"null."
);
throw
new
NullPointerException();
}
if
(Build.VERSION.SDK_INT>= Build.VERSION_CODES.N) {
Uri contentUri = FileProvider.getUriForFile(context.getApplicationContext(),
BuildConfig.APPLICATION_ID +
".fileProvider"
,apkFile);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(contentUri,
"application/vnd.android.package-archive"
);
Log.i(
"lxl update->"
,
">>>==Build.VERSION_CODES.N"
);
}
else
{
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(apkFile),
"application/vnd.android.package-archive"
);
Log.i(
"lxl update->"
,
"<<<<< Build.VERSION_CODES.N"
);
}
startActivity(intent);
}
閱讀更多 代碼專家 的文章