翻看谷歌源码 那些让人感兴趣的东西--WebView如何申请授权

转载请注明出处:王亟亟的大牛之路

强行安利下自己的收纳库(日更):https://github.com/ddwhan0123/Useful-Open-Source-Android

昨天写了个CardView的貌似,不太受欢迎,今天上午没啥大事就继续翻源码,这一篇讲的是WebView的授权实现(WebView这个坑大家怨声载道,现在看看官方是如何操作的吧)

先看下运行效果

翻看谷歌源码 那些让人感兴趣的东西--WebView如何申请授权_第1张图片

OK,来看看包结构

翻看谷歌源码 那些让人感兴趣的东西--WebView如何申请授权_第2张图片

比平时的那些sample东西稍微多点 其实主要业务就在 PermissionRequestFragment和ConfirmationDialogFragment里

我们一个个看(主要看授权行为)

MainActivity初始化一些UI 然后把 Log部分和WebView部分以及bar逻辑的一些相关内容做了处理,不涉及授权操作(主要做呈现)

他继承于SampleActivityBase,也是做一些初始化Log的行为,这里不做解释

接下来是授权行为操作的PermissionRequestFragment(重要步骤已经注释)

/**
 * This fragment shows a {@link WebView} and loads a web app from the {@link SimpleWebServer}.
 */
public class PermissionRequestFragment extends Fragment
        implements ConfirmationDialogFragment.Listener {

    private static final String TAG = PermissionRequestFragment.class.getSimpleName();

    private static final String FRAGMENT_DIALOG = "dialog";

    /**
     * We use this web server to serve HTML files in the assets folder. This is because we cannot
     * use the JavaScript method "getUserMedia" from "file:///android_assets/..." URLs.
     * 异步对WebView的操作
     */
    private SimpleWebServer mWebServer;

    /**
     * A reference to the {@link WebView}.
     * 嵌入的ViewView
     */
    private WebView mWebView;

    /**
     * This field stores the {@link PermissionRequest} from the web application until it is allowed
     * or denied by user.
     * 授权行为
     */
    private PermissionRequest mPermissionRequest;

    /**
     * For testing.
     * 控制台行为
     */
    private ConsoleMonitor mConsoleMonitor;

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        //布局处理
        return inflater.inflate(R.layout.fragment_permission_request, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        //初始化WebView相关操作
        mWebView = (WebView) view.findViewById(R.id.web_view);
        // Here, we use #mWebChromeClient with implementation for handling PermissionRequests.
        mWebView.setWebChromeClient(mWebChromeClient);
        configureWebSettings(mWebView.getSettings());
    }

    @Override
    public void onResume() {
        super.onResume();
        //模拟访问某URLWebView
        final int port = 8080;
        mWebServer = new SimpleWebServer(port, getResources().getAssets());
        mWebServer.start();
        mWebView.loadUrl("http://localhost:" + port + "/sample.html");
    }

    @Override
    public void onPause() {
        //关闭
        mWebServer.stop();
        super.onPause();
    }

    //允许JS操作行为
    @SuppressLint("SetJavaScriptEnabled")
    private static void configureWebSettings(WebSettings settings) {
        settings.setJavaScriptEnabled(true);
    }

    /**
     * This {@link WebChromeClient} has implementation for handling {@link PermissionRequest}.
     * 主要的时间传递行为
     */
    private WebChromeClient mWebChromeClient = new WebChromeClient() {

        // This method is called when the web content is requesting permission to access some
        // resources.
        //当网页内容被请求允许访问某些资源,调用此方法。
        @Override
        public void onPermissionRequest(PermissionRequest request) {
            Log.i(TAG, "onPermissionRequest");
            mPermissionRequest = request;
            //把事件传递给了ConfirmationDialogFragment
            ConfirmationDialogFragment.newInstance(request.getResources())
                    .show(getChildFragmentManager(), FRAGMENT_DIALOG);
        }

        // This method is called when the permission request is canceled by the web content.
        //这个方法在授权请求被取消时被调用
        @Override
        public void onPermissionRequestCanceled(PermissionRequest request) {
            Log.i(TAG, "onPermissionRequestCanceled");
            // 驳回提示UI作为请求不再有效。
            mPermissionRequest = null;
            DialogFragment fragment = (DialogFragment) getChildFragmentManager()
                    .findFragmentByTag(FRAGMENT_DIALOG);
            if (null != fragment) {
                fragment.dismiss();
            }
        }

        @Override
        public boolean onConsoleMessage(@NonNull ConsoleMessage message) {
            switch (message.messageLevel()) {
                case TIP:
                    Log.v(TAG, message.message());
                    break;
                case LOG:
                    Log.i(TAG, message.message());
                    break;
                case WARNING:
                    Log.w(TAG, message.message());
                    break;
                case ERROR:
                    Log.e(TAG, message.message());
                    break;
                case DEBUG:
                    Log.d(TAG, message.message());
                    break;
            }
            if (null != mConsoleMonitor) {
                mConsoleMonitor.onConsoleMessage(message);
            }
            return true;
        }

    };

    @Override
    public void onConfirmation(boolean allowed) {
        if (allowed) {
            mPermissionRequest.grant(mPermissionRequest.getResources());
            Log.d(TAG, "Permission granted.");
        } else {
            mPermissionRequest.deny();
            Log.d(TAG, "Permission request denied.");
        }
        mPermissionRequest = null;
    }

    public void setConsoleMonitor(ConsoleMonitor monitor) {
        mConsoleMonitor = monitor;
    }

    /**
     * For testing.
     */
    public interface ConsoleMonitor {
        public void onConsoleMessage(ConsoleMessage message);
    }

}

