【问题标题】:Android webview cookie returns nullAndroid webview cookie 返回 null
【发布时间】:2013-10-14 15:43:22
【问题描述】:

我试图在 android 的 webview 中简单地设置和检索 cookie。我已经尝试了许多 cookie 管理器脚本来尝试让它工作。我启用了 JavaScript。

在三星 S3 和三星 Galaxy Tab 10.1 上运行应用程序时,cookie 似乎根本没有设置(android 4.1)。然而,在三星 Galaxy ace、HTC Desire Z 和安卓模拟器上运行该软件时,cookie 的设置和读取都非常好。

工作时,webview按预期返回字符串,不工作时,输出只是“null”; cookie 没有值/未设置。

我的具体案例也使用了滑动导航类,它是Actionbar Sherlock的扩展。

非常感谢任何帮助,我已经为此苦苦挣扎了好几个星期了。 谢谢。

HTML:

<html>
<head>
    <title>
    </title>
<script>
    function createCookie(name, value) 
    {
            var day  = (1 * 24 * 60 * 60 * 1000);
            var date = new Date();
            date.setTime(date.getTime() + (20 * 365 * day));
            var expires = "; expires=" + date.toGMTString();

            document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
            document.cookie = name + "=" + value + expires + "; path=/";
    }

    function readCookie(name) 
    {
        var nameEQ = name + "=";
        var ca = document.cookie.split(';');
        for(var i=0;i < ca.length;i++) {
            var c = ca[i];
            while (c.charAt(0)==' ') c = c.substring(1,c.length);
            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
        }
        return null;
    }

</script>
</head>
<body>
<h1 class="">
<script type="text/javascript">
    createCookie("test", "If this is working, it returns this string. If this is not working, it returns null.");
    document.write("test: " + readCookie("test"));
</script>
</body>
</html>

Java 代码:

公共类 MainActivity 扩展 SherlockActivity 实现 IsideNavigationCallback {

public static final String EXTRA_TITLE = "com.devspark.sidenavigation.sample.extra.MTGOBJECT";
public static final String EXTRA_RESOURCE_ID = "com.devspark.sidenavigation.sample.extra.RESOURCE_ID";
public static final String EXTRA_MODE = "com.devspark.sidenavigation.sample.extra.MODE";
public static String WebLoaded = "0";
public static String page = "signup.php";

private ImageView icon;
private SideNavigationView sideNavigationView;
private WebView engine;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    icon = (ImageView) findViewById(android.R.id.icon);
    sideNavigationView = (SideNavigationView) findViewById(R.id.side_navigation_view);
    sideNavigationView.setMenuItems(R.menu.side_navigation_menu);
    sideNavigationView.setMenuClickCallback(this);

    if (getIntent().hasExtra(EXTRA_TITLE)) {
        String title = getIntent().getStringExtra(EXTRA_TITLE);
        int resId = getIntent().getIntExtra(EXTRA_RESOURCE_ID, 0);
        setTitle(title);
        icon.setImageResource(resId);
        sideNavigationView.setMode(getIntent().getIntExtra(EXTRA_MODE, 0) == 0 ? Mode.LEFT : Mode.RIGHT);
    }

   //test
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    String domain = "localhost";

    CookieManager cookieManager = CookieManager.getInstance();
    cookieManager.setAcceptCookie(true);
    cookieManager.setCookie(domain, "name=value");
    cookieManager.setCookie(domain, "path=/");
    cookieManager.setCookie(domain, "HttpOnly");

    //enable cookies
    CookieManager.getInstance().setAcceptCookie(true);
    //navigates web engine, including on nav click
    engine = (WebView) findViewById(R.id.web_engine);  


    engine.loadUrl("file:///android_asset/" + page);
    //enable JavaScript support - disabled by default for some weird reason
    engine.getSettings().setJavaScriptEnabled(true);  
    engine.setWebViewClient(new WebViewClient());        






    //disables text selection
    engine.setOnLongClickListener(new View.OnLongClickListener() {
        public boolean onLongClick(View v) {
            return true;
        }
    });     
}

@Override
public void onPause()
{
    super.onPause();
    engine.getSettings().setJavaScriptEnabled(false);
}

