使用 HttpClient 时跨活动使用 Cookie

Posted

技术标签:

【中文标题】使用 HttpClient 时跨活动使用 Cookie【英文标题】:Using Cookies across Activities when using HttpClient 【发布时间】:2011-08-13 17:47:22 【问题描述】:

我正在尝试一个简单的应用程序来读取网站的 html,并将其转换为在 android 中创建一个易于阅读的 UI(这是学习 android 的练习,而不是制作一个可用的应用程序)。我遇到的问题是跨活动保持用户会话,然后在召回后使用HttpClient 中的会话。

我想“正确地”这样做,推荐的方法似乎是使用CookieManager。但是我遇到了问题 - 我似乎找不到“正确”的方式从 CookieManager 获取 Cookie 并在以后的 HttpClient 实例化中在单独的活动中使用它。

当使用CookieManager 时,我可以保存Cookie,然后Cookie 在其他活动的范围内(参见代码 sn-p 2)。在请求页面时,我还没有找到稍后如何使用它(参见代码 sn-p 3)。

废话不多说,这里有一些代码。首先是我的登录操作和 Cookie 存储:

private OnClickListener loginActionListener = new OnClickListener() 

    public void onClick(View v) 
    
        EditText usernameTextView = (EditText) findViewById(R.id.Username);
        EditText passwordTextView = (EditText) findViewById(R.id.Password);
        String username = usernameTextView.getText().toString();
        String password = passwordTextView.getText().toString();

        try 
            HttpPost postMethod = new HttpPost(URI);                
            HttpParams params   = new BasicHttpParams();

            params.setParameter("mode", "login");
            params.setParameter("autologin", true);
            params.setParameter("username", username);
            params.setParameter("password", password);
            postMethod.setParams(params);

            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpResponse response        = httpClient.execute(postMethod);
            List<Cookie> cookies = httpClient.getCookieStore().getCookies();

            if(cookies != null)
            
                for(Cookie cookie : cookies)
                
                    String cookieString = cookie.getName() + "=" + cookie.getValue() + "; domain=" + cookie.getDomain();                        
                    CookieManager.getInstance().setCookie(cookie.getDomain(), cookieString);  
                
            
            CookieSyncManager.getInstance().sync();

            Intent intent = new Intent(v.getContext(), IndexAction.class);
            startActivity(intent);
     catch (Exception e) ...

决定是让用户登录还是进入索引的启动活动如下。从这段代码可以看出cookie在作用域内,可以读取:

public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    CookieSyncManager.createInstance(this);

    if(CookieManager.getInstance().getCookie(URI) == null)
    
        Intent intent = new Intent(this, LoginAction.class);
        startActivity(intent);
    
    else
    
        Intent intent = new Intent(this, IndexAction.class);
        startActivity(intent);
    

但是从我的代码阅读索引页面我希望你能提出我所缺少的:

@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    CookieSyncManager.createInstance(this);

    try
    
            HttpGet getMethod = new HttpGet(URI_INDEX);  

            HttpParams params   = new BasicHttpParams();                        
            HttpConnectionParams.setConnectionTimeout(params, 30000);
            HttpConnectionParams.setSoTimeout(params, 30000);

            // This code results in a ClassCastException, I'm assuming i've found a red herring with this solution.
            // HttpContext localContext = new BasicHttpContext();    
            // localContext.setAttribute(ClientContext.COOKIE_STORE, CookieManager.getInstance().getCookie(URI));

            DefaultHttpClient httpClient = new DefaultHttpClient(params);
            HttpResponse response        = httpClient.execute(getMethod);

            if(response.getStatusLine().getStatusCode() > 299 && response.getStatusLine().getStatusCode() < 400)
            
                // Not logged in doesn't give a redirect response. Very annoying.
            

            final char[] buffer = new char[0x10000];
            StringBuilder out = new StringBuilder();
            Reader in = new InputStreamReader(response.getEntity().getContent(), "UTF-8");
            int read = 0;
            while (read>=0)
            
              read = in.read(buffer, 0, buffer.length);
              if (read>0) 
                out.append(buffer, 0, read);
              
            

            String returnString = out.toString();
     catch (ClientProtocolException e) ...

execute(getMethod) 上的 HttpClient 没有使用 Cookie(在调试中仔细检查)来拉回页面。如果有人能填补我知识的这个空白,那就太好了。

提前致谢。

编辑

当注释代码被重新添加(将httpClient.execute(getMethod) 方法更改为httpClient.execute(getMethod, localContext))时,会产生此轨迹跟踪 - 假设是因为我正在用Cookie 填充属性ClientContext.COOKIE_STORE 而不是String CookieStore:

