Uncaught TypeError при использовании JavascriptInterface - PullRequest
14 голосов
/ 15 сентября 2011

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

Я в своем уме. Буду признателен за любую помощь или совет!

Ответы [ 4 ]

18 голосов
/ 03 марта 2012

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

Итак, я рад сообщить, что моя проблема решена.По сути, это известная ошибка в 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, которые происходят ввеб-просмотр.Если целевой URL содержит волшебную строку, приложение пытается проанализировать ее, чтобы извлечь вызов метода.Вместо того, чтобы использовать токенизатор для извлечения отдельных параметров, я передаю его версии моего longSignature метода, который может анализировать и обрабатывать его.Это подробно описано в заключительной части этого поста.

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

Это очень похоже на реализацию в блоге Джейсона.Однако я обхожу размышления по большей части.Я пытался использовать метод в блоке с отражением для обработки всех моих связанных функций, но из-за того, что 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, и я не знаком с некоторыми из лучших практик и соглашений.Удачи!

2 голосов
/ 03 апреля 2015

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

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

0 голосов
/ 17 сентября 2013

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

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

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

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