@Override
public void onResume()
{
    super.onResume();
    engine.getSettings().setJavaScriptEnabled(true);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            sideNavigationView.toggleMenu();
            break;
        case R.id.mode_left:
            item.setChecked(true);
            sideNavigationView.setMode(Mode.LEFT);
            break;
        case R.id.mode_right:
            item.setChecked(true);
            sideNavigationView.setMode(Mode.RIGHT);
            break;

        default:
            return super.onOptionsItemSelected(item);
    }
    return true;
}

@Override
public void onSideNavigationItemClick(int itemId) {


    switch (itemId) {
        case R.id.side_navigation_menu_item1:
            invokeActivity(getString(R.string.title1), R.drawable.ic_android1);     
            page = "index.html";
            break;

        case R.id.side_navigation_menu_item2:
            invokeActivity(getString(R.string.title2), R.drawable.ic_android2);
            page = "test.html";
            break;

        case R.id.side_navigation_menu_item3:
            invokeActivity(getString(R.string.title3), R.drawable.ic_android3);
            break;

        case R.id.side_navigation_menu_item4:
            invokeActivity(getString(R.string.title4), R.drawable.ic_android4);
            break;

        case R.id.side_navigation_menu_item5:
            invokeActivity(getString(R.string.title5), R.drawable.ic_android5);
            break;

        default:
            return;
    }
    finish();
}

【问题讨论】:

  • 您的运行进程是否在 logcat 中收到相关错误/警告?您没有在 JS 中指定域,因此它将被解析为可能是 localhost 或特定 IP 地址的当前主机,我不确定它是否总是被视为安全的(同源)。另外,我不明白您在 Java 中对 cookie 的操作——它们是否会对 JavaScript 部分产生影响?
  • 我假设您已经查看了这篇文章:blog.winfieldpeterson.com/2013/01/17/… 和这个问题:stackoverflow.com/questions/1652850/…。你有没有尝试解决它们?结果如何?
  • 最近的安卓版本需要在cookie值中设置过期日期!否则 CookieManager 不会接受。

标签: java javascript android cookies webview


【解决方案1】:

经过一个多月的研究,我得出的结论是,在文件位于 localhost(直接从 assets 文件夹中读取)的 webview 中,无法在高于 2.3 的 android 版本中设置 cookie。

我尝试了许多使用 cookie 存储的替代方法,包括使用 HTML 的本地存储。我被引导相信这里的整个问题是安全性。如果我要在 localhost 上创建一个 cookie 或 localstorage,那么其他应用程序可以访问该存储,因此是一个重大的安全威胁。

我的最终问题是试图通过与 java 的双向通信来桥接 webview,并且在此过程中还有一个存储数据的地方。我的解决方案是利用 JavascriptInterface

思路如下:

  • 解析数据以在 javascript 中运行
  • Javascript 调用一个特殊的 javascript 函数,例如"setData(myDataName, myData)"
  • Java 有一个监听这个的函数,它会接受 javscript 设置的参数,也可以将值返回给 javascript。
  • 将数据解析为 Java 后,Java 会将其存储在资产中的一系列文件中。
  • 调用javascript函数“getData(myDataName)”时,Java以同种方法返回数据。

我发现这非常有用:Android JavascriptInterface Documentation, Handling JavascriptInterface in Android 2.3

我发现的唯一主要“挂断”是 Android 2.3(在 3.0 中修复)中的 fairly serious bug,它有效地破坏了 JavascriptInterface。上面的第二个链接描述了我找到的最佳解决方法。

这是一个如何让双向通信正常工作的示例(它非常混乱,但确实有效):

自定义网络接口类:

public class webViewInterface { 
    //@JavascriptInterface //for SDK 12+
    public String showToast(String myText) {
        Toast.makeText(context, myText, Toast.LENGTH_LONG).show();
        return myText;
    }
    }

主类: protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

engine = (WebView) findViewById(R.id.web_engine); 

context = this;
WebSettings webSettings = engine.getSettings();
webSettings.setJavaScriptEnabled(true);

engine.setWebViewClient(new WebViewClient());
engine.loadUrl("file:///android_asset/" + page);

