2009-12-27 51 views
9

我正尝试在Android应用中使用OAuth。我有它正常工作,但有时会在验证阶段遇到问题。在Android中,我启动浏览器供用户登录并进行身份验证。然后回调url将重定向回我的应用程序。Android中的OAuth实例状态

这是问题所在。我的应用程序有一个OAuth使用者和提供者作为我的主类的成员。当浏览器启动进行认证时,有时我的主Activity被放弃以节省内存。当回调url重新启动我的主Activity时,提供者和使用者都是新实例,因此在尝试向api发出请求时不起作用。如果主要Activiy在认证阶段没有被释放,那么一切正常,因为我仍然与原始消费者和提供者一起工作。

我试过使用onSaveInstanceState()和onRestoreInstanceState(),但没有成功。看起来onRestoreInstanceState()在我的回调url被处理时没有被调用。似乎直接进入onResume()。

在这种情况下坚持使用者和提供者的正确方法是什么?

回答

3

完整保存/恢复解决方案

除了request_tokentoken_secret,该isOauth10a()状态是供应商要恢复很重要。未来可能会有更多的国家信息。因此,我喜欢坚持和负载解决方案最好。

我延长了GrkEngineer的解决方案更加完整。它会保存/恢复提供者和使用者,处理所有异常,并在恢复时设置httpClient。

protected void loadProviderConsumer() 
{ 
    try { 
    FileInputStream fin = this.openFileInput("tmp_provider.dat"); 
    ObjectInputStream ois = new ObjectInputStream(fin); 
    provider = (CommonsHttpOAuthProvider) ois.readObject(); 
    provider.setHttpClient(httpClient); 
    ois.close(); 
    fin.close(); 

    fin = this.openFileInput("tmp_consumer.dat"); 
    ois = new ObjectInputStream(fin); 
    consumer = (CommonsHttpOAuthConsumer) ois.readObject(); 
    ois.close(); 
    fin.close(); 

    Log.d("OAuthTwitter", "Loaded state"); 
    } catch (FileNotFoundException e) { 
    e.printStackTrace(); 
    } catch (StreamCorruptedException e) { 
    e.printStackTrace(); 
    } catch (IOException e) { 
    e.printStackTrace(); 
    } catch (ClassNotFoundException e) { 
    e.printStackTrace(); 
    } 
} 

protected void persistProviderConsumer() 
{ 

    try { 
    FileOutputStream fout = this.openFileOutput("tmp_provider.dat", MODE_PRIVATE); 
    ObjectOutputStream oos = new ObjectOutputStream(fout); 
    oos.writeObject(provider); 
    oos.close(); 
    fout.close(); 

    fout = this.openFileOutput("tmp_consumer.dat", MODE_PRIVATE); 
    oos = new ObjectOutputStream(fout); 
    oos.writeObject(consumer); 
    oos.close(); 
    fout.close(); 

    Log.d("OAuthTwitter", "Saved state"); 
    } catch (FileNotFoundException e) { 
    e.printStackTrace(); 
    } catch (IOException e) { 
    e.printStackTrace(); 
    } 
} 

我测试了这个代码,它的工作原理。

3

您可以阅读我的old post here。通常我所做的是使用静态引用和使用WebView而不是独立浏览器显示验证表单

+0

我实际上是从你的旧帖开始的。很有帮助。我可能会尝试你的解决方案,但我想尝试一些其他的东西来帮助我更好地理解一些OAuth的东西。 我遇到了这个例子,http://code.google.com/p/jpoco/source/browse/trunk/jpoco-android-app/src/jpoco/android/MainActivity.java?r=346其中他将各种令牌存储为应用程序的SharedPreferences。如果我想做类似的事情,那么我需要保存哪些消费者和提供商的部分?我也认为这可能有助于在每次运行应用程序时不必重新进行身份验证。 – GrkEngineer 2009-12-28 02:56:03

+0

您不必重新进行身份验证,只需保存令牌并重新使用即可。 Twitter不会过期。你是对的 - 在我的例子中,我没有试图理解签名是如何完成的(例如),而仅仅是如何使用它。从我与DroidIn的经验以及我得到的反馈 - 它工作得很好。 另外,我看到有人使用密钥生成解决方案,其中用户身份验证后提供的密钥,他需要在应用程序签名页面输入回来,但感觉更痛苦 – Bostone 2009-12-28 05:13:08

+0

是的,你的例子真的很有帮助。我想我很想知道提供者/消费者字段是否可以使用SharedPreferences保存到磁盘。然后当回调返回时,您可以使用保存的字段重新填充提供者/消费者。 – GrkEngineer 2009-12-28 19:59:37

3

我通过将提供者对象持久化到文件来解决此问题。我正在使用路标库,提供者和使用者都是可序列化的。

protected void loadProvider() 
{ 
    FileInputStream fin = this.openFileInput("provider.dat"); 
    ObjectInputStream ois = new ObjectInputStream(fin); 
    this.provider = (DefaultOAuthProvider) ois.readObject(); 
    ois.close(); 
    consumer = this.provider.getConsumer(); 
} 

