Подтвердить что ты не робот

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 к моему телефону. Когда я создаю и публикую приложение, оно ломается.

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

4b9b3361

Ответ 1

Такая же проблема для моего мобильного телефона 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>;
}

Ответ 2

Итак, я рад сказать, что моя проблема решена. В основном, это известная ошибка в 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 not broken
    iface = new JavaScriptInterface(this);
    if (!javascriptInterfaceBroken) {
        threadView.addJavascriptInterface(new JavaScriptInterface(this), "Android");
    }
}

Здесь происходит несколько вещей.

  • В отличие от метода PhoneGap, я использую сравнение startsWith с версией. Это связано с тем, что Build.VERSION.RELEASE 2.3.4 на моем ссылочном устройстве. Вместо того, чтобы тестировать все версии в серии 2.3, мне удобнее рисовать все устройства одним мазком.

  • javascriptInterface - это bool, инициализированный до false. JavaScriptInterface, созданный как iface, является классом, который обычно обрабатывает события JS в моем WebView.

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

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

Ответ 3

Вы должны аннотировать (@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
}    }

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

Ответ 4

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

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

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

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