Back

android - 实现自动升级 upgrade auto update version (更新进度 progressbar, 自动安装install apk, 读取文件 fileprovider)

发布时间: 2020-01-13 12:31:00

下载并自动更新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-exposed

1. 在入口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" />

Back