*org.apache.http.client.protocol.RequestAddCookies.process(RequestAddCookies.java:88), org.apache.http.protocol.BasicHttpProcessor.process(BasicHttpProcessor.java:290), org.apache.http.protocol.HttpRequestExecutor.preProcess(HttpRequestExecutor.java:160), org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:401)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555), org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487), 
com.testapp.site.name.IndexAction.onCreate(IndexAction.java:47), 
android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047), 
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611), 
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663), 
android.app.ActivityThread.access$1500(ActivityThread.java:117), 
android.app.ActivityThread$H.handleMessage(ActivityThread.java:931), 
android.os.Handler.dispatchMessage(Handler.java:99), 
android.os.Looper.loop(Looper.java:123), 
android.app.ActivityThread.main(ActivityThread.java:3683), 
java.lang.reflect.Method.invokeNative(Native Method), 
java.lang.reflect.Method.invoke(Method.java:507), 
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839), 
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597), 
dalvik.system.NativeStart.main(Native Method)*

【问题讨论】:

您能提供异常的堆栈跟踪吗?这个问题可能会有所帮助:***.com/questions/1652850/… 可能还有***.com/questions/678630/… httpClient.getCookieStore().addCookie(Cookie) 需要一个 Cookie 对象并且 CookieManager.getInstance().getCookie(URI) 返回一个字符串并没有帮助。 Cookie 是一个接口,我不知道要使用什么子类型。 【参考方案1】:

CookieManager 由 Java 的内部 HTTP 客户端使用。它与 Apache HttpClient 无关。

在您的代码中,您总是为每个请求创建一个 HttpClient 的新实例,因此创建一个新的 CookieStore 实例,显然,一旦该 HttpClient 实例退出,它就会与存储的所有 cookie 一起被垃圾收集范围。

你应该

(1) 对所有逻辑相关的HTTP请求重复使用HttpClient的同一实例,并在所有逻辑相关的线程之间共享(这是使用Apache HttpClient的推荐方式) p>

(2) 或者,至少,在逻辑相关的线程之间共享CookieStore的同一个实例

(3) 或者,如果您坚持使用 CookieManager 来存储您的所有 cookie,创建一个由 CookieManager 支持的自定义 CookieStore 实现

【讨论】:

我特别希望能够只传递我的应用程序所需的内容(尽管 /how/ 上的示例代码会很受欢迎)。在这种情况下,我在每个Activity 中唯一需要的是Cookie。跨活动使用Cookies 的推荐方法(以及我读过的跨进程)是使用CookieManager,这就是为什么我想用它来保存、查看然后在内部重用Cookies其中。我会尽快发布我自己的解决方案来解决这个问题,并希望能解释其中的任何弱点。 我还将考虑更改此代码以使用 Java 的内部 HttpClient 并发布解决方案以及我在其中看到的任何优势。 这个答案太棒了 感谢您将“推荐方法”与“自定义方法”进行比较。 我正在开发一个全新的 android 应用程序并将使用您的第一种方法 - 共享相同的 HttpClient。将在自定义 BaseActivity 上有一个静态 HttpClient,我的所有活动都将扩展 BaseActivity。这样我对所有屏幕都有相同的 HttpClient 。谢谢【参考方案2】:

(正如承诺的解决方案。我仍然不喜欢它,并且觉得我错过了这样做的“正确”方式,但是它有效。)

您可以使用CookieManager 使用以下代码注册您的 cookie(从而使这些 cookie 在应用之间可用):

将 cookie 保存到 CookieManager:

List<Cookie> cookies = httpClient.getCookieStore().getCookies();

if(cookies != null)

    for(Cookie cookie : cookies)
    
        String cookieString = cookie.getName() + "=" + cookie.getValue() + "; domain=" + cookie.getDomain();                        
        CookieManager.getInstance().setCookie(cookie.getDomain(), cookieString);  
    

CookieSyncManager.getInstance().sync();

检查指定域上的 cookie: if(CookieManager.getInstance().getCookie(URI_FOR_DOMAIN)

重建 HttpClient 的值:

DefaultHttpClient httpClient = new DefaultHttpClient(params);
String[] keyValueSets = CookieManager.getInstance().getCookie(URI_FOR_DOMAIN).split(";");
for(String cookie : keyValueSets)

    String[] keyValue = cookie.split("=");
    String key = keyValue[0];
    String value = "";
    if(keyValue.length>1) value = keyValue[1];
    httpClient.getCookieStore().addCookie(new BasicClientCookie(key, value));

【讨论】:

重要的是还要设置新的 BasicClientCookie(key, value) 的域,这样更好,比如 BasicClientCookie c = new BasicClientCookie(key, value); c.setDomain(URI_FOR_DOMAIN); httpClient.getCookieStore().addCookie(c); 这为我节省了一些头发。另外,我很想知道自从您发布此内容以来您是否设法找到了更好的方法。谢谢:) 我得到:The method getCookieStore() is undefined for the type HttpClient 注意是DefaultHttpClient【参考方案3】:

在我的应用程序服务器中,想要使用相同的会话和相同的 cookie... 经过几个小时的“谷歌搜索”和痛苦的头痛后,我只是将 cookie 保存到 SharedPreference 或只是放入一些对象并再次使用相同的 cookie 设置 DefaultHttpClient ... onDestroy 只需删除 SharedPreferences ...就是这样:

    首先将 SerializableCookie 类复制到您的包中: SerializableCookie

看下面的例子:

public class NodeServerTask extends AsyncTask<Void, Void, String> 
private DefaultHttpClient client;


