强大灵活的WebView代理库-PrimWeb

PrimWeb https://github.com/JakePrim/PrimWeb

logo.png

PrimWeb is What?

PrimWeb 是一个代理的WebView基于的 Android WebView 和 腾讯 x5 WebView,容易、灵活使用以及功能非常强大的库,提供了 WebView 一系列的问题解决方案 ,并且轻量和灵活, 更方便 webview 的切换.

What Support?

  1. 支持动态添加WebView
  2. 支持X5WebView 和 原生的WebView切换
  3. 简化Js通信
  4. 灵活的设置WebSetting
  5. 代理WebViewClient 兼容 X5 WebView和android WebView
  6. 代理WebChormeClient 兼容 android webview 和 x5 webview
  7. 支持判断js方法是否存在
  8. 支持input标签文件上传
  9. 支持Js通信文件上传
  10. 简化回退及返回键的处理
  11. 简化url加载
  12. webview 安全漏洞的问题修复,更加安全
  13. 支持权限管理,常用的定位、相册的权限
  14. 支持电话、短信、邮件的跳转
  15. 支持自定义进度条指示器
  16. 支持自定义错误页面
  17. 支持跳转到其他应用页面

Activity调用PrimWeb

Fragment调用PrimWeb

识别电话短信邮箱

activity.gif

fragment.gif

sms.gif

JS通信

文件上传

自定义错误页面

js.gif

input.gif

error.gif

How Do I Use?

PrimWeb.with(getActivity())
                    .setWebParent(webParent, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
                    .useDefaultUI()
                    .useDefaultTopIndicator()
                    .setWebViewType(PrimWeb.WebViewType.X5)
                    .setListenerCheckJsFunction(this)
                    .buildWeb()
                    .lastGo()
                    .launch(mParam1);

Update Log

  • v1.0.0 完善功能
  • v1.0.1 1. 回退和返回键的简化处理 2. 添加返回拦截,处理特殊情况 3. 添加进度条指示器可自定义 4. 添加错误页面可自定义 5. 优化文件上传

TODO

  1. 实现刷新回弹功能
  2. 实现JS通信文件上传
  3. webview下载文件

API 详解

动态添加webView,防止内存泄漏

.setWebParent(frameLayout, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))

内存泄漏不用担心,已经在内部处理了
    @Override
    public void destroy() {
        removeAllViewsInLayout();
        ViewParent parent = getParent();
        if (parent instanceof ViewGroup) {//从父容器中移除webview
            ((ViewGroup) parent).removeAllViewsInLayout();
        }
        releaseConfigCallback();
        super.destroy();
    }

动态切换 X5和Android 的webview

如果要是用x5的webview需要在application中调用此方法 PrimWeb.init(this); 初始化x5

//使用库中X5的webview
.setWebViewType(PrimWeb.WebViewType.X5)

//使用库中Android的webview
.setWebViewType(PrimWeb.WebViewType.Android)

public enum WebViewType {
       Android, X5
}

Javascript调Java? 可以addJavascriptInterface 多个,具体请看 SafeJsInterface

primWeb.getJsInterface().addJavaObject(new MyJavaObject(),"jsAgent")

/** 注入js脚本 */
public class MyJavaObject {

        @JavascriptInterface
        public void login(String data) {

        }

    }

Java调用Javascript方法,方便安全的加载js方法可传多个参数,具体请看 SafeCallJsLoaderImpl

primWeb.getCallJsLoader().callJS("jsMethod");
primWeb.getCallJsLoader().callJS("callByAndroidParam", 1234);
primWeb.getCallJsLoader().callJs("callByAndroidMoreParams", agentValueCallback, getJson(), "prim", true);


//可传多个参数,可使用高级的API
@RequiresApi(Build.VERSION_CODES.KITKAT)
void callJs(String method, AgentValueCallback<String> callback, String... params);
@RequiresApi(Build.VERSION_CODES.KITKAT)
void callJs(String method, AgentValueCallback<String> callback);
void callJS(String method, String... params);
void callJS(String method);

灵活的设置WebSetting如:X5DefaultWebSetting 继承 BaseAgentWebSetting类 注意WebSettings 是X5 的 API

.setAgentWebSetting(new X5DefaultWebSetting(this))

public class X5DefaultWebSetting extends BaseAgentWebSetting<WebSettings> {
    private Context context;
    private static final String APP_CACAHE_DIRNAME = "/webcache";

    public X5DefaultWebSetting(Context context) {
        this.context = context;
    }

    @SuppressLint("SetJavaScriptEnabled")
    @Override
    protected void toSetting(WebSettings webSetting) {
        webSetting.setJavaScriptEnabled(true);
        webSetting.setJavaScriptCanOpenWindowsAutomatically(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            // 通过 file url 加载的 Javascript 读取其他的本地文件 .建议关闭
            webSetting.setAllowFileAccessFromFileURLs(false);
            // 允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源
            webSetting.setAllowUniversalAccessFromFileURLs(false);
        }
        }
       ......
   }

