安卓实现应用内更新

安卓实现应用内更新


android 应用更新 移动端
我们做的几个B端App,IOS端是通过app store更新,这个没什么问题;但是在安卓端,一开始采用的更新方式都是打开浏览器,然后在通过浏览器的默认下载行为下载最新版本的apk,完成更新。
之前有个需求,让用户可以实现应用内更新,免去打开浏览器的步骤,同时可以规避掉部分安卓系统打开浏览器失效的问题。
由于没有原生团队支持,app也都是前端纯weex开发的,所以最后折中选择了一个方案,就是使用原有的app内跳转webview功能,跳转地址使用apk文件的线上地址,然后在webViewActivity内实现下载行为。

这里做下记录,完整的代码如下:

package com.eros.framework.activity;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.provider.MediaStore;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.DownloadListener;
import android.webkit.JavascriptInterface;
import android.webkit.SslErrorHandler;
import android.webkit.URLUtil;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Toast;
import android.webkit.ValueCallback;

import com.eros.framework.BMWXApplication;
import com.eros.framework.BMWXEnvironment;
import com.eros.framework.R;
import com.eros.framework.adapter.router.RouterTracker;
import com.eros.framework.constant.Constant;
import com.eros.framework.constant.WXEventCenter;
import com.eros.framework.event.mediator.EventCenter;
import com.eros.framework.manager.ManagerFactory;
import com.eros.framework.manager.impl.FileManager;
import com.eros.framework.manager.impl.ModalManager;
import com.eros.framework.manager.impl.dispatcher.DispatchEventManager;
import com.eros.framework.model.WebViewParamBean;
import com.eros.framework.utils.SharePreferenceUtil;
import com.eros.widget.utils.BaseCommonUtil;

import java.io.File;
import java.util.Map;

/**
 * Created by Carry on 2017/8/25.
 */

public class GlobalWebViewActivity extends AbstractWeexActivity {

    private final String LOCAL_SCHEME = "bmlocal";

    private View rl_refresh;
    private ProgressBar mProgressBar;
    private WebView mWeb;
    private String mFailUrl;
    public static String WEBVIEW_URL = "WEBVIEW_URL";
    private WebViewParamBean mWebViewParams;
    private RelativeLayout mContainer;
    private String mTitle;
    private String webUrl;
    private String apkUrl;

    private static final int REQUEST_EXTERNAL_STORAGE = 1;

    private static String[] PERMISSIONS_STORAGE = {

            "android.permission.READ_EXTERNAL_STORAGE",

            "android.permission.WRITE_EXTERNAL_STORAGE" };