protected String doInBackground(Void... params) 
  HttpPost httpPost = new HttpPost(url);
    List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
    urlParameters.add(nameValuePair);
    try 
        httpPost.setEntity(new UrlEncodedFormEntity(urlParameters));
        List<Cookie> cookies = loadSharedPreferencesCookie();
        if (cookies!=null)
            CookieStore cookieStore = new BasicCookieStore();
            for (int i=0; i<cookies.size(); i++)
                cookieStore.addCookie(cookies.get(i));
            client.setCookieStore(cookieStore);
        
        HttpResponse response = client.execute(httpPost);
        cookies = client.getCookieStore().getCookies();

        saveSharedPreferencesCookies(cookies);

//保存和加载cookies的两种方法...

 private void saveSharedPreferencesCookies(List<Cookie> cookies) 
    SerializableCookie[] serializableCookies = new SerializableCookie[cookies.size()];
    for (int i=0;i<cookies.size();i++)
        SerializableCookie serializableCookie = new SerializableCookie(cookies.get(i));
        serializableCookies[i] = serializableCookie;
    
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    SharedPreferences.Editor editor = preferences.edit();
    ObjectOutputStream objectOutput;
    ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
    try 
        objectOutput = new ObjectOutputStream(arrayOutputStream);


        objectOutput.writeObject(serializableCookies);
        byte[] data = arrayOutputStream.toByteArray();
        objectOutput.close();
        arrayOutputStream.close();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Base64OutputStream b64 = new Base64OutputStream(out, Base64.DEFAULT);
        b64.write(data);
        b64.close();
        out.close();

        editor.putString("cookies", new String(out.toByteArray()));
        editor.apply();
     catch (IOException e) 
        e.printStackTrace();
    


private List<Cookie> loadSharedPreferencesCookie() 
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    byte[] bytes = preferences.getString("cookies", "").getBytes();
    if (bytes.length == 0 || bytes.length==2)
        return null;
    ByteArrayInputStream byteArray = new ByteArrayInputStream(bytes);
    Base64InputStream base64InputStream = new Base64InputStream(byteArray, Base64.DEFAULT);
    ObjectInputStream in;
    List<Cookie> cookies = new ArrayList<Cookie>();
    SerializableCookie[] serializableCookies;
    try 
        in = new ObjectInputStream(base64InputStream);
        serializableCookies = (SerializableCookie[]) in.readObject();
        for (int i=0;i<serializableCookies.length; i++)
            Cookie cookie = serializableCookies[i].getCookie();
            cookies.add(cookie);
        
        return cookies;
     catch (IOException e) 
        e.printStackTrace();
     catch (ClassNotFoundException e) 
        e.printStackTrace();
    
    return null;

谷歌运气

【讨论】:

SerializableCoockie.class 文件在哪里 我没有得到文件。请把它的链接发给我。我真的需要它。 您可以从here下载SerializableCoockie.class【参考方案4】:

我遇到了同样的问题。由于我使用的是 Volley 而不是直接使用 HTTPClient,因此 HTTPClient 的 Singleton 似乎不是正确的解决方案。但是使用相同的想法,我只是将 CookieManager 保存在我的应用程序单例中。 因此,如果 app 是我的应用程序单例,那么在每个活动的 onCreate() 中添加:

    if (app.cookieManager == null) 
        app.cookieManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL);
    
    CookieHandler.setDefault(app.cookieManager);

为了更好地做到这一点,我的所有活动都是 MyAppActivity 的子类,所以我所要做的就是将它放在 MyAppActivity 的 onCreate 中,然后我的所有活动都继承此功能。现在我的所有活动都使用相同的 cookie 管理器,并且我的 Web 服务会话由所有活动共享。简单而且效果很好。

【讨论】:

【参考方案5】:

我想最好的方法之一是将单例模式应用于您的 HTTPClient,这样您就只有一个实例!

import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;

public class myHttpClient 
    private static HttpClient mClient;
    private myHttpClient() ;
    public static HttpClient getInstance() 
        if(mClient==null)
            mClient = new DefaultHttpClient();
        return mClient;
    

【讨论】:

HttpClient类的单个实例无法完成异步请求【参考方案6】:

cookie 存储的一个好的解决方案是遵循 ThreadLocal 模式以避免随机存储 cookie。

这将有助于保持唯一的 CookieStore。有关此模式的更多说明,您可以点击这个有用的链接。

http://veerasundar.com/blog/2010/11/java-thread-local-how-to-use-and-code-sample/

干杯

【讨论】:

以上是关于使用 HttpClient 时跨活动使用 Cookie的主要内容,如果未能解决你的问题,请参考以下文章

使用vue-cli开发时跨域问题

Java通过httpclient获取cookie模拟登录

连接保持活动在某些主机上无法与 System.Net.Http.HttpClient 一起使用

Android httpclient 登录并使用 cookie 进行进一步处理

.NET HttpClient 在多次请求后挂起(除非 Fiddler 处于活动状态)

请求错误:在 --prod 模式下构建时,Angular 中的“推荐人策略:严格原产地时跨原产地”