我的回答与这里的其他人基本相似,因为我创建了第二个WebView
来托管 Facebook 登录页面,而不是尝试通过重定向来解决问题。但是,我选择将登录WebView
放在它自己的Fragment
,并给它自己的专用WebViewClient
和WebChromeClient
子类。我认为这可以更容易地了解每个组件所扮演的角色,以及哪些对象需要哪些设置和行为。
我还利用它WebChromeClient.onCloseWindow()
来检测 Facebook 的 JavaScript 何时想要关闭登录窗口。从不同的答案来看,这比我最初采用的方法要强大得多。
在您的Activity
布局中,您将拥有WebView
承载评论的 "primary" 和用于FacebookWebLoginFragment
. 登录Fragment
是在需要时即时创建的,然后在 Facebook 的登录 JavaScript 请求关闭其窗口时删除。
我的Activity
布局如下所示:
<include layout="@layout/toolbar_common" />
<FrameLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/web_view_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
/>
<!-- Used for Facebook login associated with comments -->
<FrameLayout
android:id="@+id/facebook_web_login_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:visibility="gone"
/>
</FrameLayout>
在您的Activity
中,您需要代码来显示和隐藏 Facebook 网络登录片段。我使用 Otto 事件总线,所以我有如下事件处理程序。(这里没有具体针对这个问题;我包含此代码只是为了让您了解登录如何Fragment
融入整体结构。)
@Subscribe
public void onShowFacebookWebLoginEvent(ShowFacebookWebLoginEvent event) {
FacebookWebLoginFragment existingFragment = getFacebookWebLoginFragment();
if (existingFragment == null) {
mFacebookWebLoginFragmentContainer.setVisibility(View.VISIBLE);
createFacebookWebLoginFragment(event);
}
}
@Subscribe
public void onHideFacebookWebLoginEvent(HideFacebookWebLoginEvent event) {
FacebookWebLoginFragment existingFragment = getFacebookWebLoginFragment();
if (existingFragment != null) {
mFacebookWebLoginFragmentContainer.setVisibility(View.GONE);
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction()
.remove(existingFragment)
.commit();
}
}
@Nullable
private FacebookWebLoginFragment getFacebookWebLoginFragment() {
FragmentManager fm = getSupportFragmentManager();
return (FacebookWebLoginFragment) fm.findFragmentById(R.id.facebook_web_login_fragment_container);
}
private void createFacebookWebLoginFragment(ShowFacebookWebLoginEvent event) {
FragmentManager fm = getSupportFragmentManager();
FacebookWebLoginFragment fragment = (FacebookWebLoginFragment) fm.findFragmentById(R.id.facebook_web_login_fragment_container);
if (fragment == null) {
fragment = FacebookWebLoginFragment.newInstance(event.getOnCreateWindowResultMessage());
fm.beginTransaction()
.add(R.id.facebook_web_login_fragment_container, fragment)
.commit();
}
}
当FacebookWebLoginFragment
它存在时,它应该被授予处理设备后退按钮按下的权限。这很重要,因为 Facebook 登录流程包含离开登录页面的导航功能,并且用户希望返回按钮返回登录。所以,在我的Activity
,我有这个:
@Override
public void onBackPressed() {
boolean handled = false;
FacebookWebLoginFragment facebookWebLoginFragment = getFacebookWebLoginFragment();
if (facebookWebLoginFragment != null) {
handled = facebookWebLoginFragment.onBackPressed();
}
if (!handled) {
WebViewFragment fragment = getWebViewFragment();
if (fragment != null) {
handled = fragment.onBackPressed();
}
}
if (!handled) {
finish();
}
}
的布局FacebookWebLoginFragment
非常简单:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
这是FacebookWebLoginFragment
代码。请注意,它依赖于一个子类WebChromeClient
来检测 Facebook 登录 JavaScript 何时准备好关闭窗口(即,删除片段)。另请注意,此登录名与包含评论 UIWebView
的主节点之间没有直接通信;WebView
身份验证令牌通过第三方 cookie 传递,这就是为什么您必须确保在您的主WebView
.
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Message;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import butterknife.Bind;
import butterknife.ButterKnife;
/**
* Hosts WebView used by Facebook web login.
*/
public class FacebookWebLoginFragment extends BaseFragment {
private static final String LOGTAG = LogHelper.getLogTag(FacebookWebLoginFragment.class);
@Bind(R.id.web_view) WebView mFacebookLoginWebView;
private WebChromeClient mFacebookLoginWebChromeClient;
private Message onCreateWindowResultMessage;
public static FacebookWebLoginFragment newInstance(Message onCreateWindowResultMessage) {
FacebookWebLoginFragment fragment = new FacebookWebLoginFragment();
fragment.onCreateWindowResultMessage = onCreateWindowResultMessage;
return fragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.frag_facebook_web_login, container, false);
ButterKnife.bind(this, rootView);
return rootView;
}
@Override
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
mFacebookLoginWebView.setVerticalScrollBarEnabled(false);
mFacebookLoginWebView.setHorizontalScrollBarEnabled(false);
mFacebookLoginWebView.setWebViewClient(new FacebookLoginWebViewClient());
mFacebookLoginWebView.getSettings().setJavaScriptEnabled(true);
mFacebookLoginWebView.getSettings().setSavePassword(false);
mFacebookLoginWebView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mFacebookLoginWebChromeClient = makeFacebookLoginWebChromeClient();
mFacebookLoginWebView.setWebChromeClient(mFacebookLoginWebChromeClient);
WebView.WebViewTransport transport = (WebView.WebViewTransport) onCreateWindowResultMessage.obj;
transport.setWebView(mFacebookLoginWebView);
onCreateWindowResultMessage.sendToTarget();
onCreateWindowResultMessage = null; // This seems to eliminate a mysterious crash
}
@Override
public void onDestroy() {
mFacebookLoginWebChromeClient = null;
super.onDestroy();
}
/**
* Performs fragment-specific behavior for back button, and returns true if the back press
* has been fully handled.
*/
public boolean onBackPressed() {
if (mFacebookLoginWebView.canGoBack()) {
mFacebookLoginWebView.goBack();
} else {
closeThisFragment();
}
return true;
}
private void closeThisFragment() {
EventBusHelper.post(new HideFacebookWebLoginEvent());
}
class FacebookLoginWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Only allow content from Facebook
Uri uri = Uri.parse(url);
String scheme = uri.getScheme();
if (scheme != null && (TextUtils.equals(scheme, "http") || TextUtils.equals(scheme, "https"))) {
if (UriHelper.isFacebookHost(uri)) {
return false;
}
}
return true;
}
}
private WebChromeClient makeFacebookLoginWebChromeClient() {
return new WebChromeClient() {
@Override
public void onCloseWindow(WebView window) {
closeThisFragment();
}
};
}
}
现在,最棘手的一点是对现有WebView
的 .
首先,确保您启用了 JavaScript,并且它支持多个窗口。
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setSupportMultipleWindows(true);
你不需要打电话setJavaScriptCanOpenWindowsAutomatically(true)
。
查看其他一些答案,您可能认为您需要对WebViewClient
分配给您的WebView
, 和 override进行猴子处理shouldOverrideUrlLoading()
。这不是必需的。重要的是WebChromeClient
,它需要覆盖onCreateWindow()
。
所以...接下来,WebChromeClient
为您分配一个自定义子类WebView
:
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
String url = null;
Message href = view.getHandler().obtainMessage();
if (href != null) {
view.requestFocusNodeHref(href);
url = href.getData().getString("url");
}
LogHelper.d(LOGTAG, "onCreateWindow: " + url);
// Unfortunately, url is null when "Log In to Post" button is pressed
if (url == null || UriHelper.isFacebookHost(Uri.parse(url))) {
// Facebook login requires cookies to be enabled, and on more recent versions
// of Android, it's also necessary to enable acceptance of 3rd-party cookies
// on the WebView that hosts Facebook comments
CookieHelper.setAcceptThirdPartyCookies(mWebView, true);
EventBusHelper.post(new ShowFacebookWebLoginEvent(resultMsg));
} else {
LogHelper.d(LOGTAG, "Ignoring request from js to open new window for URL: " + url);
}
return true;
}
});
您会注意到这是第二次调用UriHelper.isFacebookHost()
. 我没有确定这一点的万无一失的方法,但这就是我所做的:
public static boolean isFacebookHost(Uri uri) {
if (uri != null && !TextUtils.isEmpty(uri.getHost())) {
String host = uri.getHost().toLowerCase();
return host.endsWith("facebook.com") || host.endsWith("facebook.net");
}
return false;
}
您还会注意到对 的调用CookieHelper.setAcceptThirdPartyCookies()
。这是代码:
public static void setAcceptThirdPartyCookies(WebView webView, boolean accept) {
CookieManager cookieManager = CookieManager.getInstance();
// This is a safeguard, in case you've disabled cookies elsewhere
if (accept && !cookieManager.acceptCookie()) {
cookieManager.setAcceptCookie(true);
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setAcceptThirdPartyCookies(webView, accept);
}
}
让一些人感到困惑的另一件事是 Facebook 开发设置中“有效 OAuth 重定向 URI”的配置。如果您在日志中看到这样的错误:
URL 被阻止:此重定向失败,因为重定向 URI 未在应用的客户端 OAuth 设置中列入白名单。确保客户端和 Web OAuth 登录已打开,并将所有应用程序域添加为有效 OAuth 重定向 URI。
...然后你会想看看这个答案:https ://stackoverflow.com/a/37009374
玩得开心!一个看似非常简单的问题的复杂解决方案。积极的一面是,Android 为开发人员提供了大量的控制权。