Uncaught TypeError при использовании JavascriptInterface

В настоящее время я показываю кучу данных для пользователя как HTML в веб-просмотре. У меня есть некоторые ссылки ниже каждой записи, которая должна вызывать метод в моем приложении при нажатии. Интерфейс javascript Android WebView кажется лучшим (только?) Способом обработки этих вещей. Тем не менее, всякий раз, когда я нажимаю ссылку, я получаю это сообщение об ошибке: 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 к моему телефону . Когда я создаю и публикую приложение, он ломается.

Я с ума сошел. Любая помощь или совет будут очень признательны!

Solutions Collecting From Web of "Uncaught TypeError при использовании JavascriptInterface"

Такая же проблема для моего 2.3.3 мобильного телефона. Но поскольку я знал, что одно приложение работает, а другое нет, я не был доволен этим обходным решением. И я выясняю разницу в моих двух приложениях. Тот, у кого сломанный JavaScriptInterface использует Proguard . После небольшого поиска я нахожу решение .

Краткое описание: интерфейс JavascriptCallback, который реализован JavaScriptInterface и добавлены правила для Proguard в proguard.conf:

 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. После некоторой царапины головы я нашел это обходное решение, созванное Джейсоном Шахом в 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, является классом, который обычно обрабатывает события JS в моем WebView.

  3. ThreadViewClient – это мясо и картофель моей реализации. Здесь происходит вся логика обработки обходного пути.

WebViewClient

В классе ThreadViewClient (который расширяет WebViewClient) я в первую очередь учитываю тот факт, что обработчик js, который обычно подключается к Android, здесь отсутствует. Это означает, что если я хочу использовать одни и те же вызовы javascript из моего WebView, мне нужно дублировать интерфейс. Это достигается путем добавления пользовательских обработчиков в содержимое вашего сайта после его загрузки …

 @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. Это позволяет повторно использовать код, отображаемый в вашем содержимом веб-просмотра.

Функции используют тот факт, что 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; } 

Здесь я перехватываю все события загрузки URL-адресов, которые происходят в WebView. Если целевой URL содержит волшебную строку, приложение пытается проанализировать ее, чтобы извлечь вызов метода. Вместо того, чтобы использовать токенизатор для извлечения отдельных параметров, я longSignature его в версию моего метода longSignature который может анализировать и обрабатывать его. Это подробно описано в заключительной части этой публикации.

Если к моменту выхода из блока «javascriptInterfaceBroken» выполнение не будет возвращено вызывающему, этот метод рассматривает действие загрузки URL как событие, связанное с обычной ссылкой. В случае с моим приложением я не хочу использовать WebView для этого, поэтому я передаю его в операционную систему с помощью намерения ACTION_VIEW.

Это очень похоже на реализацию в блоге Джейсона. Тем не менее, я в большинстве своем обойдусь размышлениями. Я пытался использовать метод в блоке с отражением для обработки всех моих связанных функций, но из-за того, что мой JavaScriptInterface был вложенным классом, мне не удалось заглянуть в него из другого. Однако, поскольку я определил интерфейс в пределах основной области действия, его методы можно вызвать напрямую.

Обработка конкатенированных параметров

Наконец, в моем 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, и я не знаком с некоторыми из лучших практик и соглашений. Удачи!

Вы должны аннотировать (@JavascriptInterface) методы в Java-классе, которые вы хотите сделать доступными для JavaScript.

  public class JavaScriptInterface { Context context; @JavascriptInterface JavaScriptInterface(Context c) { context = c; } @JavascriptInterface public void edit(String postid) { Log.d("myApp", "EDIT!"); //do stuff } } 

Это сработало для меня. Попробуйте это.

Я применил реализацию Джейсона Шаха и г-на S как строительный блок для моего исправления и значительно улучшил его.

В этот комментарий есть слишком много кода, который я просто свяжу с ним.

Ключевые моменты:

  • Применяется ко всем версиям Gingerbread (2.3.x)
  • Звонки с JS на Android теперь синхронны
  • Больше не нужно вручную определять методы интерфейса
  • Исправлена ​​возможность разрыва кода разделителей строк
  • Намного легче изменить JS подпись и имена интерфейсов