安卓实现应用内更新
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);
}
}
}
}