设置 setWebViewClient 使用代理的WebViewClient 兼容android webview 和 x5 webview

.setAgentWebViewClient(agentWebViewClient)

AgentWebViewClient agentWebViewClient = new AgentWebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(IAgentWebView view, String url) {
            Log.e(TAG, "shouldOverrideUrlLoading: " + url);
            return super.shouldOverrideUrlLoading(view, url);
        }

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public boolean shouldOverrideUrlLoading(IAgentWebView view, WebResourceRequest request) {
            Log.e(TAG, "shouldOverrideUrlLoading: WebResourceRequest --> " + request.getUrl());
            return super.shouldOverrideUrlLoading(view, request);
        }
};

代理WebChormeClient 兼容android webview 和 x5 webview

 .setAgentWebChromeClient(agentChromeClient)

AgentChromeClient agentChromeClient = new AgentChromeClient() {
        @Override
        public void onReceivedTitle(View webView, String s) {
            super.onReceivedTitle(webView, s);
            if (actionBar != null) {
                actionBar.setTitle(s);
            }
        }
    };

设置允许打开其他应用页面

.alwaysOpenOtherPage(true)

设置进度指示器

关闭进度指示器:
.colseTopIndicator()

设置默认的进度指示器:
.useDefaultTopIndicator()

如果想改变指示器的颜色可以调用如下:
.useDefaultTopIndicator(@ColorInt int color)
.useDefaultTopIndicator(@NonNull String color)

如果想改变指示器的高可以调用如下:
.useDefaultTopIndicator(@ColorInt int color, int height)

如果想自定义指示器可以调用如下:主要需要继承BaseIndicatorView
.useCustomTopIndicator(@NonNull BaseIndicatorView indicatorView)

设置错误页面

设置默认的UI
.useDefaultUI()

可以设置布局的layout和点击的id
.useCustomUI(@LayoutRes int errorLayout, @IdRes int errorClickId)

可以设置为一个view
.useCustomUI(@NonNull View errorView)

灵活安全的加载url,具体可以看UrlLoader

 primWeb.getUrlLoader().loadUrl();
 primWeb.getUrlLoader().reload();
 primWeb.getUrlLoader().stopLoading();

webview的生命周期

    @Override
    protected void onResume() {
        super.onResume();
        primWeb.webLifeCycle().onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        primWeb.webLifeCycle().onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        primWeb.webLifeCycle().onDestory();
    }

判断js方法是否存在

     //设置监听
    .setListenerCheckJsFunction(this)

    //要检查的js方法
    primWeb.getCallJsLoader().checkJsMethod("returnBackHandles");

    @Override
    public void jsFunExit(Object data) {
        Toast.makeText(getActivity(), data.toString() + "方法存在", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void jsFunNoExit(Object data) {
        Toast.makeText(getActivity(), data.toString() + "方法不存在", Toast.LENGTH_SHORT).show();
    }

内部是这样处理的,我自己写了一个js方法,来专门判断, 具体请看BaseCallJsLoader

 @Override
    public void checkJsMethod(String method) {
        StringBuilder sb = new StringBuilder();
        sb.append("function checkJsFunction(){ if(typeof ")
                .append(method)
                .append(" != \"undefined\" && typeof ")
                .append(method)
                .append(" == \"function\")")
                .append("{console.log(\"")
                .append(method)
                .append("\");")
                .append("checkJsBridge['jsFunctionExit']();")
                .append("}else{")
                .append("if(typeof checkJsBridge == \"undefined\") return false;")
                .append("checkJsBridge['jsFunctionNo']();}}");
        call("javascript:" + sb.toString() + ";checkJsFunction()", null);
    }

webview 上传文件

PrimWeb库已经默认实现了文件上传

如果想禁止文件上传可以设置:
.setAllowUploadFile(false)

如果想使用第三方文件选择库上传文件设置:
.setUpdateInvokThrid(true)

同时具体的上传逻辑需要自己完成,具体的可以看Demo


@Override
    public void jsOpenVideos() {
        Log.e(TAG, "jsOpenVideos: ");
        PrimPicker.with(WebFragment.this)
                .choose(MimeType.ofVideo())
                .setMaxSelected(1)
                .setImageLoader(new MyImageLoad())
                .setShowSingleMediaType(true)
                .setCapture(false)
                .setSpanCount(3)
                .lastGo(1001);
    }

    @Override
    public void jsOpenPick() {
        PrimPicker.with(WebFragment.this)
                .choose(MimeType.ofImage())
                .setMaxSelected(1)
                .setImageLoader(new MyImageLoad())
                .setShowSingleMediaType(true)
                .setCapture(false)
                .setSpanCount(3)
                .lastGo(1001);
    }

      private ValueCallback<Uri[]> agentValueCallbacks;

    private ValueCallback<Uri> agentValueCallback;

    AgentChromeClient agentChromeClient = new AgentChromeClient() {
        @Override
        public boolean onShowFileChooser(View webView, ValueCallback<Uri[]> valueCallback, com.tencent.smtt.sdk.WebChromeClient.FileChooserParams fileChooserParams) {
            agentValueCallbacks = valueCallback;
            //注意监听需要写在这里 否则监听有时候会收不到
            primWeb.setThriedChooserListener(WebFragment.this);
            return super.onShowFileChooser(webView, valueCallback, fileChooserParams);
        }

        @Override
        public void openFileChooser(ValueCallback<Uri> valueCallback) {
            agentValueCallback = valueCallback;
            primWeb.setThriedChooserListener(WebFragment.this);
            super.openFileChooser(valueCallback);
        }

        @Override
        public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType) {
            agentValueCallback = valueCallback;
            primWeb.setThriedChooserListener(WebFragment.this);
            super.openFileChooser(valueCallback, acceptType);
        }

        @Override
        public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
            agentValueCallback = valueCallback;
            primWeb.setThriedChooserListener(WebFragment.this);
            super.openFileChooser(valueCallback, acceptType, capture);
        }
    };

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.e(TAG, "onActivityResult: requestCode --> " + requestCode + " | resultCode --> " + resultCode);
        if (resultCode == RESULT_CANCELED && requestCode == 1001) {
            cancelFilePathCallback();
        } else if (resultCode == RESULT_OK && requestCode == 1001) {
            uploadImgVideo(PrimPicker.obtainUriResult(data).get(0));
        }
    }

    private void uploadImgVideo(Uri uri) {
        Uri result = uri;
        Uri[] results = new Uri[]{uri};
        if (agentValueCallbacks != null) {
            agentValueCallbacks.onReceiveValue(results);
            agentValueCallbacks = null;
        } else if (agentValueCallback != null) {
            if (result == null) {
                return;
            }
            agentValueCallback.onReceiveValue(result);
            agentValueCallback = null;
        }
    }

    private void cancelFilePathCallback() {
        if (agentValueCallback != null) {
            agentValueCallback.onReceiveValue(null);
            agentValueCallback = null;
        } else if (agentValueCallbacks != null) {
            agentValueCallbacks.onReceiveValue(null);
            agentValueCallbacks = null;
        }
    }

