![\color{#008484}{ 下載安裝需要配置的權(quán)限}](https://math.jianshu.com/math?formula=%5Ccolor%7B%23008484%7D%7B%20%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E9%9C%80%E8%A6%81%E9%85%8D%E7%BD%AE%E7%9A%84%E6%9D%83%E9%99%90%7D)
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--未知來(lái)源安裝權(quán)限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
![\color{#008484}{ 一普办、請(qǐng)求存儲(chǔ)權(quán)限}](https://math.jianshu.com/math?formula=%5Ccolor%7B%23008484%7D%7B%20%E4%B8%80%E3%80%81%E8%AF%B7%E6%B1%82%E5%AD%98%E5%82%A8%E6%9D%83%E9%99%90%7D)
/**
* 我這里用了別人的請(qǐng)求權(quán)限框架 AndPemission
* Github地址:https://github.com/yanzhenjie/AndPermission
*/
if (AndPermission.hasPermissions(this, Permission.Group.STORAGE)) {
// 有存儲(chǔ)權(quán)限, 開(kāi)啟服務(wù)下載
startService();
} else {
// 無(wú)存儲(chǔ)權(quán)限, 則請(qǐng)求權(quán)限
AndPermission.with(this).runtime().permission(Permission.Group.STORAGE).onGranted(data -> {
if (CollectionUtils.isEmpty(data)) {
return;
}
// 用戶(hù)允許了權(quán)限, 開(kāi)啟服務(wù)下載
startService();
}).start();
}
![\color{#008484}{二工扎、啟動(dòng)服務(wù)開(kāi)始下載Apk,注意適配安卓8.0}](https://math.jianshu.com/math?formula=%5Ccolor%7B%23008484%7D%7B%E4%BA%8C%E3%80%81%E5%90%AF%E5%8A%A8%E6%9C%8D%E5%8A%A1%E5%BC%80%E5%A7%8B%E4%B8%8B%E8%BD%BDApk%EF%BC%8C%E6%B3%A8%E6%84%8F%E9%80%82%E9%85%8D%E5%AE%89%E5%8D%938.0%7D)
Intent intent = new Intent(this, ApkDownloadService.class);
// 給Service傳值, 我這里直接把整個(gè)Bean傳過(guò)去了, 其實(shí)只需要個(gè)下載地址和當(dāng)前下載的版本號(hào)
intent.putExtra(IntentKey.BEAN, newVersionBean);
if (Build.VERSION.SDK_INT >= 26) {
// Android8.0適配
startForegroundService(intent);
} else {
startService(intent);
}
![\color{#008484}{在Service的onStartCommand方法中處理下載}](https://math.jianshu.com/math?formula=%5Ccolor%7B%23008484%7D%7B%E5%9C%A8Service%E7%9A%84onStartCommand%E6%96%B9%E6%B3%95%E4%B8%AD%E5%A4%84%E7%90%86%E4%B8%8B%E8%BD%BD%7D)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// android8.0適配: 被啟動(dòng)的Service創(chuàng)建服務(wù)后的五秒內(nèi)要調(diào)用startForground(0, new Notification())
// 如果不調(diào)用或調(diào)用時(shí)間超過(guò)5秒會(huì)拋出一個(gè)ANR
// 調(diào)用startForground用到了通知泌豆,android8.0通知又必須要設(shè)置通知渠道
// 創(chuàng)建通知渠道并運(yùn)行服務(wù)到前臺(tái)
createNotificationChannel();
// 獲取Intent傳值信息
mVersionInfo = (QueryVersionsVo) intent.getSerializableExtra(IntentKey.BEAN);
// 開(kāi)始異步任務(wù)下載Apk
downloadApk();
return super.onStartCommand(intent, flags, START_STICKY);
}
![\color{#008484}{Service的幾個(gè)方法}](https://math.jianshu.com/math?formula=%5Ccolor%7B%23008484%7D%7BService%E7%9A%84%E5%87%A0%E4%B8%AA%E6%96%B9%E6%B3%95%7D)
private void createNotificationChannel() {
// 這里的id輸入自己的項(xiàng)目的包名
String ID = "com.***.***";
String NAME = "Channel One";
Intent intent = new Intent(this, HomeActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
// 創(chuàng)建服務(wù)對(duì)象
NotificationCompat.Builder notification;
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// Android8.0要求必須創(chuàng)建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(ID, NAME, NotificationManager.IMPORTANCE_HIGH);
channel.enableLights(false);
channel.setShowBadge(false);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
manager.createNotificationChannel(channel);
}
notification = new NotificationCompat.Builder(this, ID);
notification.setContentTitle("更新提示")
.setContentText("正在下載最新版本..")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher_esp)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher_esp))
.setContentIntent(pendingIntent)
.build();
Notification notification1 = notification.build();
startForeground(1, notification1);
}
public void downloadApk() {
// 設(shè)置最新安裝包名稱(chēng)
StringBuilder builder = new StringBuilder("your_app_name-");
if (!TextUtils.isEmpty(mVersionInfo.getPushVersions())) {
builder.append(mVersionInfo.getPushVersions());
} else {
builder.append(System.currentTimeMillis());
}
builder.append(".apk");
// 設(shè)置apk所在目錄
File baseFile = ContextHolder.getContext().getExternalFilesDir("yc_team");
if (!baseFile.exists()) {
baseFile.mkdirs();
}
mApkFile = new File(baseFile.getPath(), builder.toString());
// 最終apk目錄 文件管理-手機(jī)存儲(chǔ)-Android-data-應(yīng)用包名-yc_team-***.apk
if (mApkFile.exists()) {
mApkFile.delete();
}
// 開(kāi)始異步下載
mAsyncTask = new DownApkAsyncTask();
mAsyncTask.execute();
}
@SuppressLint("StaticFieldLeak")
private class DownApkAsyncTask extends AsyncTask<Void, Long, Void> {
@Override
protected Void doInBackground(Void... voids) {
HttpURLConnection httpConnection = null;
InputStream is = null;
FileOutputStream fos = null;
int updateTotalSize;
URL url;
try {
url = new URL(mVersionInfo.getUrl());
httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setConnectTimeout(60000);
httpConnection.setReadTimeout(60000);
if (httpConnection.getResponseCode() != 200) {
return null;
}
updateTotalSize = httpConnection.getContentLength();
// mApkFile.createNewFile();
is = httpConnection.getInputStream();
fos = new FileOutputStream(mApkFile, false);
byte[] buffer = new byte[4096];
int readSize;
int currentSize = 0;
while ((readSize = is.read(buffer)) > 0) {
fos.write(buffer, 0, readSize);
currentSize += readSize;
int finalCurrentSize = currentSize;
int finalUpdateTotalSize = updateTotalSize;
// 這里可以發(fā)消息實(shí)時(shí)通知進(jìn)度
handler.post(() -> {
// 用finalCurrentSize和finalUpdateTotalSize計(jì)算出進(jìn)度
});
}
// 下載完成, 通知安裝
installApk();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (httpConnection != null) {
httpConnection.disconnect();
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
/**
* 我這里是發(fā)了個(gè)EventBus定庵,通知Activity可以安裝Apk了
*/
private void installApk() {
handler.post(() -> {
EventBusMode mode = new EventBusMode(EventBusType.ESP_DOWNLOAD_APK_SUCCESS);
mode.setTempStr(mApkFile.getAbsolutePath());
EventBus.getDefault().post(mode);
// 下載完成,關(guān)閉當(dāng)前服務(wù)
stopSelf();
});
}
/**
* 在Service結(jié)束時(shí), 停止異步下載
*/
@Override
public void onDestroy() {
if (mAsyncTask != null) {
mAsyncTask.cancel(true);
}
super.onDestroy();
}
![\color{#008484}{Activity收到通知踪危,開(kāi)始請(qǐng)求未知來(lái)源安裝權(quán)限}](https://math.jianshu.com/math?formula=%5Ccolor%7B%23008484%7D%7BActivity%E6%94%B6%E5%88%B0%E9%80%9A%E7%9F%A5%EF%BC%8C%E5%BC%80%E5%A7%8B%E8%AF%B7%E6%B1%82%E6%9C%AA%E7%9F%A5%E6%9D%A5%E6%BA%90%E5%AE%89%E8%A3%85%E6%9D%83%E9%99%90%7D)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// Android8.0之前蔬浙,直接安裝Apk
installApk();
return;
}
boolean haveInstallPermission = getPackageManager().canRequestPackageInstalls();
if (!haveInstallPermission) {
// 權(quán)限沒(méi)有打開(kāi)則提示用戶(hù)去手動(dòng)打開(kāi)
Uri packageURI = Uri.parse("package:" + getPackageName());
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
startActivityForResult(intent, 1001);
}
/**
* 未知來(lái)源安裝權(quán)限申請(qǐng)回調(diào)
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) {
return;
}
if (requestCode == 1001 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 未知來(lái)源安裝應(yīng)用權(quán)限開(kāi)啟
boolean haveInstallPermission = getPackageManager().canRequestPackageInstalls();
if (haveInstallPermission) {
installApk();
}
}
}
/**
* 安裝最新Apk
*/
private void installApk() {
// Service發(fā)的通知中的文件絕對(duì)路徑
File file = new File(mNewApkFilePath);
try {
// 這里有文件流的讀寫(xiě),需要處理一下異常
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//如果SDK版本>=24贞远,即:Build.VERSION.SDK_INT >= 24
String packageName = context.getApplicationContext().getPackageName();
String authority = new StringBuilder(packageName).append(".provider").toString();
Uri uri = FileProvider.getUriForFile(context, authority, file);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
} else {
Uri uri = Uri.fromFile(file);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
}
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
// 安裝的時(shí)候會(huì)進(jìn)行版本自動(dòng)檢測(cè)畴博,更新版本小于已有版本,是會(huì)走當(dāng)前異常的蓝仲,注意俱病!
}
}