WebViewandroid

Android接入腾讯X5内核以及相关问题以及WebView相关

2017-04-18  本文已影响2077人  黄海佳
一、android WebView 替换方案

1.腾讯X5(推荐)X5内核下载地址
2.Crosswalk(包会打10--20mb
可能导致第三方APP无法开启X5内核的情况)

二、TBS(腾讯浏览服务)的优势
三、腾讯X5内核的使用
1 首先我们按照官方文档集成 SDK,下载jar包,以及配置权限! 在application里面进行初始化。

具体例子看官方demo,已经很详细了
腾讯 X5官网 http://x5.tencent.com/tbs/

2 MainActivity
 @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set layout
    getWindow().setFormat(PixelFormat.TRANSLUCENT);
    setContentView(R.layout.activity_main);
    mWebView = (com.tencent.smtt.sdk.WebView) findViewById(R.id.forum_context);
    mWebView.getSettings().setJavaScriptEnabled(true);// 支持js
    mWebView.getSettings().setUseWideViewPort(true); //自适应屏幕
    mWebView.loadUrl("http://res.ky-express.com/h5/video/72.html");
}
3 activity_main
<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"/>
四、一些注意的问题
1 多个.So库的问题

要把对应的so包导入。如果你的项目当中也用到了其他的.so库,这时你不仅要分包导入。so库,同时还要在gradle里面进行配置!

ndk {
        abiFilters"armeabi","armeabi-v7a","x86","mips"
    }
2 关于首期启动加载X5内核会出现过慢的问题

可以考虑用线程加载或者服务加载

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();
    initX5();
    preinitX5WebCore();
}

private void initX5() {
    QbSdk.initX5Environment(getApplicationContext(), QbSdk.WebviewInitType.FIRSTUSE_AND_PRELOAD, cb);
    Log.d("gggbbb","预加载中...");
}

QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() {

    @Override
    public void onViewInitFinished(boolean arg0) {
        // TODO Auto-generated method stub
        Log.e("0912", " onViewInitFinished is " + arg0);
    }

    @Override
    public void onCoreInitFinished() {
        // TODO Auto-generated method stub

    }
};
 private void preinitX5WebCore() {

    if(!QbSdk.isTbsCoreInited()) {

        // preinit只需要调用一次,如果已经完成了初始化,那么就直接构造view

        QbSdk.preInit(MainActivity.this, null);// 设置X5初始化完成的回调接口

    }
}

Application代码

@Override
public void onCreate() {
    super.onCreate();
    initX5();
}
private void initX5() {
    Intent intent = new Intent(this, PreLoadX5Service.class);
    startService(intent);
}
3 我们会发现集成X5后,项目编译变慢了,可以在build.config里面加上下面这段代码试试
dexOptions {
    javaMaxHeapSize "4g"
    preDexLibraries = false
}
4 视频全屏的时候有个qq浏览器的推广的去掉

设置布局改变的监听:
getWindow().getDecorView().addOnLayoutChangeListener()
监听里面通过 getWindow().getDecorView() 的 findViewsWithText() 方法可以拿到显示推广文案的 TextView,把拿到的 view 设置为 GONE 状态就可以去掉了。

五、webview与x5的测试对比

1、一个链接的第一次的加载webview会比x5速度快一点。

2、如果重复点击一个链接,来回切换,只要次数足够多,X5速度会比webview快。

3、如果点击不同链接,并且次数不是很多,webview会比x5速度快。


另外补充一下webview的基本常识

关于webview的加载
//打开本包内asset目录下的index.html文件
 
wView.loadUrl(" file:///android_asset/index.html ");   
 
//打开本地sd卡内的index.html文件
 
wView.loadUrl("content://com.android.htmlfileprovider/sdcard/index.html");
 
//打开指定URL的html文件
 
wView.loadUrl(" http://m.oschina.net");

关于js 调用 native

有三种方式

mWebView.getSettings().setJavaScriptEnabled(true);

