在无谷歌服务的安卓手机上实现谷歌登录

772 阅读6分钟

在无谷歌服务的安卓手机上实现谷歌登录

事情的起因

因为公司的app是在google store上架的,然后我们的国产机售卖的时候没有预装谷歌服务。于是用户就因为无法使用谷歌登录我司的app一直在被刷差评。我的评价是狗屁理由,折磨我😣。

领导一看🤔,这样不行🙅‍♂️。我们必须在没有谷歌服务的机器上能够支持谷歌登录。说了一句肯定有办法能够实现这个功能的,让我多看看。

images (1).jpeg


这个项目是用flutter写的app,但感觉这个思路感觉是安卓都可以用的。下面会贴出flutter和h5的代码。

过程

我先是问了一问大佬,并得到回答谷歌插件必须在有谷歌登录的机子上才能使用。我记得在没有谷歌服务的手机上调用是会报一个12500的错误,具体的我忘了。

使用webview的方法实现

images.jpeg

h5实现谷歌登录

关于使用h5实现谷歌登录,网上都能查到,不多bb,直接贴代码。

其中个人认为比较值得注意的地方有plugin_name,我开了Google People API。{prompt:'select_account'}添加这个参数可以在点击登录的时候选择账号。否则之前已经登过了,会直接登录。

能够在网页上能够成功登录,并且在控制台看到输出信息,应该就没有问题了。

我测试的地址:yuwenhao.site/google/

<html lang="en">

<head>

    <meta charset="UTF-8">
</head>

<body>

<div style="width: 100px;height: 100px;background-color: red" onclick="openwindow()"></div>

<button id="customBtn" type="button">Google登录</button>

<div id="name"></div>

<button type="button" onclick="signOut();">Sign out</button>


<script src="https://accounts.google.com/gsi/client" async defer></script>

<script src="https://apis.google.com/js/api:client.js" async defer></script>

<script src="https://apis.google.com/js/platform.js?onload=onLoadCallback" async defer></script>

<script>

    var googleUser = {};
    var auth2

    window.onLoadCallback = function () {

        gapi.load('auth2', function () {

            // Retrieve the singleton for the GoogleAuth library and set up the client.

            auth2 = gapi.auth2.init({

                client_id: '*******.apps.googleusercontent.com', //客户端ID

                cookiepolicy: 'single_host_origin',

                scope: 'profile', //可以请求除了默认的'profile' and 'email'之外的数据

                plugin_name: 'Google People API'

            });

            attachSignin(document.getElementById('customBtn'));

        });

    };

    function attachSignin(element) {

        auth2.attachClickHandler(element, {prompt:'select_account'},

            function (googleUser) {

                document.getElementById('name').innerText = "Signed in: " + googleUser.getBasicProfile().getName();

                var profile = auth2.currentUser.get().getBasicProfile();

                var authResponse = auth2.currentUser.get().getAuthResponse();

                console.log(authResponse.getAccessToken)

                console.log('======')

                console.log(auth2.currentUser.get())

                console.log(auth2.currentUser.get('access_token'))

                console.log('ID: ' + profile.getId());

                console.log('Full Name: ' + profile.getName());

                console.log('Given Name: ' + profile.getGivenName());

                console.log('Family Name: ' + profile.getFamilyName());

                console.log('Image URL: ' + profile.getImageUrl());

                console.log('Email: ' + profile.getEmail());

                window.flutter_inappwebview.callHandler('handlerFoo', profile,authResponse).then(function (result) {

                    console.log(profile.getId());

                });

                // window.location.href='https://www.baidu.com/';

            }, function (error) {

                console.log(JSON.stringify(error, **undefined**, 2));

            });

    }


    function openwindow() {

        window.open('https://www.baidu.com/')

    }


    //注销

    function signOut() {

        var auth2 = gapi.auth2.getAuthInstance();

        auth2.signOut().then(function () {

            alert('用户注销成功');

        });

    }

</script>

</body>

</html>


flutter端 完成谷歌登录

