AndroidでShould not happen: no rect-based-test nodes foundエラー

Android で ActionBar に Tab を使って 2番目の Tab に WebView を貼り付けると、WebView の中のリンクをタップしてもタップされたアイテムが反転表示されない。
で、その際にログには「Should not happen: no rect-based-test nodes found」ってエラーが出てる。


前から気になっていたのだけど、やっと解決する方法が見つかったのでメモ。
(ただし、かな〜りレアケースっぽいので他の人の参考にはならないと思う。)

問題の起きる環境というか条件

Eclipse で新しい Android Application Project を作って、その際に Minimum Required SDKAPI 11 にして Navigation Type を Fixed Tabs + Swipe にする。


すると、タプが 3つあってスワイプでそれぞれを変更できるアプリケーションができあがる。


ここで、MainActivity を少し書き換えて 2番目のタブには 自前の Fragment を用意、そこに WebView を貼り付ける。
MainActivity の変更箇所は SectionsPagerAdapter の getItem の部分。
2 番目のタブに自前の Fragment(MyWebFragment)を用意しているだけ。

@Override
public Fragment getItem(int position) {
    // getItem is called to instantiate the fragment for the given page.
    // Return a DummySectionFragment (defined as a static inner class
    // below) with the page number as its lone argument.
    Fragment fragment;
    if (position==1) {
        fragment = new MyWebFragment();
    }
    else {
        fragment = new DummySectionFragment();
        Bundle args = new Bundle();
        args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
        fragment.setArguments(args);
    }
    return fragment;
}

で、自前の Fragment(MyWebFragment)はこんな感じ。
Fragment を拡張して WebView を貼り付けているだけ。

public class MyWebFragment extends Fragment {
    WebView mWebView;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.webview_fragment, container, false);
        mWebView = (WebView) v.findViewById(R.id.myWebView);
        mWebView.setWebViewClient(new WebViewClient(){
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String urlString) {
                return false;
            }
        });
        String url = "http://jp.msn.com/";
        mWebView.loadUrl(url);
        return v;
    }
}

で、これを起動するとちゃんと 2番目のタブで指定した URL が表示される。

のだけど、リンクをタップしても見た目が反応しない。


上の画面だと「ニュース」の下の項目(花火会場で爆発など)をタップしてもテキストが反転しない。
ただし、少しスクロールさせた後だと、ちゃんとテキストが反転してその後は常に問題がなくなる。


また、ログにはエラーメッセージが表示されている。

08-14 17:07:41.784: E/webcoreglue(4786): Should not happen: no rect-based-test nodes found

どうやら起きちゃいけないことが起きているらしい…。


というのが自分のハマった問題点なのだけど、Web 上では特に誰も言及していないところを見ると自分の実装の仕方がいけないんだろうな〜と思ったりもする…。


更に、この問題はどの Web サイトでも必ず起きるわけではないみたい。
普通に表示されるページもあったりするのでやっかい。


ただ、WebView を貼るのが 1番目のタブだと問題がない。
2 番目のタブの Fragment は、最初に 1番目のタブが表示された時にほぼ同時に実体化?していて、裏で WebView の中身も読み込まれてる。
どうもこれが悪さしているみたいなんだけど…。


ってか、Fragment に WebView 貼り付けるような場合は WebViewFragment を使えってのが正しい道なんだろうな〜。

Should not happen: no rect-based-test nodes foundを解決

で、このエラーメッセージで検索してサクっと見つかる情報はこれ。

というか、このページで全て解決!


ここの最初の回答の最後のコメントがこれ。

I met with John Reck, an engineer on the Android WebView team, a couple of days ago. He confirmed that this is a bug--basically a race condition where the webkit layout engine gets out of sync with the Android layout and starts mapping touch events to an incorrect coordinate system. Until the bug is fixed, calling onScrollChanged() to resync the layouts seems to be the best workaround. – Ian Ni-Lewis Apr 10 at 18:05

回避策は WebView の onScrollChanged() を呼んであげること。
ただし、onScrollChanged() は protected なので、WebView を継承したクラスを作って外から呼べるメソッド作ってそこから呼ばなきゃならない。
これも例が載ってる。

public void applyAfterMoveFix() {
  onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
}

これを WebView を貼り付けている Fragment から適切なタイミングで呼べば OK。


今回はこんな感じにしてみた。

public class MyWebView extends WebView {
    public WebViewForFragment(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void applyAfterMoveFix() {
        onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
    }
}

onScrollChanged()はいつ呼ぶの?

今回、一番ハマってたのはこれ。


最初、MainActivity.java の FragmentActivity の onTabSelected() で、選択されたのが WebView の Fragment だった時に呼んでいた。
でも、これだと画面をスワイプして移動した時にはちゃんと機能するのだけど、タブを選択して画面が変わった場合には機能しない。
(たぶん)タイミングが早いのだと思って、Fragment から WebView の applyAfterMoveFix() を呼ぶのを 100msec 遅らせたらちゃんと機能するようになった。


けど、それじゃ気持ちが悪いコードになってしまうので、タブの切り替わったタイミングを取れる他の方法を探していたら見つかった。
FragmentPagerAdapter の setPrimaryItem()。
ここで position が WebView のタブの場合に ApplyAfterMoveFix() を呼ぶようにしたら、スワイプでもタブを選択でもちゃんと機能するようになった。


めでたし、めでたし。