APK Online Upgrade
APK online upgrade is an essential function of almost all programs.
Online upgrades can solve existing problems and provide richer new functions.
The basic process is:
- New version information detected
- Pop-up upgrade prompt window
- Click No to not upgrade, it's over!
- Click Yes to download the upgrade program in the background
- Download the program and go to the installation page
- Successful Installation, Enter New Program
Here's how to use Update AppUtil to implement online upgrades
0. Require permissions
You need to authorize android.permission.INTERNET, android.permission.WRITE_EXTERNAL_STORAGE. The specific application version is not expanded here.
Interested partners can read past articles.
1. Getting new version information
In general, access to the server to obtain new version information (for example, the following)
{
"url":"https://www.google.com/test/a670ef11/apk/test.apk",
"versionCode":1,
"versionName":"v2.1.0",
"create_time":"2019-12-14 03:44:34",
"description":"New satellite links to support global access."
}
APK must have a download link (url), version code, or version Name.
It's all needed next.
2. Setting Information
UpdateAppUtil.from(MainActivity.this)
.checkBy(UpdateAppUtil.CHECK_BY_VERSION_NAME) //Update detection mode, default VersionCode
.serverVersionCode(0)
.serverVersionName(version)
.updateInfo(description)
.apkPath(url)
.update();
field | Explain |
---|---|
checkBy | Whether you need to pop up the basis for the upgrade prompt. CHECK_BY_VERSION_NAME pops up the upgrade prompt according to the server Version Name. CHECK_BY_VERSION_CODE is a pop-up upgrade prompt based on server Version Code higher than the current software version. |
serverVersionCode | Setting up versionCode for new software (as in Example 1) |
serverVersionName | Set up version Name for the new software (for example, "v2.1.0" for the example) |
updateInfo | New Software Description for Upgrade Tip Window Display |
apkPath | New software download link (you need to download new software through this link) |
update | Inspect upgrade immediately (pop up upgrade tips if upgrade requirements are met) |
isForce | If you do not choose to upgrade, exit the program directly |
3. Download the upgrade program
Android has a variety of frameworks to download programs (okhttp, etc.) or to open a thread to download (IntentService).
Update AppUtil uses Download Manager, a download framework provided by Android SDK.
public static void downloadWithAutoInstall(Context context, String url, String fileName, String notificationTitle, String descriptInfo) {
if (TextUtils.isEmpty(url)) {
Log.e(TAG, "url Empty!!!!!");
return;
}
try {
Uri uri = Uri.parse(url);
Log.i(TAG, String.valueOf(uri));
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(uri);
// Display in the notification bar
request.setVisibleInDownloadsUi(true);
request.setTitle(notificationTitle);
request.setDescription(descriptInfo);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setMimeType("application/vnd.android.package-archive");
String filePath = null;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {//Is SD Card Normally Mounted
filePath = Environment.getExternalStorageDirectory().getAbsolutePath();
} else {
Log.i(TAG, "No, SD card" + "filePath:" + context.getFilesDir().getAbsolutePath());
return;
}
downloadUpdateApkFilePath = filePath + File.separator + fileName;
// If it exists, delete it
deleteFile(downloadUpdateApkFilePath);
Uri fileUri = Uri.parse("file://" + downloadUpdateApkFilePath);
request.setDestinationUri(fileUri);
downloadUpdateApkId = downloadManager.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
}
}
request.setVisibleInDownloadsUi Download UI is displayed on the notification bar
request.setTitle Sets the Title of the Notification Bar
request.setDescription Sets Messages for Notification Bar
Request. setNotification Visibility download process has been displayed download information, after download also exists (until the user eliminate)
The unfinished files will be cleared and re-downloaded.
Download Manager broadcasts during download, and listens for broadcasts if you want to process the download.
(Download Update ApkFilePath saves the path of the download file, which can be installed after download is complete)
4. Enter Installation
When Download Manager downloads, ACTION_DOWNLOAD_COMPLETE will be issued.
public class UpdateAppReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Cursor cursor = null;
try {
if (! intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
return;
}
if (DownloadAppUtil.downloadUpdateApkId <= 0) {
return;
}
long downloadId = DownloadAppUtil.downloadUpdateApkId;
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
cursor = manager.query(query);
if (cursor.moveToNext()) {
int staus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (staus == DownloadManager.STATUS_FAILED) {
manager.remove(downloadId);
} else if ((staus == DownloadManager.STATUS_SUCCESSFUL)
&& (DownloadAppUtil.downloadUpdateApkFilePath != null)) {
Intent it = new Intent(Intent.ACTION_VIEW);
it.setDataAndType(Uri.parse("file://" + DownloadAppUtil.downloadUpdateApkFilePath),
"application/vnd.android.package-archive");
// todo may have different uri addresses for different mobile phones and sdk versions
it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(it);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}
The judgement is that Download Manager. ACTION_DOWNLOAD_COMPLETE obtains the APK path for installation.
I. Preface
App online update is a common requirement. When the new version is released, the user enters our app and pops up the update prompt box, which updates the new version of app at the first time. Online updates are divided into the following steps:
1. Get the online version number through the interface, version Code 2. Compare the online version code with the local version code, and pop up the update window 3. Download APK files (file download) 4. Install APK
The first two steps are relatively simple, and the last two are important. Because Android versions collect and protect privileges and privacy, there will be various adaptation problems. So this paper summarizes the methods of online update and some adaptation problems encountered in app.
II. apk Download
apk download is actually file download, and there are many ways to download files:
1. Many tripartite frameworks have file upload and download capabilities, which can be supported by tripartite frameworks (such as Volley,OkHttp) 2. You can also open a thread to download. (You can use IntentService) 3. The simplest way: Android SDK actually provides us with Download Manager class, which can be easily downloaded with simple configuration settings.
In the third way, download the apk with Download Manager.
1. Download apk using Download Manager
Download Manager comes with SDK. The process is as follows:
(1) Create a Request for simple configuration (download address, file save address, etc.)
(2) After the download is completed, the system will send a download completed broadcast, we need to monitor the broadcast.
(3) After listening to the downloaded broadcast, find the downloaded apk file according to id
(4) Perform apk installation in the code.
public void downloadApk(String apkUrl, String title, String desc) {
// fix bug: You can't install the new version. You should delete the existing files before downloading.
File apkFile = new File(weakReference.get().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "test.apk");
if (apkFile != null && apkFile.exists()) {
apkFile.delete();
}
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
//Setting title
request.setTitle(title);
// Setup Description
request.setDescription(desc);
// Display notification bar after completion
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalFilesDir(weakReference.get(), Environment.DIRECTORY_DOWNLOADS, "test.apk");
//Create a download folder on the mobile SD card
// Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdir() ;
//Specify download/my / directory to SD card
// request.setDestinationInExternalPublicDir("/codoon/","test.apk");
request.setMimeType("application/vnd.android.package-archive");
//Remember reqId
mReqId = mDownloadManager.enqueue(request);
}
As shown in the above code, first build a Request, set the download address, title, description, apk storage directory, etc., and finally call mDownload Manager. enqueue (request) to start downloading.
Note: Here we need to remember this mReqId, because after downloading, we need to find the APK file according to this ID, and then install the apk.
2. Update download progress
To download files, we usually need to know the download progress, give users a friendly prompt in the interface, and app updates are the same. We need to show the current download progress and total progress on the interface, so that users know how long they will probably wait. So what about getting download progress?
Before downloading, we need to register an Observer in Activity, which is an observer. When the download progress changes, we will notify the observer and update the progress. The steps are as follows:
1. First, we define an observer, Download Change Observer, to observe the download progress. 2. Update UI progress in Download Change Observer and give user hints 3. Register Observer in Activity before downloading
The code is as follows:
DownloadChangeObserver.class:
class DownloadChangeObserver extends ContentObserver {
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public DownloadChangeObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
updateView();
}
}
In the updateView() method, query the download progress.
private void updateView() {
int[] bytesAndStatus = new int[]{0, 0, 0};
DownloadManager.Query query = new DownloadManager.Query().setFilterById(mReqId);
Cursor c = null;
try {
c = mDownloadManager.query(query);
if (c != null && c.moveToFirst()) {
//Number of bytes downloaded
bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
//Total number of bytes to download
bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
//Column index where the state is located
bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
}
} finally {
if (c != null) {
c.close();
}
}
if (mUpdateListener != null) {
mUpdateListener.update(bytesAndStatus[0], bytesAndStatus[1]);
}
Log.i(TAG, "Download progress:" + bytesAndStatus[0] + "/" + bytesAndStatus[1] + "");
}
According to the ID we recorded earlier to query progress, the code has been commented, no more.
To get the progress, you have to register Download ChangeObserver before downloading. The code is as follows:
weakReference.get().getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"), true,
mDownLoadChangeObserver);
3. Get the download results
After the download is completed, Download Manager will send a downloaded broadcast Download Manager. ACTION_DOWNLOAD_COMPLETE. We just need to listen to the broadcast and get the apk file installation after receiving the broadcast.
Define a broadcast Download Receiver.
class DownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
// Install APK
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
Logger.e(TAG, "receive a broadcast");
Uri uri;
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW);
if (completeDownLoadId == mReqId) {
uri = mDownloadManager.getUriForDownloadedFile(completeDownLoadId);
}
intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}
}
Register for broadcasting before downloading
// Register broadcasting, monitor APK download completion
weakReference.get().registerReceiver(mDownloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
Through the above steps, we basically complete the online update function of app, which can run normally under Android 6.0. But don't be too busy, this article is not over yet. Every Android version has some changes, which leads us to need to adapt different versions. Otherwise, there will be problems. Let's take a look at the relevant adaptation of Android 6.0,7.0,8.0.
3. Adapting Android 6.0
Through the previous steps, app online updates can run normally below 6.0. On Android 6.0, the following errors will be reported when installing:
Caused by:
5 android.content.ActivityNotFoundException:No Activity found to handle Intent { act=android.intent.action.VIEW typ=application/vnd.android.package-archive flg=0x10000000 }
Why did you report the above error? After debug, it was found that the Uri obtained by Download Manager was different between Android 6.0 and Android 6.0.
The difference is as follows: (1) Android 6.0, getUriForDownloadedFile gets the value: content://downloads/my_downloads/10
(2) Under Android 6.0, getUriForDownloaded File gets a value of: file://storage/emulated/0/Android/data/packgeName/files/Download/xxx.apk.
As you can see, Android 6.0 gets an apk address at the beginning of content://and the error will be reported when it is installed. How to solve it? After searching for information, the solution was found:
//Through downLoadId query downloaded apk, solve the problem of installation after 6.0
public static File queryDownloadedApk(Context context, long downloadId) {
File targetApkFile = null;
DownloadManager downloader = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadId != -1) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
Cursor cur = downloader.query(query);
if (cur != null) {
if (cur.moveToFirst()) {
String uriString = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
if (!TextUtils.isEmpty(uriString)) {
targetApkFile = new File(Uri.parse(uriString).getPath());
}
}
cur.close();
}
}
return targetApkFile;
}
As shown above, the code does not get Uri through getUriForDownloadedFile, but gets the apk address through the field of Download Manager. COLUMN_LOCAL_URI.
After adapting to Android 6.0, the code to install the apk is as follows:
/**
* @param context
* @param intent
*/
private void installApk(Context context, Intent intent) {
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
Logger.e(TAG, "receive a broadcast");
Uri uri;
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW);
if (completeDownLoadId == mReqId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // Less than 6.0
uri = mDownloadManager.getUriForDownloadedFile(completeDownLoadId);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { // 6.0 - 7.0
File apkFile = queryDownloadedApk(context, completeDownLoadId);
uri = Uri.fromFile(apkFile);
}
// Installation and Application
Logger.e("zhouwei", "Download completed");
intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}
}
IV. Adapting Android 7.0
Just adapted to 6.0, there is a problem on the machine above 7.0. Why? Because on Android 7.0, the access rights to files have been changed, and you can't access files using Uri in file:// format. Android 7.0 provides FileProvider. You should use this to get the APK address and install the apk. Simple adaptions are made as follows:
(1) Under the res directory, create a new XML folder and create a file provider_paths under xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="" />
<external-files-path
name="Download"
path="" />
</paths>
(2) Declare Provider in the Android Manifest.xml manifest file:
<!-- Android 7.0 Photographs, APK Download and save path-->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="packgeName.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
(3) File address acquisition on Android 7.0:
uri = FileProvider.getUriForFile(context,
"packageNam.fileProvider",
new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "xxx.apk"));
OK, so the 7.0 adaptation work is completed. The installation code after the adaptation is as follows:
/**
* @param context
* @param intent
*/
private void installApk(Context context, Intent intent) {
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
Logger.e(TAG, "receive a broadcast");
Uri uri;
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW);
if (completeDownLoadId == mReqId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // Less than 6.0
uri = mDownloadManager.getUriForDownloadedFile(completeDownLoadId);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { // 6.0 - 7.0
File apkFile = queryDownloadedApk(context, completeDownLoadId);
uri = Uri.fromFile(apkFile);
} else { // Android 7.0 or more
uri = FileProvider.getUriForFile(context,
"packgeName.fileProvider",
new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "xxx.apk"));
intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
// Installation and Application
Logger.e("zhouwei", "Download completed");
intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}
}
Note: Replace the package Nam above with your own package name and xxx.apk with your own apk name.
For more information about FileProvider, let's not start here. You can read Hongyang's article if you want to know more about it. Android 7.0 Behavior Change Sharing Files Between Applications Through FileProvider
It's very clear.
5. Adapting Android 8.0: Application privileges from unknown sources
So tired, continue to adapt to Android 8.0, because there is no Android 8.0 mobile phone, has not paid attention to, a few days ago, a Huawei user feedback online update version, the specific performance is: the apk download completed, flash, did not jump to the apk installation interface. After investigation, it is determined that Android 8.0 permission is a problem.
Android 8.0 or above, applications from unknown sources can not be installed by code (find apk in sd card, manual installation is possible), the switch of unknown application installation permission is removed, and replaced by the management list of applications from unknown sources, which needs to open the installation of your application from unknown sources. Jurisdiction. Google did this to prevent serious applications from starting and then starting to do some illegal things through upgrades, infringing on users'rights and interests.
Knowing the problem, let's adapt:
(1) Declare permissions in the manifest file: REQUEST_INSTALL_PACKAGES
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
(2) In the code to determine whether the user has been authorized, if authorized, can be installed directly, if not authorized, then jump to the authorization list, let the user open the installation rights of applications from unknown sources, open, then install the application.
Add the following code to the broadcast that monitors the download status of the apk:
boolean haveInstallPermission;
// Compatible with Android 8.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//Get permission to install applications from unknown sources first
haveInstallPermission = context.getPackageManager().canRequestPackageInstalls();
if (!haveInstallPermission) {//No privileges
// Bullet window and set page authorization
final AndroidOInstallPermissionListener listener = new AndroidOInstallPermissionListener() {
@Override
public void permissionSuccess() {
installApk(context, intent);
}
@Override
public void permissionFail() {
ToastUtils.shortToast(context, "Authorization failed to install the application");
}
};
AndroidOPermissionActivity.sListener = listener;
Intent intent1 = new Intent(context, AndroidOPermissionActivity.class);
context.startActivity(intent1);
} else {
installApk(context, intent);
}
} else {
installApk(context, intent);
}
Because we need a bullet-box prompt when authorizing, we use an Activity agent to create an Activity: Android OPermissionActivity to apply for permission. After the user clicks on the settings, he jumps to the permission settings interface, and then we judge in the onActivity Result that the authorization is successful.
The Android OPermissionActivity code is as follows:
/**
* Compatible with Android 8. 0 APP Online Update, Privilege Application Interface
* Created by zhouwei on 2018/3/23.
*/
public class AndroidOPermissionActivity extends BaseActivity {
public static final int INSTALL_PACKAGES_REQUESTCODE = 1;
private AlertDialog mAlertDialog;
public static AppDownloadManager.AndroidOInstallPermissionListener sListener;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Popup
if (Build.VERSION.SDK_INT >= 26) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, INSTALL_PACKAGES_REQUESTCODE);
} else {
finish();
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case INSTALL_PACKAGES_REQUESTCODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (sListener != null) {
sListener.permissionSuccess();
finish();
}
} else {
//startInstallPermissionSettingActivity();
showDialog();
}
break;
}
}
private void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.app_name);
builder.setMessage("For normal upgrade xxx APP,Please click the Settings button to allow the installation of applications from unknown sources. This function is limited to xxx APP Version upgrade");
builder.setPositiveButton("Set up", new DialogInterface.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onClick(DialogInterface dialogInterface, int i) {
startInstallPermissionSettingActivity();
mAlertDialog.dismiss();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if (sListener != null) {
sListener.permissionFail();
}
mAlertDialog.dismiss();
finish();
}
});
mAlertDialog = builder.create();
mAlertDialog.show();
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallPermissionSettingActivity() {
//Note that this is the 8.0 new API
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && resultCode == RESULT_OK) {
// Successful authorization
if (sListener != null) {
sListener.permissionSuccess();
}
} else {
// privilege grant failed
if (sListener != null) {
sListener.permissionFail();
}
}
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
sListener = null;
}
}
Note: When jumping to an unknown application authorization list through Intent, you must add a package name so that you can jump directly to your app, otherwise you can only jump to the list.
Okay, so Android 8.0 can also be updated online.
6. Complete code encapsulating a class AppDownload Manager
To be independent of an Activity, an AppDownload Manager is encapsulated
A few lines of code can be updated online, giving the complete code:
public class AppDownloadManager {
public static final String TAG = "AppDownloadManager";
private WeakReference<Activity> weakReference;
private DownloadManager mDownloadManager;
private DownloadChangeObserver mDownLoadChangeObserver;
private DownloadReceiver mDownloadReceiver;
private long mReqId;
private OnUpdateListener mUpdateListener;
public AppDownloadManager(Activity activity) {
weakReference = new WeakReference<Activity>(activity);
mDownloadManager = (DownloadManager) weakReference.get().getSystemService(Context.DOWNLOAD_SERVICE);
mDownLoadChangeObserver = new DownloadChangeObserver(new Handler());
mDownloadReceiver = new DownloadReceiver();
}
public void setUpdateListener(OnUpdateListener mUpdateListener) {
this.mUpdateListener = mUpdateListener;
}
public void downloadApk(String apkUrl, String title, String desc) {
// fix bug: You can't install the new version. You should delete the existing files before downloading.
File apkFile = new File(weakReference.get().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_name.apk");
if (apkFile != null && apkFile.exists()) {
apkFile.delete();
}
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
//Setting title
request.setTitle(title);
// Setup Description
request.setDescription(desc);
// Display notification bar after completion
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalFilesDir(weakReference.get(), Environment.DIRECTORY_DOWNLOADS, "app_name.apk");
//Create a download folder on the mobile SD card
// Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdir() ;
//Specify download/my / directory to SD card
// request.setDestinationInExternalPublicDir("/codoon/","codoon_health.apk");
request.setMimeType("application/vnd.android.package-archive");
//
mReqId = mDownloadManager.enqueue(request);
}
/**
* Cancel Download
*/
public void cancel() {
mDownloadManager.remove(mReqId);
}
/**
* Correspondence{@link Activity }
*/
public void resume() {
//Set up listener Uri.parse("content://downloads/my_downloads")
weakReference.get().getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"), true,
mDownLoadChangeObserver);
// Register broadcasting, monitor APK download completion
weakReference.get().registerReceiver(mDownloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
/**
* Correspondence{@link Activity#onPause()} ()}
*/
public void onPause() {
weakReference.get().getContentResolver().unregisterContentObserver(mDownLoadChangeObserver);
weakReference.get().unregisterReceiver(mDownloadReceiver);
}
private void updateView() {
int[] bytesAndStatus = new int[]{0, 0, 0};
DownloadManager.Query query = new DownloadManager.Query().setFilterById(mReqId);
Cursor c = null;
try {
c = mDownloadManager.query(query);
if (c != null && c.moveToFirst()) {
//Number of bytes downloaded
bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
//Total number of bytes to download
bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
//Column index where the state is located
bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
}
} finally {
if (c != null) {
c.close();
}
}
if (mUpdateListener != null) {
mUpdateListener.update(bytesAndStatus[0], bytesAndStatus[1]);
}
Log.i(TAG, "Download progress:" + bytesAndStatus[0] + "/" + bytesAndStatus[1] + "");
}
class DownloadChangeObserver extends ContentObserver {
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public DownloadChangeObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
updateView();
}
}
class DownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
boolean haveInstallPermission;
// Compatible with Android 8.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//Get permission to install applications from unknown sources first
haveInstallPermission = context.getPackageManager().canRequestPackageInstalls();
if (!haveInstallPermission) {//No privileges
// Bullet window and set page authorization
final AndroidOInstallPermissionListener listener = new AndroidOInstallPermissionListener() {
@Override
public void permissionSuccess() {
installApk(context, intent);
}
@Override
public void permissionFail() {
ToastUtils.shortToast(context, "Authorization failed to install the application");
}
};
AndroidOPermissionActivity.sListener = listener;
Intent intent1 = new Intent(context, AndroidOPermissionActivity.class);
context.startActivity(intent1);
} else {
installApk(context, intent);
}
} else {
installApk(context, intent);
}
}
}
/**
* @param context
* @param intent
*/
private void installApk(Context context, Intent intent) {
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
Logger.e(TAG, "receive a broadcast");
Uri uri;
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW);
if (completeDownLoadId == mReqId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // Less than 6.0
uri = mDownloadManager.getUriForDownloadedFile(completeDownLoadId);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { // 6.0 - 7.0
File apkFile = queryDownloadedApk(context, completeDownLoadId);
uri = Uri.fromFile(apkFile);
} else { // Android 7.0 or more
uri = FileProvider.getUriForFile(context,
"package_name.fileProvider",
new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_name.apk"));
intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
// Installation and Application
Logger.e("zhouwei", "Download completed");
intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}
}
//Through downLoadId query downloaded apk, solve the problem of installation after 6.0
public static File queryDownloadedApk(Context context, long downloadId) {
File targetApkFile = null;
DownloadManager downloader = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadId != -1) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
Cursor cur = downloader.query(query);
if (cur != null) {
if (cur.moveToFirst()) {
String uriString = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
if (!TextUtils.isEmpty(uriString)) {
targetApkFile = new File(Uri.parse(uriString).getPath());
}
}
cur.close();
}
}
return targetApkFile;
}
public interface OnUpdateListener {
void update(int currentByte, int totalByte);
}
public interface AndroidOInstallPermissionListener {
void permissionSuccess();
void permissionFail();
}
}
It's easy to use, as follows:
(1) Pop-up update prompt box: prompt user to update
private void showUpdateDialog(final AppUpdateInfo updateInfo) {
AppUpdateDialog dialog = new AppUpdateDialog(getContext());
dialog.setAppUpdateInfo(updateInfo);
dialog.setOnUpdateClickListener(new AppUpdateDialog.OnUpdateClickListener() {
@Override
public void update(final AppUpdateDialog updateDialog) {
String title = "app name";
String desc = "Version Update";
mDownloadManager.setUpdateListener(new AppDownloadManager.OnUpdateListener() {
@Override
public void update(int currentByte, int totalByte) {
updateDialog.setProgress(currentByte, totalByte);
if ((currentByte == totalByte) && totalByte != 0) {
updateDialog.dismiss();
}
}
});
mDownloadManager.downloadApk(updateInfo.download_url, title, desc);
}
});
dialog.setCanceledOnTouchOutside(false);
dialog.setCancelable(false);
dialog.show();
}
(2) Note that the corresponding method is called in onResume and onPause:
@Override
public void onResume() {
super.onResume();
if (mDownloadManager != null) {
mDownloadManager.resume();
}
}
@Override
public void onPause() {
super.onPause();
if (mDownloadManager != null) {
mDownloadManager.onPause();
}
}
Summary
This article summarizes some of the adaptation problems encountered in the online update of app in the project. With regard to the adaptation of Android 6.0, if you do not use Download Manager, you may not encounter this problem. Adaptation of 7.0 and 8.0 will be available in either way.
This is the end of the adaptation of the online update version of app. If you have any questions, you are welcome to point out.
Update of September 4, 2018
There's a lot of AppUpdate Dialog code in the comment area, but there's nothing to refer to. Just a custom Dialog, let me post it.
public class AppUpdateDialog extends HeaderBaseDialog implements View.OnClickListener {
private String mContent;
private AppUpdateInfo mAppUpdateInfo;
private OnUpdateClickListener mOnUpdateListener;
private ProgressBar mProgressBar;
private TextView mValueText;
private boolean mForceUpdate = true;
public AppUpdateDialog(@NonNull Context context) {
super(context);
}
public void setContent(String mContent) {
this.mContent = mContent;
}
public void setAppUpdateInfo(AppUpdateInfo mAppUpdateInfo) {
this.mAppUpdateInfo = mAppUpdateInfo;
}
public void setProgress(int progress, int maxValue) {
if (maxValue == 0) {
return;
}
mProgressBar.setMax(maxValue);
mProgressBar.setProgress(progress);
mValueText.setText((int) (progress * 1.0f / maxValue * 100) + "%");
}
public void setOnUpdateClickListener(OnUpdateClickListener mOnUpdateListener) {
this.mOnUpdateListener = mOnUpdateListener;
}
@Override
protected void initView() {
setCanceledOnTouchOutside(false);
if (mAppUpdateInfo.force_update) {
mForceUpdate = true;
} else {
mForceUpdate = false;
}
TextView textView = (TextView) findViewById(R.id.app_update_content);
TextView version = (TextView) findViewById(R.id.app_update_version);
mProgressBar = (ProgressBar) findViewById(R.id.app_update_progress);
mValueText = (TextView) findViewById(R.id.app_update_current_percent);
version.setText(mAppUpdateInfo.version + " Update content");
textView.setText(mAppUpdateInfo.note);
TextView btnUpdate = (TextView) findViewById(R.id.btn_app_update);
btnUpdate.setText("To update");
btnUpdate.setOnClickListener(this);
}
@Override
protected int getHeaderLayout() {
return R.layout.app_update_header_layout;
}
@Override
protected int getContentLayout() {
return R.layout.app_update_content_layout;
}
@Override
protected Drawable getBgDrawable() {
return null;
}
@Override
protected int getStartColor() {
return getContext().getResources().getColor(R.color.body_weight_start_color);
}
@Override
protected int getEndColor() {
return getContext().getResources().getColor(R.color.body_weight_end_color);
}
@Override
protected boolean isShowClose() {
return !mForceUpdate;
}
@Override
public void onClick(View v) {
// update
if (mOnUpdateListener != null) {
findViewById(R.id.update_progress_layout).setVisibility(View.VISIBLE);
findViewById(R.id.btn_app_update).setVisibility(View.GONE);
mOnUpdateListener.update(this);
}
}
public interface OnUpdateClickListener {
void update(AppUpdateDialog dialog);
}
}