简体   繁体   English

使用JavascriptInterface时未捕获TypeError

[英]Uncaught TypeError when using a JavascriptInterface

I'm currently displaying a bunch of data to the user as HTML in a webview. 我目前正在webview中以HTML格式向用户显示一堆数据。 I have some links below each entry that should call a method in my app when clicked. 我在每个条目下面都有一些链接,单击时应该在我的应用程序中调用方法。 The Android WebView's javascript interface seems to be the best (only?) way of handling these things. Android WebView的javascript界面​​似乎是处理这些事情的最佳(唯一?)方式。 However, whenever I click the link, I get this error message: ERROR/Web Console(6112): Uncaught TypeError: Object [my namespace]@4075ff10 has no method 'edit' at [base URL]:55 但是,每当我单击链接时,都会收到以下错误消息: ERROR/Web Console(6112): Uncaught TypeError: Object [my namespace]@4075ff10 has no method 'edit' at [base URL]:55

I have the following interface declared: 我声明了以下接口:

public class JavaScriptInterface {
    Context context;

    JavaScriptInterface(Context c) {
        context = c;
    }

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

I then add it to my WebView: 然后我将它添加到我的WebView:

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

And, finally, I call this within my HTML as follows: 最后,我在我的HTML中调用它如下:

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

The real kicker is this all works when I'm debugging my app via the emulator or adb connection to my phone . 当我通过模拟器或adb连接到我的手机调试我的应用程序时 ,真正的踢球者就是这一切 When I build and publish the app, it breaks. 当我构建和发布应用程序时,它会中断。

I'm at my wits end. 我的智慧结束了。 Any help or advice would be greatly appreciated! 任何帮助或建议将不胜感激!

Same problem for my 2.3.3 mobile phone. 我的2.3.3手机也有同样的问题。 But as I knew one app that worked and another not, I was not happy with this workaround. 但是,由于我知道一个应用程序有效,另一个应用程序没有,我对此解决方法不满意。 And I find out the differnce of my two apps. 我发现了我的两个应用程序的不同之处。 The one with the broken JavaScriptInterface uses Proguard . JavaScriptInterface损坏的那个使用Proguard After a little search, I find a solution . 经过一番搜索后,我找到了解决方案

Short summary: interface JavascriptCallback, which is implemented by JavaScriptInterface and added rules for Proguard in proguard.conf: 简短摘要:接口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: proguard.cfg:

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

So, I'm pleased to say that my problem has been solved. 所以,我很高兴地说我的问题已经解决了。 Basically, it's a known bug in Gingerbread, and is present on my 2.3.4 device. 基本上,它是Gingerbread中的已知错误 ,并存在于我的2.3.4设备上。 After some head scratching, I found this workaround concocted by Jason Shah at PhoneGap. 经过一番头疼,我发现这个解决方法是由Jason Shah在PhoneGap上编造的。 The real kudos for this goes to him as my solution is a slightly modified version of the code in that post. 真正的荣誉归功于他,因为我的解决方案是该帖子中代码的略微修改版本。

The WebView WebView

In my onLoad method, I call the following function. 在我的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");
    }
}

There are several things going on here. 这里有几件事情。

  1. In contrast with the PhoneGap method, I'm using a startsWith comparison against the version string. 与PhoneGap方法相比,我正在使用与版本字符串的startsWith比较。 This is because Build.VERSION.RELEASE is 2.3.4 on my reference device. 这是因为我的参考设备上的Build.VERSION.RELEASE是2.3.4。 Rather than test against all releases in the 2.3 series, I'm comfortable painting all devices with one brushstroke. 而不是测试2.3系列中的所有版本,我很舒服用一笔画来绘制所有设备。

  2. javascriptInterface is a bool initialized to false . javascriptInterface是一个初始化为falsebool JavaScriptInterface, instantiated as iface, is the class that normally handles JS events in my WebView. JavaScriptInterface,实例化为iface,是通常在我的WebView中处理JS事件的类。

  3. ThreadViewClient is the meat and potatoes of my implementation. ThreadViewClient是我实现的肉和土豆。 It's where all the logic for handling the workaround occurs. 这是处理变通方法的所有逻辑发生的地方。

The WebViewClient WebViewClient

In the class ThreadViewClient (which extends WebViewClient), I first account for the fact that the js handler that Android normally attaches isn't here. 在类ThreadViewClient(扩展WebViewClient)中,我首先考虑到Android通常附加的js处理程序不在这里的事实。 This means that, if I want to use the same javascript calls from within my WebView, I need to duplicate the interface. 这意味着,如果我想在我的WebView中使用相同的javascript调用,我需要复制该接口。 This is accomplished by inserting custom handlers into the content of your website once it has loaded... 这是通过在加载后将自定义处理程序插入到您网站的内容中来实现的...

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

There's a lot to process there. 那里有很多东西需要处理。 In the javascript, I define an object handler that contains the functions that map to my js interface. 在javascript中,我定义了一个对象handler ,它包含映射到我的js接口的函数。 An instance of it is then bound to "Android", which is the same interface name as that used by non-2.3 implementation. 然后将其实例绑定到“Android”,这与非2.3实现使用的接口名称相同。 This allows for re-use of the code rendered within your webview content. 这允许重复使用webview内容中呈现的代码。

