admin 管理员组

文章数量: 887021

背景

在使用Android的webview嵌套页面,从而打包成APP,实现apk。但是,文件下载功能出现了问题,总是下载不了。

TBS

官网介绍:
腾讯浏览服务是致力于优化移动端webview体验的整套解决方案。该方案由SDK、手机QQ浏览器X5内核和X5云端服务组成,解决移动端webview使用过程中出现的一切问题,优化用户的浏览体验。同时,腾讯还将持续提供后续的更新和优化,为开发者提供最新最优秀的功能和服务。

集成

可查看官网快速接入

试试水

  1. 初始化 x5,创建 MyAplication 类:
import android.app.Application;
import android.util.Log;

import com.tencent.smtt.sdk.QbSdk;

public class MyAplication extends Application {
    @Override
    public void onCreate() {
// TODO Auto-generated method stub
        super.onCreate();
        initX5();
    }
    /**
     * 初始化X5
     */
    private void initX5() {
//x5內核初始化回调
        QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() {
            @Override
            public void onViewInitFinished(boolean arg0) {
// TODO Auto-generated method stub
//x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。
                Log.d("app", " onViewInitFinished is " + arg0);
            }
            @Override
            public void onCoreInitFinished() {
// TODO Auto-generated method stub
            }
        };
//x5内核初始化接口
        QbSdk.initX5Environment(getApplicationContext(), cb);
    }
}
  1. 在layout xml 添加 WebView
<com.tencent.smtt.sdk.WebView
        android:id="@+id/webview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
  1. 使用腾讯tbs webview 加载url,在 MainActivity 中添加
WebView mWebView = (com.tencent.smtt.sdk.WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);// 支持js
mWebView.setWebViewClient(new WebViewClient());//防止加载网页时调起系统浏览器
mWebView.loadUrl("http://192.168.16.87/wechat/login.html?t=" + new Date().getTime());

注意: webview 使用的包为: com.tencent.smtt.sdk.*

结果

tbs 加载网页速度加快了不少,但是依旧下载不了。23333333

再次探索

通过查看 TBS Api 发现 shouldOverrideUrlLoading 方法可监听 url 的变化。思路:
通过 文件的url,通知Android 去下载文件,然后使用QbSdk.openFileReader 去实现文件打开。

下载工具类
  1. 在 app - build.gradle - dependencies 添加:
 compile 'com.squareup.okhttp3:okhttp:3.4.1'
 compile 'com.squareup.okhttp3:okhttp-urlconnection:3.4.1'
  1. 添加 DownloadUtil 类:
package utils;



import android.os.Handler;
import android.os.Message;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * Created by yufs on 2017/8/16.
 */

public class DownloadUtil {
    public static final int DOWNLOAD_FAIL=0;
    public static final int DOWNLOAD_PROGRESS=1;
    public static final int DOWNLOAD_SUCCESS=2;
    private static DownloadUtil downloadUtil;
    private final OkHttpClient okHttpClient;
    public static DownloadUtil getInstance() {
        if (downloadUtil == null) {
            downloadUtil = new DownloadUtil();
        }
        return downloadUtil;
    }

    private DownloadUtil() {
        okHttpClient = new OkHttpClient();
    }

