如何正确地在多个活动中使用Google Plus登录?

42
什么是将Google+ API客户端生命周期与多活动应用程序的流程相结合的好/推荐方法?将活动依赖于onConnected API客户端方法以触发其功能,将其用作一次性的“激活”操作,或者可能完全是其他内容?
我目前在努力理解如何在具有多个活动的Android应用程序中正确使用Google+登录。
想法是,在第一阶段中,仅使用G +登录来验证用户并能够获取她的电子邮件,以发送通知和类似的东西。最终,我计划推出其他Google功能,例如地图或其他Google Play服务,因此我认为实现它已经很有用了。
但是,我的应用程序没有按预期运行,我已经将问题缩小到当存在多个活动时,我尚未理解G +登录应用程序周期的事实。
实施此身份验证方法的正确或推荐方式是什么?是否有可能指导我正确方向的某种模式?
例如,我找到了一个非常简单的 api客户端生命周期的很简单的图表,但这与应用程序流程有何关系?
最初,我有一个登录活动,在其中放置了登录按钮。按照Google的指导,我能够登录,当调用onConnected方法时,我启动Home Activity(类似于应用程序的仪表板或主屏幕)。
这有点起作用。例如,每个活动的onStart和onStop如何处理?每次为每个活动重新连接和重新验证api客户端是否是一个好主意?因此,也许实现所有这些的BaseActivity是一个好主意。另一个问题是,我应该使用相同的api客户端对象并以某种方式传递它,或者将其存储在Base Activity类中吗?还是每次都创建和初始化一个新的api客户端对象?
可以考虑只使用登录活动来对G+进行身份验证,然后只获取电子邮件并将其存储在本地数据库中,并将用户标记为“已认证”或“活动”等。这将防止每次关闭应用程序或连接中断时重新进行身份验证,甚至允许一些电池节省。
该应用程序实际上并没有使用G+发布或任何其他类似的功能。理想情况下,它应该可以离线使用,仅需要连接来进行初始身份验证或其他一次性事项。
非常感谢任何建议或指向正确方向的指针。
编辑:我已经阅读了我能找到的每一篇使用Google +的指南和教程,每个指南都从单个活动的角度解决了这个问题。我认为这是一个足够常见的问题,可以从模式或至少一般指南中受益。

https://dev59.com/x2Qn5IYBdhLWcg3wcWzM - dd619
谢谢,我已经看过那篇帖子了。虽然它说明你可以安全地使用多个API客户端实例,但我正在寻找更详细的建议或建议,特别是关于Activity对api客户端的依赖性。例如,每个Activity是否应该依赖于api客户端的onConnected()回调才能正常工作?如果用户撤销了对应用程序的访问权限,我应该多久重新连接客户端?等等。 - Acapulco
3个回答

41

每次活动重新连接都是完全可以的。大致上有3种方法,我见过人们实现这个功能:

  1. 在BaseActivity中实现,然后让其他活动继承它。这样每个活动都需要连接/断开连接,但代码仅存在于一个地方。
  2. 在Fragment中实现连接/断开连接,并将其包含在需要认证的活动中。如果您已经有一个不能继承的BaseActivity(例如某些游戏情况),则这很有帮助。
  3. 实现一个服务来连接/断开连接。如果需要登录,则可以触发BroadcastIntent或类似操作。

所有这些方法都能起作用,我在真实世界的应用程序中看到了它们的使用。需要记住的主要是将99%的逻辑(用户要么登录要么注销,并且你正在被通知)与相对较少的“在此时刻登录”用例分离开来。例如,您可能会经常触发onConnected/onConnectionFailed,但大多数情况下,您只是忽略了或者只是改变了应用程序的状态位。只有在屏幕上具有登录按钮的情况下,您才需要连接结果分辨率和onActivityResult之类的东西。把Google Play服务连接视为大多数时间询问用户状态而不是登录,那么您就应该没问题了。


我认为这就是我一直在寻找的东西。确认现实世界中的应用程序确实使用了其中任何一种方法。李的回答是一个很好的后续,但我猜这更接近我想要了解的内容。 - Acapulco
"为每个活动重新连接是完全可以的。但这将显示一个连接对话框,这将成为玩家的干扰因素。" - iappmaker
对于g+登录,只有在解决了userrecoverableauthexception时才会自动显示同意屏幕。 - Ian Barber

27
我同意Ian Barber的回答,但需要进一步解释,您的Activity应考虑为两种类型 - 解决登录问题的Activity和需要登录的Activity
大多数Activity与用户身份验证无关,您的应用程序中将具有相同的逻辑。它们将创建一个GoogleApiClient,该客户端连接到设备上运行的Google Play服务进程并读取用户的缓存签名状态 - 如果用户已登录,则返回onConnected(),如果未登录,则返回onConnectionFailed()。大多数Activity都希望在用户未登录时重置应用程序状态并启动LoginActivity。每个Activity应保留自己的GoogleApiClient实例,因为它是用于访问Google Play服务进程持有的共享状态的轻量级对象。例如,此行为可以封装在共享的BaseActivity类或共享的SignInFragment类中,但每个实例都应具有其自己的GoogleApiClient实例。
然而,您的LoginActivity需要以不同方式实现。它也应创建一个GoogleApiClient,但当它收到onConnected()表示用户已登录时,它应为用户启动适当的Activityfinish()。当您的LoginActivity收到onConnectionFailed()表示用户未登录时,您应尝试使用startResolutionForResult()解决登录问题。

