android - 实现自动升级 upgrade auto update version (更新进度 progressbar, 自动安装install apk, 读取文件 fileprovider)
访问量: 2270
下载并自动更新progressbar, 参考:
https://stackoverflow.com/questions/3028306/download-a-file-with-android-and-showing-the-progress-in-a-progressdialog下载后, 自动安装 apk ,参考:
FileUriExposedException: (无法自动执行安装) https://stackoverflow.com/questions/39147608/android-install-apk-with-intent-view-action-not-working-with-file-provider?rq=1 这个是android > 26的: https://stackoverflow.com/questions/38200282/android-os-fileuriexposedexception-file-storage-emulated-0-test-txt-exposed1. 在入口activity 中, 调用一个方法:
showUpgradeDialogIfNeeded(initItem.getUpgrade_information());
2.
/**
* 显示升级弹窗
*/
public void showUpgradeDialogIfNeeded(InitItem.UpgradeInformationBean upgradeInformationBean) {
if (upgradeInformationBean == null) {
return;
}
final InitItem.UpgradeInformationBean.AndroidBean androidBean = upgradeInformationBean.getAndroid();
if (androidBean == null) {
return;
}
// 获得本机的app 的版本。 进行对比。
String localVersion = Constants.getMetaData(getActivity(), Constants.CURRENT_VERSION);
Log.d(TAG, "== local version: " + localVersion);
Log.d(TAG, "== remote version: " + androidBean.getVersion());
// 如果对比,版本号一致的话,就没必要升级。跳过。
if(localVersion.compareTo(androidBean.getVersion()) >= 0){
Log.d(TAG, "== upgrade is NOT needed. return");
return;
}
Log.d(TAG, "== upgrade is needed.");
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
// 开始确定 升级提示的文字。
String message = "";
SharedPreferences settings = getSharedPreferences(Constants.SHARED_PREFERRENCE_YUNBI, 0);
// 加载语言(日语,韩语啥的)
String languageToLoad = settings.getString(Constants.KEY_LANGUAGE, Constants.DEFAULT_LANGUAGE);
Log.d(TAG, "== languageToLoad: " + languageToLoad);
if(languageToLoad.equals("en")){
message = androidBean.getMessage().getEn();
Log.d(TAG, "== now loading english upgrade message" + message);
}else{
message = androidBean.getMessage().getZh();
}
String title = getResources().getString(R.string.upgrade_message_title, androidBean.getVersion());
builder.setTitle(title)
.setMessage(message)
.setPositiveButton(R.string.upgrade_now, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startDownload(androidBean);
}
});
// 测试时打开
// if (true) {
if (androidBean.isIs_forced_upgrade()) {
// 强制更新不可取消
builder.setMessage(message + "\n\n" + getResources().getString(R.string.upgrade_message_must_do));
builder.setCancelable(false);
} else {
// 可以取消
builder.setNegativeButton(R.string.upgrade_later, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
});
} // 弹出alert
builder.show();
}
/**
* 开始下载apk
*/
private void startDownload(String apk_url) {
if (this == null) {
return;
}
final ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.show();
String versionName = apk_url.substring(apk_url.lastIndexOf('/') + 1);
File cacheDir = this.getExternalCacheDir();
Log.d(TAG, "== cacheDir: " + cacheDir.getAbsolutePath());
final File file = new File(cacheDir, versionName);
if (!cacheDir.exists()) {
Log.d(TAG, "== cacheDir not exists, making dir...");
cacheDir.mkdirs();
}
String filePath = file.getAbsolutePath();
Log.d(TAG, "== download file path: " + filePath);
new AsyncDownloader() {
@Override
protected void onProgressUpdate(Long... values) {
long progress = values[0];
long max = values[1];
progressDialog.setProgress((int) progress);
progressDialog.setMax((int) max);
progressDialog.setProgressNumberFormat((CommonUtils.humanReadableByteCount(progress, true)) + "/" + (CommonUtils.humanReadableByteCount(max, true)));
}
@Override
protected void onPostExecute(Boolean result) {
Log.d(TAG, "== download result: " + result);
if (result) {
installApk(file);
}
progressDialog.dismiss();
}
}.execute(apk_url, filePath);
}
3. 要有个asyncdownloader.java
package com.yunbi.utils; import android.os.AsyncTask; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class AsyncDownloader extends AsyncTask{ @Override protected Boolean doInBackground(String... params) { String url = params[0]; String filePath = params[1]; OkHttpClient httpClient = new OkHttpClient(); Call call = httpClient.newCall(new Request.Builder().url(url).get().build()); try { Response response = call.execute(); if (response.code() == 200) { InputStream inputStream = null; try { inputStream = response.body().byteStream(); byte[] buff = new byte[1024 * 4]; Long downloaded = 0L; Long target = response.body().contentLength(); publishProgress(0L, target); OutputStream outputStream = new FileOutputStream(filePath); while (true) { int readed = inputStream.read(buff); if(readed == -1){ break; } outputStream.write(buff, 0, readed); //write buff downloaded += readed; publishProgress(downloaded, target); if (isCancelled()) { return false; } } return downloaded.equals(target); } catch (IOException ignore) { return false; } finally { if (inputStream != null) { inputStream.close(); } } } else { return false; } } catch (IOException e) { e.printStackTrace(); return false; } } @Override protected void onProgressUpdate(Long... values) { } @Override protected void onPostExecute(Boolean result) { } }
4. 上面用到的一个Utils.
package com.yunbi;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Utils {
public static File saveToFile(Context context, byte[] portraitImgData, String name) {
File file = new File(context.getCacheDir(), name);
if (file.exists()) {
file.delete();
}
try {
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(portraitImgData);
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
/**
* 设置屏幕顶栏的颜色
*/
public static void setWindowStatusBarColor(Activity activity) {
activity.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor( Color.TRANSPARENT);
// window.setNavigationBarColor(Color.TRANSPARENT);
}
}
public static String humanReadableByteCount(long bytes, boolean si) {
int unit = si ? 1000 : 1024;
if (bytes < unit) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
/**
* This method converts dp unit to equivalent pixels, depending on device density.
*
* @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
* @param context Context to get resources and device specific display metrics
* @return A float value to represent px equivalent to dp depending on device density
*/
public static float convertDpToPixel(float dp, Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = dp * ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
return px;
}
/**
* This method converts device specific pixels to density independent pixels.
*
* @param px A value in px (pixels) unit. Which we need to convert into db
* @param context Context to get resources and device specific display metrics
* @return A float value to represent dp equivalent to px value
*/
public static float convertPixelsToDp(float px, Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float dp = px / ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
return dp;
}
}
为当前的Activity获取权限:
/**
* 以下为获取权限
*/
private static final int REQUEST_WRITE_PERMISSION = 786;
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_WRITE_PERMISSION && grantResults[0] == PackageManager.PERMISSION_GRANTED)
Toast.makeText(this, "Permission granted", Toast.LENGTH_LONG).show();
}
private void requestPermission() {
Log.d(TAG, "== in requestPermission");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, "== before requestPermissions....");
requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_PERMISSION);
}
}
private boolean canReadWriteExternal() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED;
}
自动执行安装程序:
/**
* 安装apk
* @param apkFile
*/
private void installApk(File apkFile) {
Log.d(TAG, "== installApk: " + apkFile);
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.d(TAG, "== Build.VERSION.SDK_INT: " + Build.VERSION.SDK_INT);
if(!canReadWriteExternal()){
Log.d(TAG, canReadWriteExternal() + ". --- before request, canReadWriteExternal()");
requestPermission();
Log.d(TAG, canReadWriteExternal() + ". --- after request, canReadWriteExternal()");
}
Uri apkUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", apkFile);
intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(apkUri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
Uri apkUri = Uri.fromFile(apkFile);
intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
Log.d(TAG, "== canReadWriteExternal: " + canReadWriteExternal());
startActivity(intent);
}
增加 res/xml/file_paths.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_download" path="/"/>
</paths>
为androidmanifest.xml 文件,增加这个provider: (注意authorities定义的 ...fileprovider 要跟 自动安装apk程序的fileprovider字符串一致)
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
同时,记得增加各种权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />