admin 管理员组文章数量: 887021
- 前言
- 联系方式
- 背景
- SDK下载
- SDK集成
- 使用
- 代码实现
前言
由于是使用的腾讯浏览服务,所以这里大部分介绍的是官网的一些东西,不过自己会做一些复杂使用部分的实现,不至于像官网上介绍的笼统。
联系方式
这里用的是TBS腾讯浏览服务。
官网地址是 https://x5.tencent
微信公众号:腾讯浏览服务
论坛 http://bbs.mb.qq/forum-112-1.html ( 开发者反馈论坛 )
反馈 http://bbs.mb.qq/newthread?fid=112
背景
这里介绍一下x5内核相对Android系统内核的一些优势
- TBS(腾讯浏览服务)的优势
1) 速度快:相比系统webview的网页打开速度有30+%的提升;
2) 省流量:使用云端优化技术使流量节省20+%;
3) 更安全:安全问题可以在24小时内修复;
4) 更稳定:经过亿级用户的使用考验,CRASH率低于0.15%;
5) 兼容好:无系统内核的碎片化问题,更少的兼容性问题;
6) 体验优:支持夜间模式、适屏排版、字体设置等浏览增强功能;
7) 功能全:在Html5、ES6上有更完整支持;
8) 更强大:集成强大的视频播放器,支持视频格式远多于系统webview;
9) 视频和文件格式的支持x5内核多于系统内核;
10) 防劫持是x5内核的一大亮点
- 运行环境
1)手机ROM版本高于或等于2.2版本;
2)手机RAM大于500M,该RAM值通过手机 /proc/meminfo 文件的MemTotal动态获取
注:如果不满足上述条件,SDK会自动切换到系统WebView,SDK使用者不用关心该切换过程。
- SDK尺寸指标
1)SDK提供的JAR包约250K
SDK下载
我们肯定需要先下载sdk的jar包,然后才能使用该sdk的功能,也就是这里的x5内核。
腾讯浏览服务的sdk下载链接是:腾讯SDK下载页。这里我选择的是第三个(完整版+文件能力的Android SDK),当然选择第一个也可以,只不过不带有文件能力。第二个适用于快速接入TBS且常规使用WebView的开发者。因为我这里可能还涉及到与html网页的交互,为了方便,就没有选择第二个。因为第一个和第三个都只是单纯的将x5的内核替换系统的内核,其他没有太多的变化。像一些基本的设置也都和系统内核的是一样的,只不过x5的内核优势很明显。
SDK集成
sdk下载完成后,解压如下图所示:
里面有文件常见问题解答文档和文件方案接口介绍文档。因为是带有文件能力的sdk,所以有这些文档,文档也比较简单,写的很基础,这里不多介绍了。
这里我们用到的是jar包,我标注在图中了。将jar包添加到我们的项目中,就可以使用对应的功能了。
将jar包放到libs目录下:
然后记得添加jar包依赖,如下:
经过上面几个步骤之后,你的项目就集成了该sdk,可以使用x5的内核了。
使用
用到的所有原生WebView导入的类和接口都改导入 com.tencent.smtt.sdk 里面的类,类接口名对应。这样就相当于用x5的内核替换了系统的内核。
需要注意的是:
1)请不要在代码里使用下述写法
import android.*;
import android.webkit.*;
import android.webkit.WebStorage.*;
import android.*;
import android.http.*;
2)除了源码里需要把相关的包名和类名进行替换,布局xml里面的声明也需要替换,例如:
<com.tencent.smtt.sdk.WebView
android:id="@+id/forum_context"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="5dp"
android:paddingRight="5dp" />
当然,你要是不在布局里面声明,而是自己在代码中new出来,添加的x5内核,那么就不用管xml布局。这里推荐大家用这种方法来实现,会比在xml布局中声明更加的灵活方便。
替换不完全时,可能发生的问题是关于cookie的身份错误、类转换时的crash等。cookie问题产生的原理是:一段代码把cookie塞给了系统内核,另外一段代码尝试从x5的内核里读取cookie就失败了。类转换的错误产生的原理是:比如xml里指定的是系统的webview,java的代码里把它当作x5的webview使用。
然后大家自己原来已经用的系统的内核,再替换为x5的内核需要你仔细一点,别忘记替换包名,不然就会发生上面的错误。如果刚开始就使用的就是集成的x5内核,那么就不用太担心,不过选择导包的时候还是需要注意别用系统的内核,要使用腾讯提供的x5内核。
x5暂时不提供64位so文件,为了保证64位手机能正常加载x5内核,请参照如下链接修改相关配置https://x5.tencent/tbs/technical.html#/detail/sdk/1/34cf1488-7dc2-41ca-a77f-0014112bcab7
AndroidManifest.xml里加入权限声明:
<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" />
优化异常上报:
为了提高合作方的webview场景稳定性,及时发现并解决x5相关问题,当客户端发生crash等异常情况并上报给服务器时请务必带上x5内核相关信息。x5内核异常信息获取接口为:com.tencent.smtt.sdk.WebView.getCrashExtraMessage(context)。以bugly日志上报为例:
UserStrategy strategy = new UserStrategy(appContext);
strategy.setCrashHandleCallback(new CrashReport.CrashHandleCallback() {
public Map<String, String> onCrashHandleStart(int crashType, String errorType, String errorMessage, String errorStack) {
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
String x5CrashInfo = com.tencent.smtt.sdk.WebView.getCrashExtraMessage(appContext);
map.put("x5crashInfo", x5CrashInfo);
return map;
}
@Override
public byte[] onCrashHandleStart2GetExtraDatas(int crashType, String errorType, String errorMessage, String errorStack) {
try {
return "Extra data.".getBytes("UTF-8");
} catch (Exception e) {
return null;
}
}
});
CrashReport.initCrashReport(appContext, APPID, true, strategy);
适配修改:
1) App 首次就可以加载 x5 内核
App 在启动后(例如在 Application 的 onCreate 中)立刻调用 QbSdk 的预加载接口 initX5Environment ,可参考接入示例,第一个参数传入 context,第二个参数传入 callback,不需要 callback 的可以传入 null,initX5Environment 内部会创建一个线程向后台查询当前可用内核版本号,这个函数内是异步执行所以不会阻塞 App 主线程,这个函数内是轻量级执行所以对 App 启动性能没有影响,当 App 后续创建 webview 时就可以首次加载 x5 内核了
package com.example.administrator.firststeppro.application;
import android.app.Application;
import com.tencent.smtt.sdk.QbSdk;
/**
* 自定义MyApplication类继承Application
* 并重写onCreate方法完成一些初始化加载操作
*/
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
preinitX5WebCore();
}
/**
* 预加载x5内核
*/
private void preinitX5WebCore() {
if (!QbSdk.isTbsCoreInited()){
// 这个函数内是异步执行所以不会阻塞 App 主线程,这个函数内是轻量级执行所以对 App 启动性能没有影响
QbSdk.initX5Environment(this, null);
}
}
}
别忘记在清单配置文件中声明该Application:
<application
android:name=".application.MyApplication"
android:largeHeap="true"
android:allowBackup="true"
android:icon="@mipmap/app_icon"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".activity.SplashActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activity.MainActivity"
android:windowSoftInputMode="stateHidden|adjustResize"
android:configChanges="screenSize|orientation|keyboardHidden"
android:screenOrientation="portrait"/>
</application>
设置name属性即可。
2) 目前,由于SDK WebView所提供的WebView类,是对系统WebView的聚合包装,所以:获取系统内核的WebView或者 x5内核的WebView的宽高
android.webkit.WebView webView = new android.webkit.WebView(this);
int width = webView.getWidth();
需要采用下面的方式进行
com.tencent.smtt.sdk.WebView webView = new com.tencent.smtt.sdk.WebView(this);
int width = webView.getView().getWidth();
调整cookie的使用:
com.tencent.smtt.sdk.CookieManager和com.tencent.smtt.sdk.CookieSyncManager的相关接口的调用,在接入SDK后,需要放到创建X5的WebView之后(也就是X5内核加载完成)进行;否则,cookie的相关操作只能影响系统内核。
兼容视频播放:
1)享受页面视频的完整播放体验需要做如下声明:
页面的Activity需要声明android:configChanges="orientation|screenSize|keyboardHidden"
2)视频为了避免闪屏和透明问题,需要如下设置
a)网页中的视频,上屏幕的时候,可能出现闪烁的情况,需要如下设置:Activity在onCreate时需要设置:
getWindow().setFormat(PixelFormat.TRANSLUCENT);(这个对宿主没什么影响,建议声明)
b)在非硬绘手机和声明需要controller的网页上,视频切换全屏和全屏切换回页面内会出现视频窗口透明问题,需要如下设置
声明当前<item name="android:windowIsTranslucent">false为不透明。
特别说明:这个视各app情况所需,不强制需求,如果声明了,对体验更有利
c)以下接口禁止(直接或反射)调用,避免视频画面无法显示:
webview.setLayerType()
webview.setDrawingCacheEnabled(true);
输入法设置
避免输入法界面弹出后遮挡输入光标的问题
方法一:在AndroidManifest.xml中设置
android:windowSoftInputMode="stateHidden|adjustResize"
方法二:在代码中动态设置:
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
app 自定义 UA 的说明
如果 app 需要自定义 UA,建议采取在 SDK 默认UA 后追加 app UA 的方式示例:
webSetting.setUserAgentString(webSetting.getUserAgentString() + APP_NAME_UA);
其中 APP_NAME_UA 是 app 自定义 UA
由于我们提供的 TBS jar 已经混淆过,所以 App 混淆时可以不再混淆我们的 TBS jar
代码实现
下面我给出我自己的项目中使用x5内核加载的网页,顺便也测试一下是否成功的加载了x5内核。
package com.example.administrator.firststeppro.fragment;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.Uri;
import android.os.Build;
import android.os.Bundle;
import android.app.Fragment;
import android.support.annotation.NonNull;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.tencent.smtt.export.external.extension.interfaces.IX5WebViewExtension;
import com.tencent.smtt.export.external.interfaces.SslError;
import com.tencent.smtt.export.external.interfaces.SslErrorHandler;
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.export.external.interfaces.WebResourceResponse;
import com.tencent.smtt.sdk.WebSettings;
import com.tencent.smtt.sdk.WebView;
import com.tencent.smtt.sdk.WebViewClient;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.example.administrator.firststeppro.R;
import com.example.administrator.firststeppro.base.BaseFragment;
import com.example.administrator.firststeppro.orm.AndroidToJs;
import com.example.administrator.firststeppro.utils.LogUtil;
import com.example.administrator.firststeppro.utils.ToastUtil;
import butterknife.BindView;
import butterknife.ButterKnife;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
/**
* A simple {@link Fragment} subclass.
* 展示个人的csdn博客主页
*/
public class PageOneFragment extends BaseFragment {
@BindView(R.id.linear_page1_webview)LinearLayout linear_page1_webiew;// webView的父布局
@BindView(R.id.material_progress_bar)MaterialProgressBar material_progress_bar;
private boolean isPrepared = false;// 是否已经准备好要加载数据
private static WebView webView = null;
private WebSettings webSettings;// WebSettings对象
public PageOneFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_page_one, container, false);
getActivity().getWindow().setFormat(PixelFormat.TRANSLUCENT);// 加载网页视频避免闪屏和透明
ButterKnife.bind(this, view);
isPrepared = true;
setLazyLoad();
requestPermission();// 申请权限
return view;
}
@SuppressLint("NewApi")
private void requestPermission() {
if(getContext().checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
// 进行授权
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 1:
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
}else {
ToastUtil.showToast(getContext(), "读写文件权限未开启");
}
break;
}
}
/*
@OnClick({R.id.btn_callJs})
public void doClick(View view){
switch (view.getId()){
case R.id.btn_callJs:// Android调用js方法
webView.post(new Runnable() {
@Override
public void run() {
// 调用index.html文件的callJS()方法
// webView.loadUrl("javascript:callJS()");
// 不同版本兼容4.4以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("index:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
// 此处为js返回的结果
LogUtil.e("TAG","" + value);
}
});
}else {
webView.loadUrl("index:callJS()");
}
}
});
break;
}
}
*/
@Override
protected void setLazyLoad() {
super.setLazyLoad();
if (isVisible && isPrepared){
LogUtil.e("TAG","loadDataPage1");
if (webView == null){
openWebViewPage();
}
}
}
/**
* 打开webView页面
*/
private void openWebViewPage() {
// 创建WebView对象添加到布局中
webView = new WebView(getContext().getApplicationContext());
IX5WebViewExtension ix5WebViewExtension = webView.getX5WebViewExtension();
ix5WebViewExtension.setScrollBarFadingEnabled(false);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
webView.setLayoutParams(params);
linear_page1_webiew.addView(webView);
// 清除网页访问留下的缓存
// 由于内核缓存是全局的因此这个方法不仅仅针对webView而是针对整个应用程序
webView.clearCache(true);
// 清除当前webView的访问历史记录
webView.clearHistory();
// 这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
webView.clearFormData();
// https://www.panda.tv/
// https://blog.csdn/csdnzouqi
// http://soft.imtt.qq/browser/tes/feedback.html
// String url = "file:android_asset/index.html";// 加载主页面的url
String url = "http://soft.imtt.qq/browser/tes/feedback.html";// 邹奇的博客主页
// 获取WebSettings对象
webSettings = webView.getSettings();
// 特别注意:5.1以上默认禁止了https和http混用。下面代码是开启
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {// 21
}
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);// 不使用缓存,直接用网络加载
webSettings.setJavaScriptEnabled(true);// webView支持javascript
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);// 告诉js可以自动打开window
// 两者一起使用,可以让html页面加载显示适应手机的屏幕大小
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
// webSettings.setAllowFileAccess(true); //设置可以访问文件
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
// 即允许在 File 域下执行任意 JavaScript 代码
// webSettings.setAllowFileAccess(true);// 设置是否允许 WebView 使用 File 协议
// 禁止 file 协议加载 JavaScript
// if (url.startsWith("file://")){
// webSettings.setJavaScriptEnabled(false);
// }else {
// webSettings.setJavaScriptEnabled(true);
// }
webSettings.setSavePassword(false);// 关闭密码保存提醒;该方法在以后的版本中该方法将不被支持
webSettings.setDomStorageEnabled(true);// 设置支持DOM storage API
// 通过addJavascriptInterface()将Java对象映射到JS对象
webView.addJavascriptInterface(new AndroidToJs(getContext()), "androidObj");
// 加载手机本地的html
// webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
// 加载html页面的一小段内容 ('#','%', '\', '?' 分别用 %23, %25, %27, %3f 替换)
// webView.loadData("显示内容", "text/html", "utf-8");
//设置WebViewClient类
webView.setWebViewClient(new WebViewClient(){
// 设置不用系统浏览器打开,直接显示在当前 webview
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 如果不是http或者https开头的url,那么使用手机自带的浏览器打开
if (!url.startsWith("http://") && !url.startsWith("https://")){
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}catch (Exception e){
e.printStackTrace();
return true;
}
}
view.loadUrl(url);
return false;
// return super.shouldOverrideUrlLoading(view, url);
}
@Override
public void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, SslError sslError) {
super.onReceivedSslError(webView, sslErrorHandler, sslError);
}
});
//设置WebChromeClient类
webView.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int newProgress) {
// 加载完成,隐藏进度条
if (newProgress == 100){
material_progress_bar.setVisibility(View.GONE);
}else {
material_progress_bar.setVisibility(View.VISIBLE);
}
}
// // 拦截js的警告框
//
// @Override
// public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
//
ToastUtil.showToast(getContext(), "拦截js alert");
// new AlertDialog.Builder(getContext())
// .setTitle("弹框")
// .setMessage(message)
// .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// result.confirm();
// }
// })
// .setCancelable(false)
// .create().show();
//
// return true;
return super.onJsAlert(view, url, message, result);
// }
//
// // 拦截js的确认框
// @Override
// public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
// return super.onJsConfirm(view, url, message, result);
// }
//
// // 拦截输入框
// @Override
// public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
//
ToastUtil.showToast(getContext(), ""+ message);
// // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
// // 假定传入进来的 url = "js://android?arg1=1&arg2=2"(同时也是约定好的需要拦截的)
// Uri uri = Uri.parse(message);
//
// if (uri.getScheme().equals("js")){
//
// if (uri.getAuthority().equals("android")){
// ToastUtil.showToast(getContext(),"Android端拦截成功!");
//
// // 可以在协议上带有参数并传递到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
//
// // 这里可以执行js所需要调用的逻辑
//
// // 返回值
// result.confirm("Android端返回给js的内容");
// }
//
// }
//
// return true;
return super.onJsPrompt(view, url, message, defaultValue, result);
// }
});
// 加载apk包中的html页面
webView.loadUrl(url);
}
@Override
public void onDestroyView() {
super.onDestroyView();
isPrepared = false;// 视图销毁的时候恢复数据加载状态
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden){
LogUtil.e("TAG", "onHiddenChanged1");
}
}
// 先让 WebView 加载null内容,然后移除 WebView,再销毁 WebView,最后置空
private void clearWebView() {
if (webView != null){
webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
webView.clearHistory();
webView.clearCache(true);// 清除缓存
((ViewGroup)webView.getParent()).removeView(webView);
webView.destroy();
webView = null;
}
}
/**
* fragment的点击事件,参数由当前fragment所依附的activity传过来
* @param keyCode
* @param event
* @param context
*/
public static void onKeyDown(int keyCode, KeyEvent event, Context context) {
if (webView != null){
if (webView.canGoBack()){
webView.goBack();
}else {
view_toast_exit = LayoutInflater.from(context).inflate(R.layout.view_toast_exit, null);
exitApp(2000, context);
}
}
}
private static Toast toast = null;// 创建Toast对象
private static long firstTime;// 记录点击返回时第一次的时间毫秒值
private static View view_toast_exit;// 吐丝,退出应用的view
/**
* 退出应用
* @param timeInterval 设置第二次点击退出的时间间隔
* @param context
*/
private static void exitApp(long timeInterval, Context context) {
if(System.currentTimeMillis() - firstTime >= timeInterval){
if (view_toast_exit != null){
toast = new Toast(context);
toast.setView(view_toast_exit);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.setDuration(Toast.LENGTH_SHORT);
toast.show();
}else {
ToastUtil.showToast(context, "再按一次退出程序");
}
// ToastUtil.showToast(this, "再按一次退出程序");
firstTime = System.currentTimeMillis();
}else {
if (toast != null){
toast.cancel();
}
// finish();// 销毁当前activity
System.exit(0);// 完全退出应用
}
}
}
那么怎么判断是否加载了x5内核呢?
webview的getX5WebViewExtension()返回非null表示已加载了x5内核webview
您的app打开网页http://soft.imtt.qq/browser/tes/feedback.html,显示000000表示加载的是系统内核,显示大于零的数字表示加载了x5内核(该数字是x5内核版本号)
上面我加载的url就是http://soft.imtt.qq/browser/tes/feedback.html,让我们运行一下看看效果:
可以看到确实是成功的加载了x5的内核。
A little bit of progress every day!Come on!
版权声明:本文标题:Android中使用x5内核加载网页的实现 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1726782328h1027886.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论