    private ValueCallback<Uri> mUploadMessage;
    private ValueCallback<Uri[]> mUploadMessageNew;
    private final static int FILECHOOSER_RESULTCODE=101;
    private final static int FILECHOOSER_RESULTCODE_NEW=102;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview);
        init();
        statusBarHidden(BMWXApplication.getWXApplication().IS_FULL_SCREEN);
        //动态申请存储权限
        verifyStoragePermissions(GlobalWebViewActivity.this);

        //广播监听下载完成回调 安装
        DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
        registerReceiver(receiver, intentFilter);
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    //检查读写权限
    private void verifyStoragePermissions(Activity activity) {
        try {
            //检测是否有写的权限
            int permission = ActivityCompat.checkSelfPermission(activity,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 没有写的权限,去申请写的权限,会弹出对话框
                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
            }else{
                setDownloadListen();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //用户点击同意或不同意的回调
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
            grantResults) {
        if (requestCode == REQUEST_EXTERNAL_STORAGE) {
            for (int i = 0; i < permissions.length; i++) {
                if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { // 用户点的拒绝,仍未拥有权限
                    Toast.makeText(this, "请在设置中打开存储权限", Toast.LENGTH_SHORT).show();
                    // 在系统设置中打开该应用的设置页面
                    Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                    Uri uri = Uri.fromParts("package", getPackageName(), null);
                    intent.setData(uri);
                    startActivity(intent);
                    return;
                }else{
                    setDownloadListen();
                }
            }
        }
    }

    //跳转浏览器
    private void downloadByBrowser(String url) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addCategory(Intent.CATEGORY_BROWSABLE);
        intent.setData(Uri.parse(url));
        startActivity(intent);
    }

    private void init() {
        Intent data = getIntent();
        mWebViewParams = (WebViewParamBean) data.getSerializableExtra(Constant.WEBVIEW_PARAMS);
        String mUrl = mWebViewParams.getUrl();

        Uri imageUri = Uri.parse(mUrl);
        if (LOCAL_SCHEME.equalsIgnoreCase(imageUri.getScheme())) {
            mUrl = "file://" + localPath(imageUri);
        }

        rl_refresh = findViewById(R.id.rl_refresh);
        mProgressBar = (ProgressBar) findViewById(R.id.pb_progress);
        mWeb = (WebView) findViewById(R.id.webView);


        mContainer = (RelativeLayout) findViewById(R.id.rl_container);
        WebSettings settings = mWeb.getSettings();
        settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
        addWebJavascriptInterface();
        settings.setDomStorageEnabled(true);
        if (Build.VERSION.SDK_INT >= 21) {
            settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }
        mWeb.setWebViewClient(new MyWebViewClient(this));
        mWeb.setWebChromeClient(new MyWebChromeClient());

        webUrl = mUrl;
        //if (!TextUtils.isEmpty(mUrl)) {
        //    mWeb.loadUrl(mUrl);
        //}
        //ModalManager.BmLoading.showLoading(this, "", true);

    }

    void setDownloadListen(){
        mWeb.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
                apkUrl = url;
                try {
                    ModalManager.BmLoading.showLoading(GlobalWebViewActivity.this, "更新下载中,请在任务栏查看进度\
请勿离开此页面...", false);
//                    Toast.makeText(GlobalWebViewActivity.this, "下载中,请在任务栏查看进度...", Toast.LENGTH_SHORT).show();
                    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
                    // 允许媒体扫描,根据下载的文件类型被加入相册、音乐等媒体库
                    request.allowScanningByMediaScanner();
                    // 设置通知的显示类型,下载进行时和完成后显示通知
                    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                    // 设置通知栏的标题,如果不设置,默认使用文件名
                    request.setTitle("App");
                    // 设置通知栏的描述
                    request.setDescription("正在下载......");
                    // 允许在计费流量下下载
                    request.setAllowedOverMetered(true);
                    // 允许该记录在下载管理界面可见
                    request.setVisibleInDownloadsUi(true);
                    // 允许漫游时下载
                    request.setAllowedOverRoaming(true);
                    // 允许下载的网络类型
                    //request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
                    // 设置下载文件保存的路径和文件名
                    String fileName = URLUtil.guessFileName(url, contentDisposition, mimeType);
                    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
                    //另外可选一下方法,自定义下载路径
                    //request.setDestinationUri()
                    //request.setDestinationInExternalFilesDir()
                    final DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                    // 添加一个下载任务
                    long downloadId = downloadManager.enqueue(request);
                }catch(Exception e){
                    downloadByBrowser(url);
                }
            }
        });
        if (!TextUtils.isEmpty(webUrl)) {
            mWeb.loadUrl(webUrl);
        }
    }

    private class DownloadCompleteReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            try {
                if (intent != null) {
                    if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
                        long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                        DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
                        String type = downloadManager.getMimeTypeForDownloadedFile(downloadId);
                        if (TextUtils.isEmpty(type)) {
                            type = "*/*";
                        }
                        Uri uri = downloadManager.getUriForDownloadedFile(downloadId);
                        if (uri != null) {
                            //android 6.0 处理
                            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { // 6.0 - 7.0
                                File apkFile = queryDownloadedApk(context, downloadId);
                                uri = Uri.fromFile(apkFile);
                            }
                            try {
                                Intent handlerIntent = new Intent();
                                //handlerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                handlerIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                                handlerIntent.setAction(Intent.ACTION_VIEW);
                                handlerIntent.setDataAndType(uri, type);
                                context.startActivity(handlerIntent);
                            } catch (Exception e) {
                                Toast.makeText(GlobalWebViewActivity.this, "请在任务栏打开下载完成的安装包", Toast.LENGTH_SHORT).show();
                            }
                        } else {
                            downloadByBrowser(apkUrl);
                        }
                    }
                } else {
                    downloadByBrowser(apkUrl);
                }
            }catch(Exception e){
                downloadByBrowser(apkUrl);
            }
        }
    }

    //通过downLoadId查询下载的apk,解决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;
    }


    @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
    private void addWebJavascriptInterface() {
        WebSettings settings = mWeb.getSettings();
        settings.setJavaScriptEnabled(true);
        mWeb.addJavascriptInterface(new JSMethod(this), "bmnative");
    }

    private String localPath(Uri uri) {
        String path = uri.getHost() + File.separator +
                uri.getPath() + "?" + uri.getQuery();
        return FileManager.getPathBundleDir(this, "bundle/" + path)
                .getAbsolutePath();
    }



    //遇到ssl错误提示用户
    private void handleSSLError(final SslErrorHandler handler) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage(R.string.str_error_ssl_cert_invalid);
        builder.setPositiveButton(getResources().getString(R.string.str_ensure), new
                DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        handler.proceed();
                    }
                });
        builder.setNegativeButton(getResources().getString(R.string.str_cancel), new
                DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        handler.cancel();
                    }
                });
        final AlertDialog dialog = builder.create();
        dialog.show();
    }


    private class MyWebViewClient extends WebViewClient {
        GlobalWebViewActivity activity;

        public MyWebViewClient(GlobalWebViewActivity activity) {
            this.activity = activity;
        }

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            handleSSLError(handler);
        }


        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return super.shouldOverrideUrlLoading(view, url);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