The functions take advantage of the fact that Android allows one to intercept all navigation that occurs within a WebView. 这些函数利用了Android允许拦截WebView中发生的所有导航的事实。 In order to communicate with the outside program, they alter the window location to one with a special signature. 为了与外部程序通信,他们将窗口位置改为具有特殊签名的窗口位置。 I'll get into this in a bit. 我稍后会谈到这一点。

Another thing I'm doing is concatenating the parameters of functions with more than one parameter. 我正在做的另一件事是将函数的参数与多个参数连接起来。 This allows me to reduce the code complexity within the location handler. 这允许我降低位置处理程序中的代码复杂性。

The location handler is also placed in ThreadViewClient... 位置处理程序也放在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;
}

Here I am intercepting all URL load events that occur in the WebView. 在这里,我拦截WebView中发生的所有URL加载事件。 If the destination URL contains a magic string, the app attempts to parse it to extract out the method call. 如果目标URL包含魔术字符串,则应用程序会尝试解析它以提取方法调用。 Rather than using the tokenizer to extract the individual parameters, I'm passing it to version of my longSignature method that can parse and handle it. 我没有使用标记生成器来提取单个参数,而是将其传递给可以解析和处理它的longSignature方法的版本。 This is detailed in the final part of this post. 这篇文章的最后部分详细介绍了这一点。

If, by the time it has exited the "javascriptInterfaceBroken" block, execution has not be returned to the caller, this method treats the URL loading action as a normal link clicked event. 如果在退出“javascriptInterfaceBroken”块时,执行没有返回给调用者,则此方法将URL加载操作视为正常链接单击事件。 In the case of my application I don't want to use the WebView for that, so I pass it off to the operating system via the ACTION_VIEW intent. 在我的应用程序的情况下,我不想使用WebView,所以我通过ACTION_VIEW意图将其传递给操作系统。

This is very similar to the implementation on Jason's blog. 这与Jason的博客上的实现非常相似。 However I am bypassing reflection for the most part. 但是我大部分时间都在绕过反思。 I was attempting to use the method in the block with reflection to handle all of my bound functions, but due to my JavaScriptInterface being a nested class I was unable to look into it from another. 我试图使用带有反射的块中的方法来处理我的所有绑定函数,但由于我的JavaScriptInterface是一个嵌套类,我无法从另一个查看它。 However, since I defined the interface within the main Activity scope, its methods can be called directly. 但是,由于我在主Activity范围内定义了接口,因此可以直接调用其方法。

Handling Concatenated Parameters 处理级联参数

Finally, in my JavaScriptInterface, I created a handler to deal with the case of a concatenated parameter... 最后,在我的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 polymorphism! 万岁多态!


And that's my solution! 那是我的解决方案! There's a lot of room for improvement, but, for now, this is sufficient. 还有很大的改进空间,但是,现在这已经足够了。 Sorry if some of my conventions have raised your hackles - this is my first Android app and I am unfamiliar with some of the best practices and conventions. 很抱歉,如果我的一些约定引起了你的讨厌 - 这是我的第一个Android应用程序,我不熟悉一些最佳实践和约定。 Good luck! 祝好运!

You have to annotate (@JavascriptInterface) methods in Java class that you want to make available to JavaScript. 您必须在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
}    }

Its worked for me. 它对我有用。 Try out this. 试试这个。

I've taken Jason Shah's and Mr S's implementation as the building block for my fix and improved upon it greatly. 我把Jason Shah和S先生的实现作为我修复的基石,并对其进行了很大的改进。

There's just far too much code to put into this comment I'll just link to it. 我只是链接到这个评论的代码太多了。

Key points are: 要点是:

  • Applies to all versions of Gingerbread (2.3.x) 适用于所有版本的Gingerbread(2.3.x)
  • Calls from JS to Android are now synchronous 从JS到Android的调用现在是同步的
  • No longer have to map out interface methods manually 不再需要手动映射接口方法
  • Fixed possibility of string separators breaking code 修复了字符串分隔符破坏代码的可能性
  • Much easier to change JS signature and interface names 更改JS签名和接口名称更容易

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

相关问题 使用loadGeoJSON时未捕获到TypeError - Uncaught TypeError when using loadGeoJSON 未捕获的TypeError:使用onload时出现类型错误 - Uncaught TypeError: Type error when using onload 在 arguments 中使用解构时出现未捕获的类型错误 - uncaught typeError when using destructuring in arguments 未捕获的TypeError:使用window [functionName]时不是函数 - Uncaught TypeError: is not a function when using window[functionName] 未捕获的TypeError:undefined在使用highcharts时不是函数 - Uncaught TypeError: undefined is not a function when using highcharts 使用轮播时,jQuery / Bootstrap“未捕获的TypeError” - Jquery / Bootstrap “Uncaught TypeError” when using Carousel Uncaught TypeError:使用ig.AnimationSheet时undefined不是函数 - Uncaught TypeError: undefined is not a function when using ig.AnimationSheet 使用fadeIn()时,jQuery“Uncaught TypeError:undefined不是函数”; - jQuery “Uncaught TypeError: undefined is not a function” when using fadeIn(); 未捕获的TypeError:使用带名称的JSON对象时,undefined不是一个函数 - uncaught TypeError: undefined is not a function when using JSON object with name 为什么在使用Flexslider时会出现“ Uncaught TypeError”? - Why am I getting “Uncaught TypeError” when using Flexslider?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM