admin 管理员组文章数量: 887021
背景
在使用Android的webview嵌套页面,从而打包成APP,实现apk。但是,文件下载功能出现了问题,总是下载不了。
TBS
官网介绍:
腾讯浏览服务是致力于优化移动端webview体验的整套解决方案。该方案由SDK、手机QQ浏览器X5内核和X5云端服务组成,解决移动端webview使用过程中出现的一切问题,优化用户的浏览体验。同时,腾讯还将持续提供后续的更新和优化,为开发者提供最新最优秀的功能和服务。
集成
可查看官网快速接入
试试水
- 初始化 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);
}
}
- 在layout xml 添加 WebView
<com.tencent.smtt.sdk.WebView
android:id="@+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
- 使用腾讯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 去实现文件打开。
下载工具类
- 在 app - build.gradle - dependencies 添加:
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.4.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否则内核会继续处理
}
});
问题
- 文件路径出现拒绝访问
- 网络不可使用
- 手机横竖屏导致webview重新加载
解决
- 在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>
- 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浏览服务 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1726442899h961524.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论