在完成了h5的google登录之后,使用webview打开谷歌登录会出现403的问题。

image.png 这里需要修改一下webview的useragent。

'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36',

这样就可以解决403的问题。


接下来才是问题的重点。

因为当你点击google登录的按钮时,在网页上查看你是可以明确的看到,他会弹出一个新的tab或者弹出一个窗口来让你进行登录,在完成登录之后,之前点击登录页面的登录成功代码才会继续执行下去。

当我使用弹出窗口的模式进行谷歌登录时,我发现他在完成谷歌登录流程后一直处于下面这种状态。这应该就是因为之前的登录页面已经没了,而他还在苦苦的给登录页面发送消息。(好家伙这是什么虐恋💔)

image.png

尝试使用重定向时,发现登录成功后的代码没有执行到(废话,网页都没了,什么给你执行😒)。


最后换了个思路才最终成功。这里非常感谢大佬的翻译的这篇文章,救我于水火之中。

贴个地址:juejin.cn/post/686929…

使用webview插件: flutter_inappwebview

这里面提到的这个章节(如何管理用target="_blank "或 "window.open "打开的弹出窗口。),对我帮助很大。

里面就提到了安卓的webview能够支持多个弹框,只需将 supportMultipleWindowsjavaScriptCanOpenWindowsAutomatically 设置为true。再在onCreateWindow中写入处理的方法,就能打开一个新的网页。

当用户点击target="_blank "的链接或通过使用window.open的JavaScript代码来管理弹出窗口时,你可以使用onCreateWindow事件。在Android上,为了能够允许这个事件,你需要将supportMultipleWindows选项设置为true。另外,为了能够允许使用JavaScript,你需要将javaScriptCanOpenWindowsAutomatically设置为true。

如果你想管理这些请求,你应该从这个事件中返回true,否则,这个事件的默认实现什么也不做,因此返回false

CreateWindowRequest代表导航请求,它包含一个windowId,可以用来创建,例如,一个新的InAppWebView实例。这个windowId被本地代码用来映射该请求和用于管理该请求的WebView。 另外,CreateWindowRequest包含了请求的url(在Android上,如果弹出窗口是用window.open的JavaScript打开的,它将是null),但是如果你需要维护Window JavaScript对象引用(用window.open方法创建的),例如,调用window.close方法,那么你应该用windowId创建新的WebView,而不使用url。 下面是一个简单的例子,当用户点击链接时,会显示一个AlertDialog

最后贴一下dart的完整代码

import 'package:coupert/Util/logger_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/flutter_svg.dart';
// import 'package:flutter_screenutil/size_extension.dart';

class GoogleLoginWebView extends StatefulWidget {
  final ValueChanged<Map> onchange;

  const GoogleLoginWebView({Key key, this.onchange}) : super(key: key);

  @override
  State<GoogleLoginWebView> createState() => _GoogleLoginWebViewState();
}