    /**
     *
     */
    public void download(final String url,final String saveDir,final OnDownloadListener listener){
        this.listener=listener;
        Request request=new Request.Builder().url(url).build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Message message=Message.obtain();
                message.what=DOWNLOAD_FAIL;
                mHandler.sendMessage(message);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                InputStream is=null;
                byte[] buf=new byte[2048];
                int len=0;
                FileOutputStream fos=null;
                //储存下载文件的目录
                String savePath=isExistDir(saveDir);
                try{
                    is=response.body().byteStream();
                    long total=response.body().contentLength();
                    File file=new File(savePath,getNameFromUrl(url));
                    fos=new FileOutputStream(file);
                    long sum=0;
                    while((len = is.read(buf))!=-1){
                        fos.write(buf,0,len);
                        sum+=len;
                        int progress=(int)(sum*1.0f/total*100);
                        //下载中
                        Message message=Message.obtain();
                        message.what=DOWNLOAD_PROGRESS;
                        message.obj=progress;
                        mHandler.sendMessage(message);

                    }
                    fos.flush();
                    //下载完成
                    Message message=Message.obtain();
                    message.what=DOWNLOAD_SUCCESS;
                    message.obj=file.getAbsolutePath();
                    mHandler.sendMessage(message);
                }catch (Exception e){
                    Log.d("down",e.toString());
                    Message message=Message.obtain();
                    message.what=DOWNLOAD_FAIL;
                    mHandler.sendMessage(message);
                }finally{
                    try{
                        if(is!=null)
                            is.close();
                    }catch (IOException e){

                    }
                    try {
                        if(fos!=null){
                            fos.close();
                        }
                    }catch (IOException e){

                    }
                }
            }
        });
    }

    private String getNameFromUrl(String url) {
        return url.substring(url.lastIndexOf("/")+1);
    }


    private String isExistDir(String saveDir) throws IOException {
        File downloadFile=new File(saveDir);
        if(!downloadFile.mkdirs()){
            downloadFile.createNewFile();
        }
        String savePath=downloadFile.getAbsolutePath();
        return savePath;
    }




    private Handler mHandler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case DOWNLOAD_PROGRESS:
                    listener.onDownloading((Integer) msg.obj);
                    break;
                case DOWNLOAD_FAIL:
                    listener.onDownloadFailed();
                    break;
                case DOWNLOAD_SUCCESS:
                    listener.onDownloadSuccess((String) msg.obj);
                    break;
            }
        }
    };


    OnDownloadListener listener;
    public interface OnDownloadListener{
        /**
         * 下载成功
         */
        void onDownloadSuccess(String path);
        /**
         * 下载进度
         * @param progress
         */
        void onDownloading(int progress);
        /**
         * 下载失败
         */
        void onDownloadFailed();
    }
}

webview 查看文件

    final Context ctx = this;
     mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                Log.d("app", " 网址 is " + url);
                int i = url.indexOf("file");
                if (i != -1) {
                    Log.d("app", " 网址 is " + url);


                    progressDialog.show();

                    /**
                     *  下载文件
                     */
                    DownloadUtil.getInstance().download(url,  ctx.getExternalCacheDir().getPath(), new DownloadUtil.OnDownloadListener() {
                        @Override
                        public void onDownloadSuccess(final String path) {

                            Log.d("app", " 下载成功 " + path);

                       
                            /**
                             *  打开文件
                             */
                            HashMap<String, String> params = new HashMap<String, String>();
                            params.put("style", "1");
                            params.put("local", "true");
                            QbSdk.openFileReader(ctx,path, params, new ValueCallback<String>() {
                                @Override
                                public void onReceiveValue(String s) {

                                }
                            });


                        }

                        @Override
                        public void onDownloading(int progress) {
                            Log.d("app","已下载"+progress+"%");
                        }

                        @Override
                        public void onDownloadFailed() {
                            Log.d("app", " 下载失败  ");
                        }
                    });
                    return  true;
                }

                //这里可以对特殊scheme进行拦截处理
                return false;//要返回true否则内核会继续处理
            }
        });

问题

  1. 文件路径出现拒绝访问
  2. 网络不可使用
  3. 手机横竖屏导致webview重新加载

解决

  1. 在res 创建xml文件夹:
    创建 x5webview_file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="sdcard" path="."/>
    <external-path name="path" path="Android/data/com.package.ride/path"/>
</paths>

