使用JavascriptInterface时未捕获TypeError

时间:2011-09-15 00:16:29

标签: javascript android android-webview

我目前正在webview中以HTML格式向用户显示一堆数据。我在每个条目下面都有一些链接,单击时应该在我的应用程序中调用方法。 Android WebView的javascript界面​​似乎是处理这些事情的最佳(唯一?)方式。但是,每当我单击该链接时,都会收到以下错误消息:ERROR/Web Console(6112): Uncaught TypeError: Object [my namespace]@4075ff10 has no method 'edit' at [base URL]:55

我声明了以下接口:

public class JavaScriptInterface {
    Context context;

    JavaScriptInterface(Context c) {
        context = c;
    }

    public void edit(String postid) {
        Log.d("myApp", "EDIT!");
        //do stuff
    }
}

然后我将它添加到我的WebView:

final WebView threadView = (WebView) findViewById(R.id.webViewThread);
threadView.getSettings().setJavaScriptEnabled(true);
threadView.addJavascriptInterface(new JavaScriptInterface(this), "Android");

最后,我在我的HTML中调用它如下:

<div class="post-actions">
    <div class="right">
        <a onClick="Android.edit('4312244');">Edit</a>
    </div>
</div>

当我通过模拟器或adb连接调试我的应用程序时,真正的踢球者这一切都有效。当我构建并发布应用程序时,它会中断。

我的智慧结束了。任何帮助或建议将不胜感激!

4 个答案:

答案 0 :(得分:18)

我的2.3.3手机也有同样的问题。 但是,由于我知道一个应用程序有效,另一个应用程序没有,我对此解决方法不满意。 我发现了我的两个应用程序的不同之处。 损坏的JavaScriptInterface使用Proguard 。 经过一番搜索后,我找到了solution

简短摘要:接口JavascriptCallback,由JavaScriptInterface实现,并在proguard.conf中为Proguard添加了规则:

public interface JavascriptCallback {

}

public class JavaScriptInterface implements JavascriptCallback {
    Context mContext;
    /** Instantiate the interface and set the context */
    JavaScriptInterface(Context c) {
        mContext = c;
    }
    /** Show a toast from the web page */
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }
}

proguard.cfg:

-keep public class YOURPACKAGENAMEHERE.JavascriptCallback
-keep public class * implements YOURPACKAGENAMEHERE.JavascriptCallback
-keepclassmembers class * implements YOURPACKAGENAMEHERE.JavascriptCallback {
    <methods>;
}

答案 1 :(得分:4)

所以,我很高兴地说我的问题已经解决了。基本上,它是姜饼中的known bug,并存在于我的2.3.4设备上。经过一番头疼,我发现这个workaround是由Jason Shah在PhoneGap上编造的。真正的荣誉归功于他,因为我的解决方案是该帖子中稍微修改过的代码版本。

WebView

在我的onLoad方法中,我调用以下函数。

private void configureWebView() {
    try {
        if (Build.VERSION.RELEASE.startsWith("2.3")) {
            javascriptInterfaceBroken = true;
        }
    } catch (Exception e) {
        // Ignore, and assume user javascript interface is working correctly.
    }

    threadView = (WebView) findViewById(R.id.webViewThread);
    threadView.setWebViewClient(new ThreadViewClient());
    Log.d(APP_NAME, "Interface Broken? " + javascriptInterfaceBroken.toString());
    // Add javascript interface only if it's not broken
    iface = new JavaScriptInterface(this);
    if (!javascriptInterfaceBroken) {
        threadView.addJavascriptInterface(new JavaScriptInterface(this), "Android");
    }
}

