繁体   English   中英

使用JavascriptInterface时未捕获TypeError

[英]Uncaught TypeError when using a JavascriptInterface

我目前正在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连接到我的手机调试我的应用程序时 ,真正的踢球者就是这一切 当我构建和发布应用程序时,它会中断。

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

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

简短摘要:接口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>;
}

所以,我很高兴地说我的问题已经解决了。 基本上,它是Gingerbread中的已知错误 ,并存在于我的2.3.4设备上。 经过一番头疼,我发现这个解决方法是由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是一个初始化为falsebool JavaScriptInterface,实例化为iface,是通常在我的WebView中处理JS事件的类。

  3. ThreadViewClient是我实现的肉和土豆。 这是处理变通方法的所有逻辑发生的地方。

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]);
    }
}

万岁多态!


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

您必须在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
}    }

它对我有用。 试试这个。

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

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

要点是:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM