android - 实现自动升级 upgrade auto update version (更新进度 progressbar, 自动安装install apk, 读取文件 fileprovider)
访问量: 2226
下载并自动更新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" />