强行安利下自己的收纳库(日更):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