Android WebView findAll 在ICE_CREAM_SANDWICH_MR1及其以下不能工作

       Android系统开发过的大家不管是framework以上还是native,大家都会遇到过不同Android版本差异带来的问题,Deprecate这个词framework层接触比较多童鞋会见得相当多,甚至一些api还会有一些bug. 本人总结Android开发针对版本不同问题的个人心得:

1.首先查看官方文档和framework源码,这里推荐一个网址在线查看android各个系统版本的源      码http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android

2.排除项目工程原因,可以自己新建一个android app 单独使用,确认是否是项目工场原因

3.排除Android系统Rom问题,可以找不同rom测试结果

4.如果有同类产品使用了相同系统api,查看其它同类产品是否能正常工作.

5.阅读源码找出问题真正原因,是否是api bug,

    (1) 如果是android api bug查看系统源码framework & native解决问题。

    (2)  参看同类产品,或者系统应用实现代码(具体方式这边不再细说)

下面举例我遇到过的问题.

       网页查找功能Android系统提供findAll这个接口,从官方文档上看这个接口只适用于api level 16一下findAll,api level 16及其以上使用findAllSync.

遇到问题是api level 16一下调用findAll 网页中查找到的文字并没有高亮。

   1.首先查看官方文档http://developer.android.com/reference/android/webkit/WebView.html#findAll(java.lang.String)

         确认在api level 16一下才生效这个接口,api 16及其以上采用findAllSync我们代码中需要做这样的处理.

   2.结论直接调用findAll不好用,新建一个工程实现简单的findAll并在API level下机型测试.

   3.结论第二步不work,再寻找其他使用相同android版本的机型,多试几台基本能确定问题.

   4.结论第三步不work,再查看同类其他产品是否具有相同功能问题。

   5.结论第四步发现同类其他app具有类似功能能正常工作,能高亮选择文字.

    通过上面几个分析能确定api 有bug, 但是可以确定的是这个bug是可解的。

我采用第五条中研究framework & native 源码解决.

分析如下:

从Android WebView 看到提供一个API showFindDialog showFindDialog showFindDialog 

<span style="font-size:18px;">public boolean showFindDialog (String text, boolean showIme)

Added in API level 11
This method was deprecated in API level 18.
This method does not work reliably on all Android versions; implementing a custom find dialog using WebView.findAllAsync() provides a more robust solution.

Starts an ActionMode for finding text in this WebView. Only works if this WebView is attached to the view system.

Parameters
text	if non-null, will be the initial text to search for. Otherwise, the last String searched for in this WebView will be used to start.
showIme	if true, show the IME, assuming the user will begin typing. If false and text is non-null, perform a find all.
Returns
true if the find dialog is shown, false otherwise
public void stopLoading ()</span>
虽然这个API 在 android level 18 deprecated但是不影响我们在api level 15机器上测试.  发现通过系统提供的这个api来实现这个功能可以正常work. 很自然会想到showFindDialog 在api level 15的内部实现细节。

showFindDialog source code on api level 15 (android 4.0.3, 4.0.4)

<span style="font-size:18px;">   public boolean showFindDialog(String text, boolean showIme) {
        checkThread();
        FindActionModeCallback callback = new FindActionModeCallback(mContext);
        if (getParent() == null || startActionMode(callback) == null) {
            // Could not start the action mode, so end Find on page
            return false;
        }
        mCachedOverlappingActionModeHeight = -1;
        mFindCallback = callback;
        setFindIsUp(true);//Highlight
        mFindCallback.setWebView(this);
        if (showIme) {
            mFindCallback.showSoftInput();
        } else if (text != null) {
            mFindCallback.setText(text);
            mFindCallback.findAll();
            return true;
        }
        if (text == null) {
            text = mLastFind;
        }
        if (text != null) {
            mFindCallback.setText(text);
        }
        return true;
    }</span>
从上面代码中我们可以看到出现了一个我们关心的函数findAll,接下来我们看看这个findAll的具体实现。

<span style="font-size:18px;">    void findAll() {
        if (mWebView == null) {
            throw new AssertionError(
                    "No WebView for FindActionModeCallback::findAll");
        }
        CharSequence find = mEditText.getText();
        if (0 == find.length()) {
            mWebView.clearMatches();
            mMatches.setVisibility(View.GONE);
            mMatchesFound = false;
        } else {
            mMatchesFound = true;
            mMatches.setVisibility(View.VISIBLE);
            mNumberOfMatches = mWebView.findAll(find.toString());
            if (0 == mNumberOfMatches) {
                mMatches.setText(mResources.getString(
                        com.android.internal.R.string.no_matches));
            } else {
                updateMatchesString();
            }
        }
    }</span>

我们可以看到其实他内部实现也是调用WebView的findAll方法. 那为什么我们自己调用会出问题呢. 答案肯定不是仅仅调用这个API的问题, 因为我们之前通过排查,单独创建工程并在ui主线程调用findAll方法并没有生效. 可能我们会猜测是不是没有调用invalidate方法,通过framework源码我们看到WebView findAll方法中已经调用了invalidate这个方法来重绘我们的UI。

<span style="font-size:18px;">    public int findAll(String find) {
        checkThread();
        if (0 == mNativeClass)
            return 0; // client isn't initialized
        int result = find != null ? nativeFindAll(find.toLowerCase(),
                find.toUpperCase(), find.equalsIgnoreCase(mLastFind)) : 0;
        invalidate();
        mLastFind = find;
        return result;
    }</span>

那么为什么showFindDialog内部处理能正常工作呢?

我们再回头看一下showFindDialog的实现,前面源码中的代码setFindIsUp, 这就是整个关键所在.

我们自己实现的区别和WebView内部实现的最大区别就是这个函数,下面我们来看一下setFindIsUp的实现源码:

<span style="font-size:18px;">    private void setFindIsUp(boolean isUp) {
        mFindIsUp = isUp;
        if (0 == mNativeClass)
            return; // client isn't initialized
        nativeSetFindIsUp(isUp);
    }</span>

这个函数的作用是触发内核当前是查找状态WebView.cpp.

<span style="font-size:18px;">    void setFindIsUp(bool up)
    {
        DBG_NAV_LOGD("up=%d", up);
        m_viewImpl->m_findIsUp = up;
    }</span>

兴奋的时刻来了, 我们急切的希望调用这个api 看是否能真的work. 前面setFindIsUp方法是private的.

java reflect 这时候就可以派上用场了.  我们在调用findAll之前触发一下这个api.下面是反射实现代码:

<span style="font-size:18px;">   public void toogleSetFindIsUp(WebView webView) {
        try {
            for (Method m : WebView.class.getDeclaredMethods()) {
                if (m.getName().equals("setFindIsUp")) {
                    m.setAccessible(true);
                    m.invoke((webView), true);
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }</span>
我们在findAll 前或者后调用这个方法来测试能否work.  

------------------------

------------------------

-----------------------

it works.  Show highlight in webpage.


转载请注明出处http://blog.csdn.net/typename/article/details/34529859 

谢谢!


      

Android WebView findAll 在ICE_CREAM_SANDWICH_MR1及其以下不能工作,布布扣,bubuko.com

Android WebView findAll 在ICE_CREAM_SANDWICH_MR1及其以下不能工作

上一篇:Android平台上直接物理内存读写漏洞的那些事


下一篇:搭建和测试Android JAVA NDK