这个函数会有一个警告,因为在特定的版本之下会有非常危险的漏洞,设置完这个属性之后,Native 需要定义一个类:

public class JSObject {
    private Context mContext;
    public JSObject(Context context) {
        mContext = context;
    }

    @JavascriptInterface
    public String showToast(String text) {
        Toast.show(mContext, text, Toast.LENGTH_SHORT).show();
        return "success";
    }
}
...

需要注意的是在 API17 版本之后,需要在被调用的地方加上 @addJavascriptInterface 约束注解,因为不加上注解的方法是没有办法被调用的,JS 代码也很简单:

function showToast(){
    var result = myObj.showToast("我是来自web的Toast");
}
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    //假定传入进来的 url = "js://openActivity?arg1=111&arg2=222",代表需要打开本地页面,并且带入相应的参数
    Uri uri = Uri.parse(url);
    String scheme = uri.getScheme();
    //如果 scheme 为 js,代表为预先约定的 js 协议
    if (scheme.equals("js")) {
          //如果 authority 为 openActivity,代表 web 需要打开一个本地的页面
        if (uri.getAuthority().equals("openActivity")) {
              //解析 web 页面带过来的相关参数
            HashMap<String, String> params = new HashMap<>();
            Set<String> collection = uri.getQueryParameterNames();
            for (String name : collection) {
                params.put(name, uri.getQueryParameter(name));
            }
            Intent intent = new Intent(getContext(), MainActivity.class);
            intent.putExtra("params", params);
            getContext().startActivity(intent);
        }
        //代表应用内部处理完成
        return true;
    }
    return super.shouldOverrideUrlLoading(view, url);
}

我们看一下 JS 的代码

function openActivity(){
    document.location = "js://openActivity?arg1=111&arg2=222";
}

这个代码执行之后,就会触发本地的 shouldOverrideUrlLoading 方法,然后进行参数解析,调用指定方法。这个方式不会存在第一种提到的漏洞问题,但是它也有一个很繁琐的地方是,如果 web 端想要得到方法的返回值,只能通过 WebView 的 loadUrl 方法去执行 JS 方法把返回值传递回去,相关的代码如下:

//java
mWebView.loadUrl("javascript:returnResult(" + result + ")");

//javascript
function returnResult(result){
    alert("result is" + result);
}

所以说第二种方式在返回值方面还是很繁琐的,但是在不需要返回值的情况下,比如打开 Native 页面,还是很合适的,制定好相应的协议,就能够让 web 端具有打开所有本地页面的能力了。

@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
    return super.onJsAlert(view, url, message, result);
}

@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) {
    //假定传入进来的 message = "js://openActivity?arg1=111&arg2=222",代表需要打开本地页面,并且带入相应的参数
    Uri uri = Uri.parse(message);
    String scheme = uri.getScheme();
    if (scheme.equals("js")) {
        if (uri.getAuthority().equals("openActivity")) {
            HashMap<String, String> params = new HashMap<>();
            Set<String> collection = uri.getQueryParameterNames();
            for (String name : collection) {
                params.put(name, uri.getQueryParameter(name));
            }
            Intent intent = new Intent(getContext(), MainActivity.class);
            intent.putExtra("params", params);
            getContext().startActivity(intent);
            //代表应用内部处理完成
            result.confirm("success");
        }
        return true;
    }
    return super.onJsPrompt(view, url, message, defaultValue, result);
}

onJsAlert 方法是弹出警告框,一般情况下在 Android 中为 Toast,在文本里面加入\n就可以换行。onJsConfirm 弹出确认框,会返回布尔值,通过这个值可以判断点击时确认还是取消,true表示点击了确认,false表示点击了取消。onJsPrompt 弹出输入框,点击确认返回输入框中的值,点击取消返回 null。


关于native 调用 js
//java
mWebView.loadUrl("javascript:show(" + result + ")");

//javascript
<script type="text/javascript">

function show(result){
    alert("result"=result);
    return "success";
}

</script>

