Android 解决webview中下载第三方APP的问题

问题描述:
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" />

个人总结,有遗漏或不足的地方,希望给予建议。

你可能感兴趣的:(webview)