PermissionRequestFragment这个类做了一系列初始化的行为(主要是对于 WebView的),然后接收JS的回调,把处理的权限给予ConfirmationDialogFragment,然后根据ConfirmationDialogFragment的回调来做相关处理

再来看下ConfirmationDialogFragment

/**
 * Prompts the user to confirm permission request.
 */
public class ConfirmationDialogFragment extends DialogFragment {

    private static final String ARG_RESOURCES = "resources";

    /**
     * Creates a new instance of ConfirmationDialogFragment.
     *
     * @param resources The list of resources requested by PermissionRequeste.
     * @return A new instance.
     */
    public static ConfirmationDialogFragment newInstance(String[] resources) {
        ConfirmationDialogFragment fragment = new ConfirmationDialogFragment();
        Bundle args = new Bundle();
        args.putStringArray(ARG_RESOURCES, resources);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        String[] resources = getArguments().getStringArray(ARG_RESOURCES);
        return new AlertDialog.Builder(getActivity())
                .setMessage(getString(R.string.confirmation, TextUtils.join("\n", resources)))
                .setNegativeButton(R.string.deny, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ((Listener) getParentFragment()).onConfirmation(false);
                    }
                })
                .setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ((Listener) getParentFragment()).onConfirmation(true);
                    }
                })
                .create();
    }

    /**
     * Callback for the user's response.
     */
    public interface Listener {

        /**
         * Called when the PermissoinRequest is allowed or denied by the user.
         *
         * @param allowed True if the user allowed the request.
         */
        public void onConfirmation(boolean allowed);
    }

}

这边做的事情比较简单,就不在注释里讲了,这边笼统的介绍下。

首先当被调用的时候接受到了具体申请授权的内容,然后显示dialog,根据用户不同的操作传递给PermissionRequestFragment ,他本身不做什么具体授权行为,主要是dialog的显示功能。

  @Override
    public void onConfirmation(boolean allowed) {
        if (allowed) {
            mPermissionRequest.grant(mPermissionRequest.getResources());
            Log.d(TAG, "Permission granted.");
        } else {
            mPermissionRequest.deny();
            Log.d(TAG, "Permission request denied.");
        }
        mPermissionRequest = null;
    }

因为在授权之后又再次为null,所以我们也就能反复对他进行操作了。

那么问题来了,从头到尾我们是如何跟JS交互的?

再来看下JS的代码

(function () {
    "use strict";

    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
    window.URL = window.URL || window.webkitURL;

    window.onload = function () {

        var video = document.querySelector('#video'),
            toggle = document.querySelector('#toggle'),
            stream = null;

        if (!navigator.getUserMedia) {
            console.error('getUserMedia not supported');
        }

        toggle.addEventListener('click', function () {
            if (null === stream) {
                // This call to "getUserMedia" initiates a PermissionRequest in the WebView.
                navigator.getUserMedia({ video: true }, function (s) {
                    stream = s;
                    video.src = window.URL.createObjectURL(stream);
                    toggle.innerText = 'Stop';
                    console.log('Started');
                }, function (error) {
                    console.error('Error starting camera. Denied.');
                });
            } else {
                stream.stop();
                stream = null;
                toggle.innerText = 'Start';
                console.log('Stopped');
            }
        });

        console.log('Page loaded');

    };

})();

整个html把事情都交付给了JS做逻辑判断 而navigator.getUserMedia 就是js向手机申请需要做某件事,手机知晓后向用户申请授权的代码了
里面还有一些读不到媒体干嘛干嘛的判断,大家爱看不看,不是太重要。

这样一来流程完全走了一遍

总结几个关键步骤:

1 WebView设置的时候要能接受JS请求

2 在需要native支持的时候通过双方的接口进行通信

3 手机接收到请求授权后问用户是否给予,如果用户不同意就 mPermissionRequest.deny()


代码那么多其实并不难理解,只是一大堆回调地狱而已!

源码地址:https://github.com/ddwhan0123/BlogSample/tree/master/PermissionRequest

源码下载地址:https://github.com/ddwhan0123/BlogSample/blob/master/PermissionRequest/PermissionRequest.zip?raw=true

你可能感兴趣的:(android资源)