已知的 WebView 任意代码执行漏洞有 4 个:


判断WebView是否已经滚动到页面底端 或者 顶端:

getScrollY() //方法返回的是当前可见区域的顶端距整个页面顶端的距离,也就是当前内容滚动的距离.
getHeight()或者getBottom() //方法都返回当前WebView这个容器的高度
getContentHeight()返回的是整个html的高度,但并不等同于当前整个页面的高度,因为WebView有缩放功能,所以当前整个页面的高度实际上应该是原始html的高度再乘上缩放比例.因此,更正后的结果,准确的判断方法应该是:

if (webView.getContentHeight() * webView.getScale() == (webView.getHeight() + webView.getScrollY())) {
        //已经处于底端
    }

    if(webView.getScrollY() == 0){
        //处于顶端
    }


前进、后退
goBack()//后退
goForward()//前进
goBackOrForward(intsteps) //以当前的index为起始点前进或者后退到历史记录中指定的steps,
                              如果steps为负数则为后退,正数则为前进

canGoForward()//是否可以前进
canGoBack() //是否可以后退

返回键:返回上一次浏览的页面
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
        mWebView.goBack();
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

在 WebView 中长按保存图片

//1. 给 WebView添加监听
mWebview.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {

    }
});

//2、获取点击的图片地址:先获取类型,根据相应的类型来处理对应的数据。
 WebView.HitTestResult result = ((WebView) v).getHitTestResult();
 int type = result.getType();

//3、获取具体信息,图片这里就是图片地址
 String imgurl = result.getExtra();

//4、操作图片(完整代码)
mWebView.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        WebView.HitTestResult result = ((WebView)v).getHitTestResult();
        if (null == result)
            return false;
        int type = result.getType();
        if (type == WebView.HitTestResult.UNKNOWN_TYPE)
            return false;

        // 这里可以拦截很多类型,我们只处理图片类型就可以了
        switch (type) {
            case WebView.HitTestResult.PHONE_TYPE: // 处理拨号
                break;
            case WebView.HitTestResult.EMAIL_TYPE: // 处理Email
                break;
            case WebView.HitTestResult.GEO_TYPE: // 地图类型
                break;
            case WebView.HitTestResult.SRC_ANCHOR_TYPE: // 超链接
                break;
            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
                break;
            case WebView.HitTestResult.IMAGE_TYPE: // 处理长按图片的菜单项
                // 获取图片的路径
                String saveImgUrl = result.getExtra();

                // 跳转到图片详情页,显示图片
                Intent i = new Intent(MainActivity.this, ImageActivity.class);
                i.putExtra("imgUrl", saveImgUrl);
                startActivity(i);
                break;
            default:
                break;
        }
    }
});

type有这几种类型:


