问题描述:
webview加载的网页中,会出现下载网页中app的情况,有些是直接请求服务器下载,而有些是跳转应用市场下载。在无法与js交互的情况下,网上很多此类情况的解决方法是跳转到手机浏览器去下载,但这明显不是最明智的解决方法,我根据个人的搜索总结,解决了在webciew中下载第三方app,中间有些是拷贝他人的地方,已经不记得出处,望谅解。
解决方法:
1.webview布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progressbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:maxHeight="1dp"
android:minHeight="1dp"
android:progressDrawable="@drawable/progressbar" />
<WebView
android:id="@+id/webVie"
android:layout_width="match_parent"
android:layout_height="match_parent" />
LinearLayout>
2.加载webview的类
public class WebviewActivity extends AppCompatActivity implements View.OnClickListener {
private WebView webview;
private ProgressBar mProgressBar;
private long downId;
private DownloadService.DownloadBinder mDownloadBinder;
private Disposable mDisposable;//可以取消观察者
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mDownloadBinder = (DownloadService.DownloadBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mDownloadBinder = null;
}
};
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_product_web);
Intent intent = new Intent(this, DownloadService.class);
startService(intent);//开启服务,此处有异议
bindService(intent, mConnection, BIND_AUTO_CREATE);//绑定服务
initView();
initData();
}
private void initView() {
webview = (WebView) findViewById(R.id.webView);
mProgressBar = (ProgressBar) findViewById(R.id.progressbar);//加载进度条
//设置WebView属性
WebSettings webSettings = webview.getSettings();
webSettings.setAllowFileAccess(true);
webSettings.setDatabaseEnabled(true);
webSettings.setBlockNetworkImage(false);
webSettings.setJavaScriptEnabled(true);
webSettings.setAllowContentAccess(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
webSettings.setAppCacheEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
// 同一个页面有时会出现http和https两种证书,导致部分页面加载不出来
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
webview.setWebViewClient(new HelloWebViewClient());
//设置Web视图
webview.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
bar.setProgress(newProgress);//设置进度条
}
}
}
private void initData() {
webview.requestFocusFromTouch();//获手势焦点
//webview下载监听
webview.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
if (mDownloadBinder != null) {
mDownloadBinder.setInstallMode(false);
Toast.makeText(ProductWebActivity.this, "应用将在通知栏查进行下载,请确保读取已安装应用列表权限打开", Toast.LENGTH_LONG).show();
downId=mDownloadBinder.startDownload(url,"XXXXX");//"xxxxx"为自定义的app包名(可以通过url截取)
}
Observable
.interval(100, 200, TimeUnit.MILLISECONDS, Schedulers.io())//无限轮询,准备查询进度,在io线程执行
.filter(times -> mDownloadBinder != null)
.map(i -> mDownloadBinder.getProgress(downId))//获得下载进度
.takeUntil(progress -> progress >= 100)//返回true就停止了,当进度>=100就是下载完成了
.distinct()//去重复
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new ProgressObserver());
}
});
webview.loadUrl(webUrl);//加载需要显示的网页
}
//观察者
private class ProgressObserver implements Observer<Integer> {
@Override
public void onSubscribe(Disposable d) {
mDisposable = d;
}
@Override
public void onNext(Integer progress) {
mProgressBar.setProgress(progress);//设置进度
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
Toast.makeText(ProductWebActivity.this, "出错", Toast.LENGTH_SHORT).show();
}
@Override
public void onComplete() {
mProgressBar.setProgress(100);
Toast.makeText(ProductWebActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
}
}
//Web视图
private class HelloWebViewClient extends WebViewClient {
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); // 接受所有网站的证书
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String mUrl = "";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mUrl = request.getUrl().toString();
} else {
mUrl = request.toString();
}
if (mUrl.startsWith("http:") || mUrl.startsWith("https:")) {
view.loadUrl(mUrl);
return false;
} else {
return false;
}
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
bar.setVisibility(View.VISIBLE);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
bar.setVisibility(View.GONE);
}
}
@Override
protected void onDestroy() {
if (mDisposable != null) {
//取消监听
mDisposable.dispose();
}
unbindService(mConnection);
super.onDestroy();
}
}
3.service类
public class DownloadService extends Service {
private DownloadManager mDownloadManager;
private DownloadBinder mBinder = new DownloadBinder();
private LongSparseArray mApkPaths;
private boolean mIsRoot = false;
private DownloadFinishReceiver mReceiver;
private long downloadId;
private String apkname="";
@Override
public void onCreate() {
super.onCreate();
mDownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
mApkPaths = new LongSparseArray<>();
//注册下载完成的广播
mReceiver = new DownloadFinishReceiver();
registerReceiver(mReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onDestroy() {
unregisterReceiver(mReceiver);//取消注册广播接收者
super.onDestroy();
}
public class DownloadBinder extends Binder {
/**
* 下载
*
* @param apkUrl 下载的url
* @param apkName 下载的包名
*/
public long startDownload(String apkUrl, String apkName) {
apkname=apkName;
//点击下载
//删除原有的APK
IOUtils.clearApk(DownloadService.this, apkName);
//使用DownLoadManager来下载
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
//将文件下载到自己的Download文件夹下,必须是External的
//这是DownloadManager的限制
File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), apkName);
request.setDestinationUri(Uri.fromFile(file));
//添加请求 开始下载
downloadId = mDownloadManager.enqueue(request);
mReceiver.setDownLoadId(downloadId);
Log.d("DownloadBinder", file.getAbsolutePath());
mApkPaths.put(downloadId, file.getAbsolutePath());
return downloadId;
}
public void setInstallMode(boolean isRoot) {
mIsRoot = isRoot;
}
/**
* 获取进度信息
*
* @param downloadId 要获取下载的id
* @return 进度信息 max-100
*/
public int getProgress(long downloadId) {
//查询进度
DownloadManager.Query query = new DownloadManager.Query()
.setFilterById(downloadId);
Cursor cursor = null;
int progress = 0;
try {
cursor = mDownloadManager.query(query);//获得游标
if (cursor != null && cursor.moveToFirst()) {
//当前的下载量
int downloadSoFar = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
//文件总大小
int totalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
progress = (int) (downloadSoFar * 1.0f / totalBytes * 100);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return progress;
}
}
//下载完成的广播
public static class DownloadFinishReceiver extends BroadcastReceiver {
long downloadId;
public void setDownLoadId(long downLoadId){
this.downloadId = downLoadId;
}
@Override
public void onReceive(Context context, Intent intent) {
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
long did = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
//获取调用DownloadManager时保存的id
long myid = downloadId;
if (myid != did) {// 用于验证是否是下载的同一个apk
return;
}
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(did);
Cursor c = manager.query(query);
if (c.moveToFirst()) {
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
switch (status) {
case DownloadManager.STATUS_SUCCESSFUL:
File path;
int fileUriIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String fileUri = c.getString(fileUriIdx);
String fileName = null;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
if (fileUri != null) {
fileName = Uri.parse(fileUri).getPath();
}
} else {
//Android 7.0以上的方式:请求获取写入权限,这一步报错
int fileNameIdx=c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
fileName = c.getString(fileNameIdx);
}
path = new File(fileName);
if (!path.exists()) {
return;
}
Intent apkIntent = new Intent(Intent.ACTION_VIEW);
// 由于没有在Activity环境下启动Activity,设置下面的标签
apkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判读版本是否在7.0以上
//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
Uri apkUri =
FileProvider.getUriForFile(context, "com.new.installapk", path);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
apkIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
apkIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
}else{
apkIntent.setDataAndType(Uri.fromFile(path),
"application/vnd.android.package-archive");
}
context.startActivity(apkIntent);
break;
default:
manager.remove(did);
break;
}
}
}
}
}
4.IOUtils类
public class IOUtils {
public static void closeIO(Closeable... closeables) {
if (closeables != null) {
for (Closeable closeable : closeables) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 删除之前的apk
*
* @param apkName apk名字
* @return
*/
public static File clearApk(Context context, String apkName) {
File apkFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), apkName);
if (apkFile.exists()) {
apkFile.delete();
}
return apkFile;
}
}
5.清单文件
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.new.installapk"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
provider>
<service android:name=".filedownload.DownloadService" />
6.最后不要忘记添加相应权限,都是普通权限,可能会有遗漏
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
个人总结,有遗漏或不足的地方,希望给予建议。