这里有几件事情。

  1. 与PhoneGap方法相比,我正在对版本字符串进行startsWith比较。这是因为我的参考设备上的Build.VERSION.RELEASE是2.3.4。而不是测试2.3系列中的所有版本,我很乐意用一笔画来绘制所有设备。

  2. javascriptInterface是bool初始化为false。 JavaScriptInterface,实例化为iface,是通常在我的WebView中处理JS事件的类。

  3. ThreadViewClient是我实现的捣蛋土豆。这就是处理变通方法的所有逻辑。

  4. WebViewClient

    在类ThreadViewClient(扩展WebViewClient)中,我首先考虑到Android通常附加的js处理程序不在这里的事实。这意味着,如果我想在我的WebView中使用相同的javascript调用,我需要复制该接口。这可以通过在加载后将自定义处理程序插入到您网站的内容中来实现......

    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        if (javascriptInterfaceBroken) {
            final String handleGingerbreadStupidity =
            "javascript:function shortSignature(id) { window.location='http://MyHandler:shortSignature:'+id; }; "
            + "javascript: function longSignature(text, username, forumnumber,threadnumber,pagenumber,postid) { var sep='[MyHandler]';"
                + "window.location='http://MyHandler:longSignature:' + encodeURIComponent(text + sep + username + sep + forumnumber + sep + threadnumber + sep + pagenumber + sep + postid);};"
          + "javascript: function handler() { this.shortSignature = shortSignature; this.longSignature = longSignature;}; "
          + "javascript: var Android = new handler();";
            view.loadUrl(handleGingerbreadStupidity);
        }
    }
    

    那里有很多东西需要处理。在javascript中,我定义了一个对象handler,其中包含映射到我的js接口的函数。然后将其实例绑定到“Android”,这与非2.3实现使用的接口名称相同。这允许重复使用webview内容中呈现的代码。

    这些功能利用了Android允许拦截WebView中发生的所有导航的事实。为了与外部程序通信,他们将窗口位置改为具有特殊签名的窗口位置。我稍后会谈到这一点。

    我正在做的另一件事是将函数的参数与多个参数连接起来。这允许我降低位置处理程序中的代码复杂性。

    位置处理程序也放在ThreadViewClient ...

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        Method sMethod = null;
        Log.d(APP_NAME, "URL LOADING");
        if (javascriptInterfaceBroken) {
            if (url.contains("MyHandler")) {
                StringTokenizer st = new StringTokenizer(url, ":");
              st.nextToken(); // remove the 'http:' portion
              st.nextToken(); // remove the '//jshandler' portion
              String function = st.nextToken();
              String parameter = st.nextToken();
              Log.d(APP_NAME, "Handler: " + function + " " + parameter);
              try {
                if (function.equals("shortSignature")) {
                    iface.shortSignature(parameter);
                } else if (function.equals("longSignature")) {
                    iface.longSignature(parameter);
                } else {
                    if (sMethod == null) {
                        sMethod = iface.getClass().getMethod(function, new Class[] { String.class });
                      }
                        sMethod.invoke(iface, parameter);
                }
            }
            //Catch & handle SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
              return true;
            }
        }
        startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
        return true;
    }
    

    这里我拦截了WebView中发生的所有URL加载事件。如果目标URL包含魔术字符串,则应用程序会尝试解析它以提取方法调用。我没有使用标记生成器来提取单个参数,而是将其传递给可以解析和处理它的longSignature方法的版本。这篇文章的最后部分详细介绍了这一点。

    如果在退出“javascriptInterfaceBroken”块时,执行没有返回给调用者,则此方法将URL加载操作视为正常链接单击事件。在我的应用程序的情况下,我不想使用WebView,所以我通过ACTION_VIEW意图将其传递给操作系统。

    这与Jason博客上的实施非常相似。但是我大部分时间都在绕过反思。我试图使用带有反射的块中的方法来处理我的所有绑定函数,但由于我的JavaScriptInterface是一个嵌套类,我无法从另一个查看它。但是,由于我在主Activity范围内定义了接口,因此可以直接调用其方法。

    处理连锁参数

    最后,在我的JavaScriptInterface中,我创建了一个处理串联参数的处理程序...

    public void longSignature(String everything) {
        try {
                everything = URLDecoder.decode(everything, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                Log.e(APP_NAME, e);
            }
        final String[] elements = everything.split("\\[MyHandler\\]");
        if (elements.length != 6) {
            Toast.makeText(getApplicationContext(), "[" + elements.length + "] wrong number of parameters!", Toast.LENGTH_SHORT).show();
        }
        else {
            longSignature(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]);
        }
    }
    

    Hooray多态性!


    那是我的解决方案!还有很大的改进空间,但是,现在这已经足够了。很抱歉,如果我的一些约定引起了你的讨厌 - 这是我的第一个Android应用程序,我不熟悉一些最佳实践和约定。祝你好运!

答案 2 :(得分:2)

您必须在Java类中注释要为JavaScript提供的(@JavascriptInterface)方法。

    public class JavaScriptInterface {
Context context;

@JavascriptInterface
JavaScriptInterface(Context c) {
    context = c;
}

@JavascriptInterface
public void edit(String postid) {
    Log.d("myApp", "EDIT!");
    //do stuff
}    }

它为我工作。试试这个。

答案 3 :(得分:0)

我把Jason Shah和S先生的实施作为我修复的基石,并对其进行了很大的改进。

我只是链接到这个评论的代码太多了。

要点是:

  • 适用于所有版本的Gingerbread(2.3.x)
  • 从JS到Android的调用现在是同步的
  • 不再需要手动映射接口方法
  • 修正字符串分隔符破坏代码的可能性
  • 更容易更改JS签名和界面名称