webview的封装
public class WebViewActivity extends AppCompatActivity  {
    private FrameLayout mFrameLayout;
    private WebView mWebView;
    private MyWebChromeClient mMyWebChromeClient;
    private String URL = "http://m.tv.sohu.com/20130704/n380744170.shtml";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview);
        mFrameLayout = (FrameLayout) findViewById(R.id.mFrameLayout);
        mWebView = (WebView) findViewById(R.id.mWebView);
        initWebView();
        mWebView.loadUrl(URL);
    }
    private void initWebView() {
        WebSettings settings = mWebView.getSettings();

        //设置了这个属性后我们才能在 WebView 里与我们的 Js 代码进行交互
        settings.setJavaScriptEnabled(true);

        //WebView 是否支持多窗口,如果设置为 true,需要重写 
        //WebChromeClient#onCreateWindow(WebView, boolean, boolean, Message) 函数,默认为 false
        //settings.setSupportMultipleWindows(true);

        //显示WebView提供的缩放控件
        settings.setDisplayZoomControls(false);
        settings.setBuiltInZoomControls(true);
        //设置页面是否支持缩放
        webSettings.setSupportZoom(true);
        //设置文本的缩放倍数,默认为 100
        //webSettings.setTextZoom(2);

        //打开 WebView 的 storage 功能,这样 JS 的 localStorage,sessionStorage 对象才可以使用
        //settings .setDomStorageEnabled(true);

        //打开 WebView 的 LBS 功能,这样 JS 的 geolocation 对象才可以使用
        //settings.setGeolocationEnabled(true);
        // settings.setGeolocationDatabasePath("");

         //设置是否打开 WebView 表单数据的保存功能
        //settings.setSaveFormData(true);

        //设置 WebView 的默认 userAgent 字符串
        //settings.setUserAgentString("");

        //设置 WebView 的字体,可以通过这个函数,改变 WebView 的字体,默认字体为 "sans-serif"
        //settings.setStandardFontFamily("");
        //设置 WebView 字体的大小,默认大小为 16
        //settings.setDefaultFontSize(20);
        //设置 WebView 支持的最小字体大小,默认为 8
        //settings.setMinimumFontSize(12);

        //设置 JS 是否可以打开 WebView 新窗口
        settings.setJavaScriptCanOpenWindowsAutomatically(true);

        //被这个 tag 声明的宽度将会被使用,如果页面没有这个 tag 或者没有提供一个宽度,那么一个宽型 viewport 将会被使用。
        settings.setUseWideViewPort(true);

        settings.setPluginState(WebSettings.PluginState.ON);
        settings.setAllowFileAccess(true);
        settings.setLoadWithOverviewMode(true);
       
        settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        settings.setCacheMode(WebSettings.LOAD_DEFAULT);
        mMyWebChromeClient = new MyWebChromeClient();
        mWebView.setWebChromeClient(mMyWebChromeClient);

        //WebViewClient主要辅助WebView执行处理各种响应请求事件的
        mWebView.setWebViewClient(new WebViewClient() {
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
            }
        });
    }

    //WebChromeClient 主要辅助 WebView 处理J avaScript 的对话框、网站 Logo、网站 title、load 进度等处理
    private class MyWebChromeClient extends WebChromeClient {
        private View mCustomView;
        private CustomViewCallback mCustomViewCallback;
        @Override
        public void onShowCustomView(View view, CustomViewCallback callback) {
            super.onShowCustomView(view, callback);
            if (mCustomView != null) {
                callback.onCustomViewHidden();
                return;
            }
            mCustomView = view;
            mFrameLayout.addView(mCustomView);
            mCustomViewCallback = callback;
            mWebView.setVisibility(View.GONE);
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        }

        public void onHideCustomView() {
            mWebView.setVisibility(View.VISIBLE);
            if (mCustomView == null) {
                return;
            }
            mCustomView.setVisibility(View.GONE);
            mFrameLayout.removeView(mCustomView);
            mCustomViewCallback.onCustomViewHidden();
            mCustomView = null;
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            super.onHideCustomView();
        }
    }
    @Override
    public void onConfigurationChanged(Configuration config) {
        super.onConfigurationChanged(config);
        switch (config.orientation) {
            case Configuration.ORIENTATION_LANDSCAPE:
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                break;
            case Configuration.ORIENTATION_PORTRAIT:
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
                break;
        }
    }
    @Override
    public void onPause() {
        super.onPause();
        mWebView.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        mWebView.onResume();
    }

    @Override
    public void onBackPressed() {
        if (mWebView.canGoBack()) {
            mWebView.goBack();
            return;
        }
        super.onBackPressed();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mWebView.destroy();
    }
}


一个完整的Html5Activity

https://github.com/Wing-Li/Html5WebView/tree/master

import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.GeolocationPermissions;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.lyl.test.R;

public class Html5Activity extends AppCompatActivity {

    private String mUrl;