protected void persistProvider() 
{ 
    FileOutputStream fout = this.openFileOutput("provider.dat", MODE_PRIVATE); 
    ObjectOutputStream oos = new ObjectOutputStream(fout); 
    oos.writeObject(this.provider); 
    oos.close(); 
} 

我打电话坚持提供商刚刚推出用于身份验证的浏览器视图意图之前,我打电话provider.retrieveAccessToken()之前恢复的onResume提供商()。如果你在更多的位置调用persistProvider()和loadProvider(),你也可以让它保存适当的令牌后验证。这将消除重新认证的需要(只要令牌有效)。

我仍然希望知道提供者类中的哪些字段实际需要保留。序列化整个对象可能会有点缓慢。

+0

我增强了此解决方案的成功。看到我的答案。 – HRJ 2010-12-03 18:43:50

2

你只需要坚持consumer.getToken()consumer.getTokenSecret()

稍后,您可以简单地重新创建一个新的consumer(customerKey,customerKeySecret)consumer.setTokenWithSecret(token, tokenSecret)

什么棘手的是找出了以下几点:

  1. 使用CommonsHttpOAuthConsumerCommonsHttpOAuthProvider(在Android上)DefaultOAuthProvider将不起作用。

  2. 使用HttpURLConnection,您无法登录该查询参数的消息负载

+0

是的,这是关于CommonsHttpOAuthConsumer vs DefaultOAuthProvider的有用信息。至于保存消费者令牌和秘密,这是我在应用程序运行之间保存的所有内容。原始问题涉及到在启动浏览器首次登录时应用程序关闭和重新启动的问题。 – GrkEngineer 2010-06-26 11:16:55

1

我有同样的问题POST请求。所有你需要坚持的是在调用retrieveRequestToken之后得到的requestToken和tokenSecret。 在您的onResume()方法中,按here所述重新创建使用者和提供者对象。 这样你就不需要坚持整个消费者和提供者对象,并且仍然能够检索accessToken。

2

可能原始海报有维护实例状态的问题的原因是因为Android的默认行为是为每个新的意图启动一项新的活动。这就是为什么GrkEngineer在web回调之后没有看到onRestoreInstanceState被调用的原因。

将您的请求令牌存储为共享首选项是一种解决方案,以便它可以从OAuth Web回调后启动的新活动中获得。

我最初尝试使用共享首选项,它似乎工作正常。但是,我不认为这是最好的解决方案。理想情况下,您希望强制Android将回调传递给您的原始活动(我将在下面解释原因)。

我试过使用singleTask和singleInstance启动模式来完成这个部分成功,但它感觉不对,而且Android文档暗示这些模式不推荐用于一般用途。

经过对文档和测试的深入研究,我发现在创建意图时使用以下标志会导致Android将意图传递给活动的现有实例(如果它已被杀死,则重新创建它)。

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

为什么我需要让回调被原始活动处理的原因是我可以与android AccountManager集成。我用下面的示例让我开始:

http://developer.android.com/resources/samples/SampleSyncAdapter/index.html

一个用的AccountManager认证机制相结合的关键部分是传递到您的活动启动认证过程中AccountAuthenticatorResponse。

我发现实现这个最大的问题是保持对AccountAuthenticatorResponse对象的引用。这将传递到您的AuthenticatorActivity中,并且您需要在完成身份验证后调用它上的方法,以便标准帐户UI保持正确的状态。但是,我遇到了GrkEngineer最初碰到的同样的问题。当我在OAuth回调之后尝试重新启动OAuth身份验证器活动时,我总是收到一个新的实例,该实例已经失去对AccountAuthenticatorResponse对象的引用,并且我看不到任何方式来保留该对象。

关键是要使用上述的意图标志。

AuthenticatorActivity使用我的AbstractAccountAuthenticator的FLAG_ACTIVITY_NEW_TASK启动。它获取请求令牌(使用AsyncTask)并启动浏览器以要求用户进行授权。

OAuthCallbackHandlerActivity被注册来处理我的自定义回调方案。在用户授予访问权限后被调用时,它将使用标志Intent.FLAG_ACTIVITY_NEW_TASK |调用AuthenticatorActivity。 Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP意图。

这会导致我的原始AuthenticatorActivity被重新激活。 AccountAuthenticatorResponse对象仍然可用(就像我在OnSaveInstanceState中保存的请求令牌/秘密一样)。该活动现在可以获取访问令牌(再次使用AsyncTask),然后调用AccountAuthenticatorResponse对象的完成方法。

使这项工作的关键是使用我提到的意图标志,并确保AuthenticatorActivity在应用程序任务中启动,而不是在帐户管理器任务中启动。 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP只会导致活动的现有实例重用,如果它们在同一个任务中。因此,如果您想要返回的活动在其他任务中启动,那么原始实例将不会被重新使用。

我在一个使用开发工具的模拟器上测试了这个,立即终止了我的AuthenticatorActivity,这样我就可以测试娱乐过程了。它使用onSaveInstanceState/onRestoreInstanceState来处理请求令牌/秘密。而且我甚至不必担心恢复AccountAuthenticatorResponse对象。这是由Android本身恢复 - 魔术!