创建 network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
  1. AndroidManifest.xml 配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android/apk/res/android"
    package="com.example.mywebapplication">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


    <!--   网络   android:requestLegacyExternalStorage...  android:networkSecurityConfig...-->
    <application
        android:requestLegacyExternalStorage="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/AppTheme">
        <!--   横竖屏重新加载  android:configChanges... -->
        <activity

          android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
<!--       文件路径 问题   -->
        <provider
            android:name="com.tencent.smtt.utils.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/x5webview_file_paths" />
        </provider>
        <service
            android:name="com.tencent.smtt.export.external.DexClassLoaderProviderService"
            android:label="dexopt"
            android:process=":dexopt" >
        </service>
    </application>


</manifest>

源码

再次探索

监听 TBS setDownloadListener。前端使用下载方式,这种方式,会调用手机浏览器下载文件。

  private class FileDownLoadListener implements DownloadListener {
        @Override
        public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
            Uri uri = Uri.parse(url);
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            startActivity(intent);
        }
    }
    
 mWebView.setDownloadListener(new FileDownLoadListener());

 mWebView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String s1, String s2, String s3, long l) {
                Uri uri = Uri.parse(url);
                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                startActivity(intent);
            }
        });

webview 选择文件

  private ValueCallback<Uri> uploadFile;
    private ValueCallback<Uri[]> uploadFiles;

   mWebView.setWebChromeClient(new WebChromeClient() {
            // For Android 3.0+
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
                Log.i("test 打开文件", "openFileChooser 1");
                MainActivity.this.uploadFile = uploadFile;
                openFileChooseProcess();
            }

            // For Android < 3.0
            public void openFileChooser(ValueCallback<Uri> uploadMsgs) {
                Log.i("test 打开文件", "openFileChooser 2");
                MainActivity.this.uploadFile = uploadFile;
                openFileChooseProcess();
            }

            // For Android  > 4.1.1
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
                Log.i("test 打开文件", "openFileChooser 3");
                MainActivity.this.uploadFile = uploadFile;
                openFileChooseProcess();
            }

            // For Android  >= 5.0
            public boolean onShowFileChooser(com.tencent.smtt.sdk.WebView webView,
                                             ValueCallback<Uri[]> filePathCallback,
                                             WebChromeClient.FileChooserParams fileChooserParams) {
                Log.i("test 打开文件", "openFileChooser 4:" + filePathCallback.toString());
                MainActivity.this.uploadFiles = filePathCallback;
                openFileChooseProcess();
                return true;
            }

        });
   /**
     *  打开文件选择
     */
    private void openFileChooseProcess() {

        Log.d("openFileChooseProcess","openFileChooseProcess ============");


//        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
//        i.addCategory(Intent.CATEGORY_OPENABLE);
//        i.setType("image/*");
//        startActivityForResult(Intent.createChooser(i, "test"), 0);

        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("image/*");
        startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE);

    }
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
        if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadFiles == null)
            return;
        Uri[] results = null;
        if (resultCode == Activity.RESULT_OK) {
            if (intent != null) {
                String dataString = intent.getDataString();
                ClipData clipData = intent.getClipData();
                if (clipData != null) {
                    results = new Uri[clipData.getItemCount()];
                    for (int i = 0; i < clipData.getItemCount(); i++) {
                        ClipData.Item item = clipData.getItemAt(i);
                        results[i] = item.getUri();
                    }
                }
                if (dataString != null)
                    results = new Uri[]{Uri.parse(dataString)};
            }
        }

        uploadFiles.onReceiveValue(results);
        uploadFiles = null;
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == FILE_CHOOSER_RESULT_CODE) {
            if (null == uploadFile && null == uploadFiles) return;
            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
            if (uploadFiles != null) {
                Log.d("onActivityResult", "uploadFiles");
                onActivityResultAboveL(requestCode, resultCode, data);
            } else if (uploadFile != null) {
                Log.d("onActivityResult", "uploadFile");
                uploadFile.onReceiveValue(result);
                uploadFile = null;
            }
        }
        }

本文标签: 腾讯 Android TBS