1
感谢您的额外解释。基本上将活动分为这两种类型确实是有道理的。我只是担心在每个活动中使用连接/断开连接会有点过度,但似乎这是标准做法。 - Acapulco

10

0. 简述

对于急于求成的编程者,可以在GitHub上找到以下实现的工作版本。

在多个应用程序中多次重写登录活动代码后,简单(但不太优雅)的解决方案是将Google API客户端创建为Application类对象。但是,由于连接状态会影响UX流程,我从未对这种方法感到满意。

将我们的问题仅限于连接概念,我们可以考虑以下内容:

  1. 它隐藏了Google API客户端。
  2. 它有有限的状态。
  3. 它是(相当)独特的。
  4. 当前状态会影响应用程序的行为。

1. 代理模式

由于Connection封装了GoogleApiClient,因此它将实现ConnectionCallbacksOnConnectionFailedListener

@Override
public void onConnected(Bundle hint) {
    changeState(State.OPENED);
}

@Override
public void onConnectionSuspended(int cause) {
    changeState(State.CLOSED);
    connect();
}

@Override
public void onConnectionFailed(ConnectionResult result) {
    if (currentState.equals(State.CLOSED) && result.hasResolution()) {
        changeState(State.CREATED);
        connectionResult = result;
    } else {
        connect();
    }
}

活动可以通过connectdisconnectrevoke方法与Connection类通信,但它们的行为取决于当前状态。以下方法是状态机所需的:

protected void onSignIn() {
    if (!googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
        googleApiClient.connect();
    }
}

protected void onSignOut() {
    if (googleApiClient.isConnected()) {
        Plus.AccountApi.clearDefaultAccount(googleApiClient);
        googleApiClient.disconnect();
        googleApiClient.connect();
        changeState(State.CLOSED);
    }
}

protected void onSignUp() {
    Activity activity = activityWeakReference.get();
    try {
        changeState(State.OPENING);
        connectionResult.startResolutionForResult(activity, REQUEST_CODE);
    } catch (IntentSender.SendIntentException e) {
        changeState(State.CREATED);
        googleApiClient.connect();
    }
}

protected void onRevoke() {
    Plus.AccountApi.clearDefaultAccount(googleApiClient);
    Plus.AccountApi.revokeAccessAndDisconnect(googleApiClient);
    googleApiClient = googleApiClientBuilder.build();
    googleApiClient.connect();
    changeState(State.CLOSED);
}

2. 状态模式

这是一种行为模式,允许对象在其内部状态改变时改变其行为。GoF设计模式书描述了如何使用该模式来表示TCP连接(这也是我们的情况)。

状态机中的状态应该是单例的,而在Java中最简单的方法是创建名为State的Enum,如下所示:

public enum State {
    CREATED {
        @Override
        void connect(Connection connection) {
            connection.onSignUp();
        }
        @Override
        void disconnect(Connection connection) {
            connection.onSignOut();
        }
    },
    OPENING {},
    OPENED {
        @Override
        void disconnect(Connection connection) {
            connection.onSignOut();
        }
        @Override
        void revoke(Connection connection) {
            connection.onRevoke();
        }
    },
    CLOSED {
        @Override
        void connect(Connection connection) {
            connection.onSignIn();
        }
    };

void connect(Connection connection) {}
void disconnect(Connection connection) {}
void revoke(Connection connection) {}

Connection类保存上下文,即当前状态,该状态定义了Connection方法connectdisconnectrevoke的行为:

public void connect() {
    currentState.connect(this);
}

public void disconnect() {
    currentState.disconnect(this);
}

public void revoke() {
    currentState.revoke(this);
}

private void changeState(State state) {
    currentState = state;
    setChanged();
    notifyObservers(state);
}

3. 单例模式

由于不需要重复创建此类,我们将其提供为单例:

public static Connection getInstance(Activity activity) {
    if (null == sConnection) {
        sConnection = new Connection(activity);
    }

    return sConnection;
}

public void onActivityResult(int result) {
    if (result == Activity.RESULT_OK) {
        changeState(State.CREATED);
    } else {
        changeState(State.CLOSED);
    }
    onSignIn();
}

private Connection(Activity activity) {
    activityWeakReference = new WeakReference<>(activity);

    googleApiClientBuilder = new GoogleApiClient
           .Builder(activity)
           .addConnectionCallbacks(this)
           .addOnConnectionFailedListener(this)
           .addApi(Plus.API, Plus.PlusOptions.builder().build())
           .addScope(new Scope("email"));

    googleApiClient = googleApiClientBuilder.build();
    currentState = State.CLOSED;
}

4. 观察者模式

Connection 类扩展了 Java 的 Observable,因此可以有一个或多个活动观察状态的变化:

@Override
protected void onCreate(Bundle bundle) {
    connection = Connection.getInstance(this);
    connection.addObserver(this);
}

@Override
protected void onStart() {
    connection.connect();
}

@Override
protected void onDestroy() {
    connection.deleteObserver(this);
    connection.disconnect();
}

@Override
protected void onActivityResult(int request, int result, Intent data) {
    if (Connection.REQUEST_CODE == request) {
        connection.onActivityResult(result);
    }
}

@Override
public void update(Observable observable, Object data) {
    if (observable != connection) {
        return;
    }
    // Your presentation logic goes here...
}

2
非常有趣的方法,而且它似乎已经完整编写了。我会尝试一下并将其与我的方法进行比较。谢谢! - Acapulco
很棒的回答,看起来你非常了解设计模式。感谢你如此出色的努力。 - Shridutt Kothari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接