class _GoogleLoginWebViewState extends State<GoogleLoginWebView> {
  InAppWebViewController _webViewController;
  InAppWebViewController _webViewPopupController;
  BuildContext popupContext;
  InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
      crossPlatform: InAppWebViewOptions(
          userAgent:
              'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36',
          javaScriptEnabled: true,
          useShouldOverrideUrlLoading: true,
          useOnLoadResource: true,
          cacheEnabled: false,
          javaScriptCanOpenWindowsAutomatically: true,
      ),
      android: AndroidInAppWebViewOptions(
        useHybridComposition: true,
        supportMultipleWindows: true,
      ));

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          height: 675.h - 48,
          child: Column(
            children: [
              Container(
                height: 50,
                padding: EdgeInsets.only(left: 17, right: 17),
                decoration: BoxDecoration(
                    color: Color(0xFFEA5A4F),
                    borderRadius: BorderRadius.only(
                        topLeft: Radius.circular(30),
                        topRight: Radius.circular(30))),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    GestureDetector(
                      onTap: () {
                        Navigator.of(context).pop();
                      },
                      child: SvgPicture.asset(
                        "images/Account/account_login_close.svg",
                        width: 18.5,
                        height: 18.5,
                      ),
                    ),
                    Text(
                      'Google sign in',
                      style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                          color: Colors.white),
                    ),
                    Container(
                      height: 18.5,
                      width: 18.5,
                    )
                  ],
                ),
              ),
              Expanded(child: InAppWebView(
                initialUrlRequest:
                URLRequest(url: Uri.parse('https://yuwenhao.site/google/')),
                initialOptions: options,
                onWebViewCreated: (InAppWebViewController controller) {
                  _webViewController = controller;
                  _webViewController.addJavaScriptHandler(
                      handlerName: 'handlerFoo',
                      callback: (args) {
                        print('webViewController.addJavaScriptHandler=======');
                        logger.d('webViewController.addJavaScriptHandler=======');
                        logger.d(args);
                        if(popupContext!=null){
                          Navigator.pop(popupContext);
                          popupContext=null;
                        }
                        Navigator.pop(context,args);
                      });
                },
                onCreateWindow: (controller, createWindowRequest) async {
                  print("onCreateWindow");
                  showModalBottomSheet(
                      context: context,
                      backgroundColor: Colors.transparent,
                      isScrollControlled: true,
                      builder: (context) {
                        popupContext = context;
                        return Container(
                          height: 675.h - 48,
                          child: InAppWebView(
                            windowId: createWindowRequest.windowId,
                            initialOptions: options,
                            onWebViewCreated:
                                (InAppWebViewController controller) {
                              _webViewPopupController = controller;
                            },
                            onLoadStart: (controller, url) {
                              print("onLoadStart popup $url");
                            },
                            onLoadStop: (controller, url) {
                              print("onLoadStop popup $url");
                            },
                          ),
                        );
                      });
                  // showDialog(
                  //   context: context,
                  //   builder: (context) {
                  //     return AlertDialog(
                  //       content: Container(
                  //         width: MediaQuery.of(context).size.width,
                  //         height: 400,
                  //         child: InAppWebView(
                  //           // Setting the windowId property is important here!
                  //           windowId: createWindowRequest.windowId,
                  //           initialOptions: options,
                  //           onWebViewCreated:
                  //               (InAppWebViewController controller) {
                  //             _webViewPopupController = controller;
                  //           },
                  //           onLoadStart: (controller, url) {
                  //             print("onLoadStart popup $url");
                  //           },
                  //           onLoadStop: (controller, url) {
                  //             print("onLoadStop popup $url");
                  //           },
                  //         ),
                  //       ),
                  //     );
                  //   },
                  // );
                  return true;
                },
              ))
            ],
          ),
        ),
      ),
    );
  }
}

历程&感受:

说实话这个玩意儿,第一次跟我提是在5月份的时候,那时候我还被疫情关在家里面,吃一顿饿一顿的日子。查了一天,没有查到在无谷歌服务的安卓手机上怎么实现谷歌登录,最后在下午尝试了一下使用webview解决。卡在了403这个地方。

第二次省略。。。主要是不想做,在列举了其他app都不能完成谷歌登录,推掉了😁。

第三次是在上个星期跟我又提了一下,我开始查方法,这次倒是解决了出现403的这个问题。卡在了没有办法回调这里,然后就先做其他的需求了。

第四次就是在这个星期,其他需求都没了。任务很明确就解决谷歌登录。。。。mmp,泪崩😭。不过挺幸运的,两天后就解决了。

解决这个问题,前前后后大概花了我4天时间😮‍💨,掉了几十根头发(我感觉应该快100了)。以此文章纪念我逝去的时间和掉落的头发。

感受就是我对“人都是逼出来的”这句话有了更深的感受😮‍💨。。。

最后小弟,第一次写博客,有什么没讲清楚的可以再评论区讲一下。时间跨度有点长,我有可能没记全我还踩了哪些坑。

虽然不一定会看,但应该会看😂😂😂😂😂

图侵必删

9191de2e4c037c89.jpeg

还有一件事,转载请注明出处。