    private LinearLayout mLayout;
    private WebView mWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web);

        Bundle bundle = getIntent().getBundleExtra("bundle");
        mUrl = bundle.getString("url");

        Log.d("Url:", mUrl);

        mLayout = (LinearLayout) findViewById(R.id.web_layout);


        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        mWebView = new WebView(getApplicationContext());
        mWebView.setLayoutParams(params);
        mLayout.addView(mWebView);

        WebSettings mWebSettings = mWebView.getSettings();
        mWebSettings.setSupportZoom(true);
        mWebSettings.setLoadWithOverviewMode(true);
        mWebSettings.setUseWideViewPort(true);
        mWebSettings.setDefaultTextEncodingName("utf-8");
        mWebSettings.setLoadsImagesAutomatically(true);

        //调用JS方法.安卓版本大于17,加上注解 @JavascriptInterface
        mWebSettings.setJavaScriptEnabled(true);

        saveData(mWebSettings);

        newWin(mWebSettings);

        mWebView.setWebChromeClient(webChromeClient);
        mWebView.setWebViewClient(webViewClient);
        mWebView.loadUrl(mUrl);
    }

    @Override
    public void onPause() {
        super.onPause();
        webView.onPause();
        webView.pauseTimers(); //小心这个!!!暂停整个 WebView 所有布局、解析、JS。
    }

    @Override
    public void onResume() {
        super.onResume();
        webView.onResume();
        webView.resumeTimers();
    }

    /**
     * 多窗口的问题
     */
    private void newWin(WebSettings mWebSettings) {
        //html中的_bank标签就是新建窗口打开,有时会打不开,需要加以下
        //然后 复写 WebChromeClient的onCreateWindow方法
        mWebSettings.setSupportMultipleWindows(false);
        mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
    }

    /**
     * HTML5数据存储
     */
    private void saveData(WebSettings mWebSettings) {
        //有时候网页需要自己保存一些关键数据,Android WebView 需要自己设置
        mWebSettings.setDomStorageEnabled(true);
        mWebSettings.setDatabaseEnabled(true);
        mWebSettings.setAppCacheEnabled(true);
        String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
        mWebSettings.setAppCachePath(appCachePath);
    }

    WebViewClient webViewClient = new WebViewClient(){

        /**
         * 多页面在同一个WebView中打开,就是不新建activity或者调用系统浏览器打开
         */
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }

    };

    WebChromeClient webChromeClient = new WebChromeClient() {

        //=========HTML5定位==========================================================
        //需要先加入权限
        //<uses-permission android:name="android.permission.INTERNET"/>
        //<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
        //<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
        @Override
        public void onReceivedIcon(WebView view, Bitmap icon) {
            super.onReceivedIcon(view, icon);
        }

        @Override
        public void onGeolocationPermissionsHidePrompt() {
            super.onGeolocationPermissionsHidePrompt();
        }

        @Override
        public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) {
            callback.invoke(origin, true, false);//注意个函数,第二个参数就是是否同意定位权限,第三个是是否希望内核记住
            super.onGeolocationPermissionsShowPrompt(origin, callback);
        }
        //=========HTML5定位==========================================================

        //=========多窗口的问题==========================================================
        @Override
        public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
            WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
            transport.setWebView(view);
            resultMsg.sendToTarget();
            return true;
        }
        //=========多窗口的问题==========================================================
    };

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
            mWebView.goBack();
            return true;
        }

        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mWebView != null) {
            mWebView.clearHistory();
            ((ViewGroup) mWebView.getParent()).removeView(mWebView);
            mWebView.loadUrl("about:blank");
            mWebView.stopLoading();
            mWebView.setWebChromeClient(null);
            mWebView.setWebViewClient(null);
            mWebView.destroy();
            mWebView = null;
        }
    }

}

App通过调用外部浏览器打开网页
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);//"android.intent.action.VIEW"
Uri content_url = Uri.parse("www.ycxc.com");
intent.setData(content_url);

//方案一
//startActivity(Intent.createChooser(intent, "请选择浏览器"));

//方案二
/*if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
}*/

//方案三
intent.setClassName("com.android.browser","com.android.browser.BrowserActivity");
startActivity(intent);
上一篇下一篇

猜你喜欢

热点阅读