强行安利下自己的收纳库(日更):https://github.com/ddwhan0123/Useful-Open-Source-Android
昨天写了个CardView的貌似,不太受欢迎,今天上午没啥大事就继续翻源码,这一篇讲的是WebView的授权实现(WebView这个坑大家怨声载道,现在看看官方是如何操作的吧)
先看下运行效果
OK,来看看包结构
比平时的那些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