回退的处理简化

返回键的回退简化:
 @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (primWeb.handlerKeyEvent(keyCode, event)) {
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }


 返回按钮的回退简化:
if (!primWeb.handlerBack()) {
                    this.finish();
                }

本文分享自微信公众号 - Android研究院(androidlinksu)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-08-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CodingBlock

Android学习笔记(四)深入探讨Activity

  在应用程序中至少包含一个用来处理应用程序的主UI功能的主界面屏幕。这个主界面一般由多个Fragment组成,并由一组次要Activity支持。要在屏幕之间切...

242100
来自专栏潇涧技术专栏

Android Training Summary (1) Getting Started

Android Training 中Getting Started部分的阅读笔记

7700
来自专栏分享达人秀

手把手教你读懂源码,View的Touch事件传递流程详细剖析

继上一篇分析,今天我们来接着分析Activity的Touch事件是如何分发传递的。 都知道在Android中的事件主要包括三部分内容:分发事件d...

18370
来自专栏Android开发小工

优雅地实现RecyclerView的上拉加载

这篇博客是承接上一篇博客--探索Android架构的DataLayer层(DataManager方式)具体实现,其实是上篇博客的一个使用比较普遍的例子,当然如果...

12440
来自专栏技术小黑屋

自定义支持读取XML属性的View

在Android中,添加一个View很简单,简单的你可以简简单单地使用xml和一部分简单的java代码就可以搞定。 比如这样

14420
来自专栏jianhuicode

如何使用MVP+Dagger2+RxJava+Retrofit开发(1)

概述 在2016年5,6月份开始在知乎上看到开发方法,那时候记得是看mvc,mvp,mvvm这三种开发模式区别,后面进一步了解到google在github上开源...

55180
来自专栏知识分享

8-51单片机ESP8266学习-AT指令(测试TCP服务器--51单片机程序配置8266,做自己的手机TCP客户端发信息给单片机控制小灯的亮灭)

http://www.cnblogs.com/yangfengwu/p/8776712.html 先把源码和资料链接放到这里 链接:https://pan.ba...

68420
来自专栏Android机动车

MVP模式的经典封装

说到MVP,大家应该都不陌生了,由于其高度解等等优点,越来越多的项目使用这个设计模式。然而,优点虽在,缺点也不少,其中一个就是类多了很多,而且V与P直接要项目通...

21520
来自专栏刘望舒

RxBinding使用和源码解析

作者 | juexingzhe 地址 | https://www.jianshu.com/u/ea71bb3770b4 声明 | 本文是 juexingzhe...

486100
来自专栏Android知识点总结

1--安卓多媒体之图片综合篇

9120

扫码关注云+社区

领取腾讯云代金券