深入探究Android的WebView下载网络文件的盗链问题

我们知道在Android中要使用WebView去访问网页需要如下设置:

webView.setWebViewClient(new WebViewClient());

否则当我们loadUrl的时候是自动调用系统默认浏览器进行访问。
废话不多少了,先上布局,布局文件非常简单,一个EditText和一个Button被填充在LinearLayout中其余部分全部留给WebView


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.y.webviewdownloadtest.MainActivity">

    <LinearLayout
        android:id="@+id/ll"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/et_url"
            android:inputType="textUri"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="10"
            android:text="http://www.imust.cn/info/1062/5273.htm" />

        <Button
            android:id="@+id/bt_go"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:text="Go" />
    LinearLayout>

    <WebView
        android:id="@+id/wv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/ll" />
RelativeLayout>

WebView默认是不支持下载操作的,我们需要对webView对象设置一个下载监听器
就像设置Button的单机监听器一样,非常简单。

webView.setDownloadListener(new MyWebViewDownLoadListener());

这里需要一个实现了DownloadListener接口的类的对象用于监听webView中产生的下载事件,我们新建一个内部类

//内部类
    private class MyWebViewDownLoadListener implements DownloadListener {
        @Override
        public void onDownloadStart(String urlString, String userAgent, String contentDisposition, String mimeType, long contentLength) {
            new DownloaderTask().execute(urlString, userAgent, contentDisposition, mimeType, contentLength + "");
        }
    }

新建一个线程来完成Android的网络访问操作