boolean javascriptInterfaceBroken = false;
try {
        if (Build.VERSION.RELEASE.startsWith("2.3")) {
         javascriptInterfaceBroken = true;
        }
    } catch (Exception e) {

    // Ignore, and assume user javascript interface is working correctly.
    }

// Add javascript interface only if it's not broken
    // @TODO: write the workaround for < 2.3 devices
if (!javascriptInterfaceBroken) {
        engine.addJavascriptInterface(new webViewInterface(), "MainActivityInterface");
}   

//disables text selection
engine.setOnLongClickListener(new View.OnLongClickListener() {
    public boolean onLongClick(View v) {
        return true;
    }
});     
}

HTML:

<html>
<head>
</head>
<body>

<input type="button" value="Say hello" onClick="showAndroidToast();" />

<script type="text/javascript">
    function showAndroidToast() {
        document.write(MainActivityInterface.showToast("eureka!"));
    }
</script>

</body>
</html>

【讨论】:

    【解决方案2】:

    我遇到了同样的限制。我正在从资产文件夹加载本地 html 文件。在 2.2 模拟器上,cookie 被存储并且可以通过 JavaScript 检索。但是,在我的 4.1 设备上进行测试,本地页面设置的 cookie 不会持续存在。

    根据@kirgy 和@user3220983 的建议,我使用了Javascript 界面。

    webview.addJavascriptInterface(new JavaScriptInterface(this), "Android");
    
    public class JavaScriptInterface {         
        ...
    
        @JavascriptInterface
        public void setCookie(String name, String value) {
            Editor editor = activity.settings.edit();       
            editor.putString(name, value);
            editor.commit();
        }
    
        @JavascriptInterface
        public String getCookie(String name) {
            return activity.settings.getString(name, "");
        }   
    
        @JavascriptInterface
        public void deleteCookie(String name) {
            Editor editor = activity.settings.edit();       
            editor.remove(name);
            editor.commit();
        }
    

    注意:这些函数不提供完整的 cookie 功能(过期、路径),我将其发布为一个好的起点。

    【讨论】:

      【解决方案3】:

      您需要设置 cookie 的过期日期,否则 cookie 将不会被接受。

      类似这样的:

      document.setCookie = function(sName,sValue)
      {
          var oDate = new Date();
          oDate.setYear(oDate.getFullYear()+1);
          var sCookie = sName.escape() + '=' + escape(sValue) + ';expires=' + oDate.toGMTString() + ';path=/';
          document.cookie= sCookie;
      }
      

      祝你好运,希望它有效!

      【讨论】:

      • 您好,非常感谢你们。我试图实现这一点,但它对我没有用。但是在我发布这篇文章后的调查过程中,我发现问题是从 /assets 目录加载 HTML 文件时我无法设置 cookie - 当从 web 加载相同的文件时,cookie 设置得很好。你知道这是为什么吗?非常感谢您的帮助!
      • 我不知道为什么会这样,也许尝试重新加载看看是否有效。
      • 我相信这是因为 webview 中的安全原因,cookie 是针对 localhost 设置的。解决方案是针对远程主机设置 cookie,从而丢失本地托管文件,或者将 cookie 全部废弃。后面我做了,用一个javascript接口解决了存储数据的问题。
      【解决方案4】:

      我遇到了和 kirgy 一样的问题。我试图在显示本地内容的 Droid webview 中使用 cookie(使用 javascript)。它不会存储数据。但是,如果从同一 web 视图中的远程 url 运行相同的代码,则可以完美运行。在 java 中启用 cookie(使用 Android CookieManger)没有效果。

      幸运的是,我使用自定义 javascript 函数将我所有的 cookie 需求封装在本地 Web 应用程序中。使用 webview javascript 界面,在 droid 上运行时,我只是通过在 java 中处理它以虚拟方式管理我的 cookie。

      出于我的需要,我只需要会话存储,所以只需将 cookie 数据放在 java HashMap 中。我不担心过期日期或长期存储。如果您需要您的“虚拟 cookie”来实现这些细节,那么一旦数据通过 js 接口传入和传出 java,应该不难弄清楚。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多