//            ModalManager.BmLoading.dismissLoading(activity);
            super.onPageFinished(view, url);
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String
                failingUrl) {
            super.onReceivedError(view, errorCode, description, failingUrl);
            //L.i("web failingUrl == " + failingUrl);
            activity.mFailUrl = failingUrl;
            activity.showRefreshView();
        }
    }


    private class MyWebChromeClient extends WebChromeClient {
        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            super.onProgressChanged(view, newProgress);
        }

        @Override
        public void onReceivedTitle(WebView view, String title) {
            super.onReceivedTitle(view, title);
            if (!TextUtils.isEmpty(title) && mTitle == null) {
                getNavigationBar().setTitle(title);
            }
        }

        @Override

        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
            Log.e("onConsoleMessage", "onConsoleMessage>>>>>" + consoleMessage.message());
            return super.onConsoleMessage(consoleMessage);
        }

        // android 5.0
        public boolean onShowFileChooser(
                WebView webView, ValueCallback<Uri[]> uploadMsg,
                WebChromeClient.FileChooserParams fileChooserParams) {
            mUploadMessageNew = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            GlobalWebViewActivity.this.startActivityForResult(Intent.createChooser(i,"File Chooser"), FILECHOOSER_RESULTCODE_NEW);

            return true;
        }


        public void openFileChooser(ValueCallback<Uri> uploadMsg) {

            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            GlobalWebViewActivity.this.startActivityForResult(Intent.createChooser(i,"File Chooser"), FILECHOOSER_RESULTCODE);

        }

        // For Android 3.0+
        public void openFileChooser( ValueCallback uploadMsg, String acceptType ) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            GlobalWebViewActivity.this.startActivityForResult(
                    Intent.createChooser(i, "File Browser"),
                    FILECHOOSER_RESULTCODE);
        }

        //For Android 4.1
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            GlobalWebViewActivity.this.startActivityForResult( Intent.createChooser( i, "File Chooser" ), GlobalWebViewActivity.FILECHOOSER_RESULTCODE );
        }
    }

    private void showRefreshView() {
        showWebCloseView();
    }


    @Override
    public void onBackPressed() {
        if (mWeb.canGoBack()) {
            mWeb.goBack();
        } else {
            BaseCommonUtil.clearAllCookies(this);
            super.onBackPressed();
        }

    }

    private void showWebCloseView() {

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode,
                                    Intent intent) {
        if(requestCode==FILECHOOSER_RESULTCODE_NEW)
        {
            if (null == mUploadMessageNew) return;
            Uri[] results = null;
            // Check that the response is a good one
            if (resultCode == Activity.RESULT_OK) {
                if (intent != null) {
                    String dataString = intent.getDataString();
                    if (dataString != null) {
                        results = new Uri[]{Uri.parse(dataString)};
                    }
                }
            }
            mUploadMessageNew.onReceiveValue(results);
            mUploadMessageNew = null;
        } else if(requestCode==FILECHOOSER_RESULTCODE)
        {
            if (null == mUploadMessage) return;
            Uri result = intent == null || resultCode != RESULT_OK ? null
                    : intent.getData();
            mUploadMessage.onReceiveValue(result);
            mUploadMessage = null;
        }
    }

    public static class JSMethod {
        private Context mContext;

        public JSMethod(Context mContext) {
            this.mContext = mContext;
        }

        @JavascriptInterface
        public void closePage() {
            //关闭当前页面
            RouterTracker.popActivity();
        }

        @JavascriptInterface
        public void fireEvent(String eventName, String param) {
            if (!TextUtils.isEmpty(eventName)) {
                Intent emit = new Intent(WXEventCenter.EVENT_JS_EMIT);
                emit.putExtra("data", new EventCenter.Emit(eventName, param));
                ManagerFactory.getManagerService(DispatchEventManager.class).getBus().post(emit);
            }
        }
    }
}
© 2025 Niko Xie