//内部类
    private class DownloaderTask extends AsyncTask {

        private String doDownload(String... strings) {
            Log.d(TAG, "urlString:" + strings[0] + "\nuserAgent: " + strings[1] + "\ncontentDisposition: " + strings[2] + "\nmimeType: " + strings[3]);
            String fileName = strings[2].substring(strings[2].lastIndexOf("=") + 1);
            long s = Long.parseLong(strings[4].trim());
            try {
                URL url = new URL(strings[0]);
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setConnectTimeout(8000);
                httpURLConnection.setReadTimeout(8000);
                httpURLConnection.setRequestMethod("GET");
                //设置http请求头一个也不能少
                httpURLConnection.setRequestProperty("attachment", strings[2].substring(strings[2].lastIndexOf("; ") + 2));
                httpURLConnection.setRequestProperty("Referer", editText.getText().toString().trim());
                httpURLConnection.setRequestProperty("User-Agent", strings[1]);

                InputStream inputStream = httpURLConnection.getInputStream();
                FileOutputStream fos = MainActivity.this.openFileOutput(fileName, Context.MODE_PRIVATE);
                byte[] b = new byte[2048];
                int j = 0;
                long now = 0;
                while ((j = inputStream.read(b)) != -1) {
                    publishProgress((int)(100*now/s));//publishProgress();//调用此方法更新Progress
                    now += j;
                    fos.write(b, 0, j);
                }
                fos.flush();
                fos.close();

            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return fileName;
        }

        @Override
        protected void onPreExecute() {
            progressDialog.show();
        }

        @Override
        protected String doInBackground(String... strings) {
            String fileName = doDownload(strings);
            return fileName;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            progressDialog.setProgress(values[0]);
        }

        @Override
        protected void onPostExecute(String s) {
            progressDialog.dismiss();
        }
    }

这里的doInBackground方法是一个抽象方法需要强制重写,doInBackground中的代码将不会在UI线程中执行,因此可以进行网络访问的操作。
另外,我们要达到目的还需要重写onPreExecute(),onProgressUpdate(Integer… values),onPostExecute(String s),需求不同后面两个方法的传入参数也可能不同,后面会详细介绍这几个回掉方法的具体用法。
onPreExecute()在异步任务开始的时候执行,因此,常在这里进行一些初始化操作,例如本程序在onPreExecute()中显示一个ProgressDialog,提示用户文件下载的进度;onProgressUpdate(Integer… values)方法是更新ProgressDialog最重要的一个环节,每当我们调用publishProgress(int i)方法时将执行onProgressUpdate(Integer… values)并把publishProgress(int i)中传入的int类型i传递给Integer中的第一个即values[0],因此我们可以在doInBackground中当下载数据发生变化时调用publishProgress(int i),并将当前下载量传递给i因此时不是在UI线程无法更新UI,也就没有办法控制ProgressDialog,但是此时的i已经通过回掉方法传递给了onProgressUpdate,所以需要在onProgressUpdate方法中取出values[0];最后异步任务执行到onPostExecute(String s)阶段,就意味着异步任务已经完成了,String类型的参数s就是我们在doInBackground子线程中返回的数据,在这里需要取消掉ProgressDialog。

这里需要重点留意的就是doDown方法,我们的下载任务就是通过doDown方法来完成的,如果有盗链问题也必然发生在这里
当代码执行到DownloadListener中说明已经发生了下载事件,此时DownloadListener将给我们五个关键信息

public abstract void  onDownloadStart (String url, String userAgent, String contentDisposition, String mimetype, long contentLength) 


Notify the host application that a file should be downloaded


Parameters

url                 The full url to the content that should be downloaded 

userAgent           the user agent to be used for the download. 

contentDisposition  Content-disposition http header, if present. 

mimetype            The mimetype of the content reported by the server 

contentLength       The file size reported by the server  

官方文档给出了非常详细的解释,第一个参数是当前下载链接URL的String意味着我们通过访问这个链接在当前访问站点没有进行防盗链保护的情况下就可以很轻松的获取到想要下载的文件,可是如果被访问的站点有盗链防护,那接下来的参数就至关重要;userAgent就是HTTP请求头中的User-Agent,存储的是当前用户信息;contentDisposition请求的文件信息;mimetype请求的文件类型;contentLength请求文件的大小;另外还需要添加一个Referer参数,这个参数标志着用户的出处,就类似于一个人他要找工作,别人一般情况下都会问他毕业于哪个学校,而Referer参数携带的信息就相当于是某某某大学毕业。

在HTTP请求之前把这几个参数添加进去

private String doDownload(String... strings) {
            Log.d(TAG, "urlString:" + strings[0] + "\nuserAgent: " + strings[1] + "\ncontentDisposition: " + strings[2] + "\nmimeType: " + strings[3]);
            String fileName = strings[2].substring(strings[2].lastIndexOf("=") + 1);
            long s = Long.parseLong(strings[4].trim());
            try {
                URL url = new URL(strings[0]);
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setConnectTimeout(8000);
                httpURLConnection.setReadTimeout(8000);
                httpURLConnection.setRequestMethod("GET");
                //设置http请求头,一个也不能少
                httpURLConnection.setRequestProperty("attachment", strings[2].substring(strings[2].lastIndexOf("; ") + 2));
                httpURLConnection.setRequestProperty("Referer", editText.getText().toString().trim());
                httpURLConnection.setRequestProperty("User-Agent", strings[1]);

                InputStream inputStream = httpURLConnection.getInputStream();
                FileOutputStream fos = MainActivity.this.openFileOutput(fileName, Context.MODE_PRIVATE);
                byte[] b = new byte[2048];
                int j = 0;
                long now = 0;
                while ((j = inputStream.read(b)) != -1) {
                    publishProgress((int)(100*now/s));//publishProgress();//调用此方法更新Progress
                    now += j;
                    fos.write(b, 0, j);
                }
                fos.flush();
                fos.close();

            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return fileName;
        }

最后在MainActivity的onCreate方法中加入View的初始化代码,以及webView的初始设置,这里需要重写WebViewClient的onPageFinished方法,并加入editText的setText以保证点击文件产生下载事件的时候获取到的Referer是正确的。

        setContentView(R.layout.activity_main);
        editText = (EditText) findViewById(R.id.et_url);
        webView = (WebView) findViewById(R.id.wv);
        progressDialog = new ProgressDialog(this);
        progressDialog.setMax(100);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setMessage("下载中,请稍后。。。。");

        webView.setWebChromeClient(new WebChromeClient());
        webView.setWebViewClient(new WebViewClient(){
            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                editText.setText(webView.getUrl());
            }
        });
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setDownloadListener(new MyWebViewDownLoadListener());
        findViewById(R.id.bt_go).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                webView.loadUrl(editText.getText().toString().trim());
            }
        });

最后附上项目代码:
点我下载

你可能感兴趣的:(Android,android,网络,webview,浏览器,布局)