zoukankan      html  css  js  c++  java
  • android webview 底层实现的逻辑

    其实在不同版本上,webview底层是有所不同的。
    先提供个地址给大家查:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.2.1_r1.2/android/webkit/WebView.java#WebView.showFindDialog%28java.lang.String%2Cboolean%29
    这是我在做某个项目上遇到的难点。
    今天弄了下android webview下的几个页面。原先以为android 4+把 webview的viewport属性忽略掉了。
    但是今天弄了下。加了个 authorizationView.getSettings().setUseWideViewPort(true);
    viewport 的几个属性重新起作用。(测试环境,4.0+的几个版本)

    但是又遇到几个问题,就是html里有input的时候。获取焦点的时候,android会重新缩放到原来模式,看源码:
    /**
         * Called in response to a message from webkit telling us that the soft
         * keyboard should be launched.
         
    */
        
    private void displaySoftKeyboard(boolean isTextView) {
            InputMethodManager imm = (InputMethodManager)
                    getContext().getSystemService(Context.INPUT_METHOD_SERVICE);

            
    // bring it back to the default level scale so that user can enter text
            boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale();
            
    if (zoom) {
                mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
                mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false);
            }
            
    if (isTextView) {
                rebuildWebTextView();
                
    if (inEditingMode()) {
                    imm.showSoftInput(mWebTextView, 0, mWebTextView.getResultReceiver());
                    
    if (zoom) {
                        didUpdateWebTextViewDimensions(INTERSECTS_SCREEN);
                    }
                    
    return;
                }
            }
            
    // Used by plugins and contentEditable.
            
    // Also used if the navigation cache is out of date, and
            
    // does not recognize that a textfield is in focus.  In that
            
    // case, use WebView as the targeted view.
            
    // see http://b/issue?id=2457459
            imm.showSoftInput(this0);
        }
    从源码可以看到,webview当要弹起键盘的时候,会判定当前的缩放比例与默认大小(我测试了下,我自己的版本的默认值是1.5),
    当你网页viewport设置initial-scale=0.5时,当input 获取焦点的时候,android会放大到原来模式,不是我们想要的,网上查了下相关,
    有个解决方案:
    wv.setOnFocusChangeListener(new View.OnFocusChangeListener() {

            @Override
            
    public void onFocusChange(View v, boolean hasFocus) {
                
    // TODO Auto-generated method stub
                try {
                    Field defaultScale = WebView.class
                            .getDeclaredField("mDefaultScale");
                    defaultScale.setAccessible(true);
                    
    float _s = defaultScale.getFloat(wv);
                    defaultScale.setFloat(wv, scale);
                    
    float x = wv.getScale();
                    
    int i = 0;
                } catch (Exception e) {
                    e.printStackTrace();
                    
    try {
                        Field defaultZoom = WebView.class
                                .getDeclaredField("mZoomManager");
                        defaultZoom.setAccessible(true);
                        Field defaultScale = defaultZoom.getType()
                                .getDeclaredField("mDefaultScale");
                        defaultScale.setAccessible(true);
                        defaultScale.setFloat(defaultZoom.get(wv), scale);
                    } catch (Exception ee) {
                        ee.printStackTrace();
                    }
                }
            }
        });
    但是作者碰到另外一个问题,引用自原话:
    as it showed, I using reflect to find the field 'mDefaultScale' to control the WebView.
    But it doesn
    't work on Android 4.1.1 (Google Nexus), and I catch an exception —— java.lang.NoSuchFieldException: mDefaultScale.
    Then I list the fileds and found the framework source seems being changed(I can only reach a field called 'mProvider').

    So how can I fix the problem (I haven
    't got the source yet)? Thanks for reading my question with my poor English, Thx :)

    PS: maybe a online framework source review website is helpful but I can
    't found one, if you can provide me one, it will be great. :P

    完了我自己测试了,发现此方案解决不了。但是引出了另外一问题,就是不用android版本下的webview实现是不一样的,其实看代码就能看出,
    原先webview有mDefaultScale字段,但是后来应该挪到mZoomManager里去了,但是我发现我手机上webview 实现和作者遇到的问题一样,只有mProvider成员,
    没有mZoomManager,所以只能寻求源码,千辛万苦,终于找到
    http://androidxref.com/4.2_r1/xref/frameworks/base/core/java/android/webkit/WebViewClassic.java,
    mProvider 其实类型就是WebViewClassic(自己看下源码实现),简要提下,WebProvider只是一个接口,作为WebView的一个成员,
    创建时用了factory来,完了看下几个工厂类,最后是webviewclassic实例)。
     对于Jerry Bean 4.2这个版本(我一个手机就是自己刷的rom),webview的实现又换了个,所以要拿到默认缩放的成员,如下:
    try {  
                        Field defaultScale 
    = WebView.class  
                                .getDeclaredField(
    "mDefaultScale");  
                        defaultScale.setAccessible(
    true);  
                        
    float sv = defaultScale.getFloat(authorizationView);
                        defaultScale.setFloat(authorizationView, xxx);  
                    } 
    catch (SecurityException e) {  
                        e.printStackTrace();  
                    } 
    catch (IllegalArgumentException e) {  
                        e.printStackTrace();  
                    } 
    catch (IllegalAccessException e) {  
                        e.printStackTrace();  
                    } 
    catch (NoSuchFieldException e) {  
                        e.printStackTrace();  
                        
    try {  
                            Field zoomManager;   
                            zoomManager 
    = WebView.class.getDeclaredField("mZoomManager");  
                            zoomManager.setAccessible(
    true);  
                            Object zoomValue 
    = zoomManager.get(authorizationView);  
                            Field defaultScale 
    = zoomManager.getType().getDeclaredField("mDefaultScale");  
                            defaultScale.setAccessible(
    true);  
                            
    float sv = defaultScale.getFloat(zoomValue);
                            defaultScale.setFloat(zoomValue, xxx);  
                        } 
    catch (SecurityException e1) {  
                            e1.printStackTrace();  
                        } 
    catch (IllegalArgumentException e1) {  
                            e.printStackTrace();  
                        } 
    catch (IllegalAccessException e1) {  
                            e.printStackTrace();  
                        } 
    catch (NoSuchFieldException e1) {  
                            e1.printStackTrace();  
                            
                            
    try {
                                Field mProviderField 
    = WebView.class.getDeclaredField("mProvider");  
                                mProviderField.setAccessible(
    true);
                                
    //mProviderField.getClass()
                                Object webviewclassic = mProviderField.get(authorizationView);  
                                
                                Field zoomManager 
    = webviewclassic.getClass().getDeclaredField("mZoomManager");   
                                zoomManager.setAccessible(
    true);
                                Object zoomValue 
    = zoomManager.get(webviewclassic);  
                                Field defaultScale 
    = zoomManager.getType().getDeclaredField("mDefaultScale");  
                                defaultScale.setAccessible(
    true);  
                                
    float sv = defaultScale.getFloat(zoomValue);
                                defaultScale.setFloat(zoomValue, xxx);  
                            }
    catch(Exception e2)
                            {
                                e2.printStackTrace();
                            }
                        }  
                    }

    虽然可以拿到,并且设置成功,但是在我的手机上还是不能解决input 获取焦点后自动放大,
    完了想了下,有个实现模式可以参考:当input 获取焦点时,js调用java设置默认放缩率,设置前保存原有值,失去焦点后重新设置原来值,不然跳转到其他页面的时候,你会发现比例不对了。:)。

    因为放大后双击还是还原回原来样子。所以暂且不来纠结这个东西了。因为不同android版本的如果webview实现不一致的话,这代码就不起作用了 :)


    ————————————————————————————————————————————————————————————————————————————————————

                      用过EditText的都知道,EditText有个特点,当在里面长按的时候,会出现一个ContextMenu,提供了选择文字,复制,剪切等功能。有时候,我们会想,如果不出现这个ContextMenu,直接就在view上选择文字,那多美好啊。相信很多人抱有这样的想法,很不幸,我也是。于是我就研究了一下EditText和TextView的代码,然后将这个问题解决了。
          网上很多资料都说,要选择一段文字,只需要用Selection.getSelectionStart()和Selection.getSelectionEnd()确定选择的文字的头和尾,然后加颜色就行。简直是胡扯啊,我敢说这样的代码根本就没有经过验证,就发到网上了,然后一大堆人互相转载,结果导致误导了很多人,杯具 啊!! 
          好,我们来分析一下解决办法。
          TextView是很多View的基类,如Button、EditText都是继承自他,所以EditText里面的代码很少。我们看一下EditText的源码,有一个Override的getDefaultEditable方法,看名字的意思是是否可编辑,这个方法直接返回true。还有一个getDefaultMovementMethod方法,它返回的是ArrowKeyMovementMethod.getInstance(),通过查看ArrowKeyMovementMethod的源码,基本确定这个方法就是弹出ContextMenu和轨迹球监听的“元凶”。
          下面,我们自己做一个view来打造自己的EditText。
          我取名TextPage,继承EditText,在里面覆盖getDefaultEditable和getDefaultMovementMethod。

    Java代码  收藏代码
    1. @Override  
    2. public boolean getDefaultEditable() {  
    3.     return false;  
    4. }  
    5. @Override  
    6. protected MovementMethod getDefaultMovementMethod() {  
    7.     return null;  
    8. }  
     

             现在测试一下,发现长按没反应了,所料不错,就是getDefaultMovementMethod方法控制了ContextMenu。
          看一下ArrowKeyMovementMethod的代码,里面提供了KeyEvent、轨迹球事件onTrackballEvent和touch事件onTouchEvent的处理。这些事件在何处调用的呢?我们看看TextView的onTouchEvent、onTrackballEvent和onKeyEvent方法里面就明白了,在这些事件回调中调用了ArrowKeyMovementMethod里面的这些方法。
          还有个问题,ContextMenu在哪里触发的?这个问题,用过ContextMenu的都知道,view里面要使用ContextMenu,需要覆盖一个onCreateContextMenu方法,然后在里面创建ContextMenu的各个选项。在TextView里面找onCreateContextMenu,果然有,里面定义了选择、复制、粘贴等选项。
          既然找到了这个,那么我们就可以进一步分析选择是如何做到的。
          onCreateContextMenu只是创建菜单,那么菜单点击之后,触发了什么呢?onCreateContextMenu里面定义了一个MenuHandler对象,然后作为参数传递给setOnMenuItemClickListener,找到MenuHandler,发现里面的onMenuItemClick返回的是onTextContextMenuItem函数,找到onTextContextMenuItem,OMG,终于找到点击menu触发的函数了。但是里面貌似没有关键的东西,选择的部分不在这里。那么,就应该在上面所说的那些事件里面了。
          重点分析ArrowKeyMovementMethod的onTouchEvent方法。发现一个重要的方法getLayout(),然后获取一个Layout对象,通过x和y坐标知道当前字符串的offset位置。
          那么,问题就可以完美的解决了。你可以点击任何地方然后拖动,释放之后,中间的文字就会被选中,so beautiful!

     

    Java代码  收藏代码
    1. import android.content.Context;  
    2. import android.graphics.Color;  
    3. import android.text.Layout;  
    4. import android.text.Selection;  
    5. import android.view.ContextMenu;  
    6. import android.view.Gravity;  
    7. import android.view.MotionEvent;  
    8. import android.widget.EditText;  
    9.   
    10. /** 
    11.  * @author chroya 
    12.  */  
    13. public class TextPage extends EditText {  
    14.     private int off; //字符串的偏移值  
    15.   
    16.     public TextPage(Context context) {  
    17.         super(context);  
    18.         initialize();  
    19.     }  
    20.   
    21.     private void initialize() {  
    22.         setGravity(Gravity.TOP);  
    23.         setBackgroundColor(Color.WHITE);  
    24.     }  
    25.       
    26.     @Override  
    27.     protected void onCreateContextMenu(ContextMenu menu) {  
    28.         //不做任何处理,为了阻止长按的时候弹出上下文菜单  
    29.     }  
    30.       
    31.     @Override  
    32.     public boolean getDefaultEditable() {  
    33.         return false;  
    34.     }  
    35.       
    36.     @Override  
    37.     public boolean onTouchEvent(MotionEvent event) {  
    38.         int action = event.getAction();  
    39.         Layout layout = getLayout();  
    40.         int line = 0;  
    41.         switch(action) {  
    42.         case MotionEvent.ACTION_DOWN:  
    43.             line = layout.getLineForVertical(getScrollY()+ (int)event.getY());          
    44.             off = layout.getOffsetForHorizontal(line, (int)event.getX());  
    45.             Selection.setSelection(getEditableText(), off);  
    46.             break;  
    47.         case MotionEvent.ACTION_MOVE:  
    48.         case MotionEvent.ACTION_UP:  
    49.             line = layout.getLineForVertical(getScrollY()+(int)event.getY());   
    50.             int curOff = layout.getOffsetForHorizontal(line, (int)event.getX());              
    51.             Selection.setSelection(getEditableText(), off, curOff);  
    52.             break;  
    53.         }  
    54.         return true;  
    55.     }  
    56. }  


    最后为webview加上一点功能,为WebView加上复制文本功能  

    需求描述:   

    • 长按WebView出现Context menu,显示"复制”菜单
    • 点击上述菜单后选择文本,复制到剪贴板
    概要设计+详细设计:
    • 用OnTouchListener实现长按实现(参照android.view.View)
    • 实现WebView的Context menu(在Activity实例中实现)
    • 实现复制文本功能(兼容多个sdk)

     

    编码:

     

    Java代码  收藏代码
    1. public class WebViewCopy {  
    2.     private Activity activity;  
    3.     private WebView webview;  
    4.     private  static boolean mIsSelectingText;  
    5.       
    6.     public static final String    TAG=WebViewCopy.class.getSimpleName();  
    7.         public WebViewCopy(final Activity activity, final WebView webView){  
    8.        this.webview=webView;  
    9.        this.activity=activity;  
    10.        this.activity.registerForContextMenu(this.webview);  
    11.         webView.requestFocus(View.FOCUS_DOWN);  
    12.         webView.setOnTouchListener(new OnTouchListener() {  
    13.               
    14.             boolean mHasPerformedLongPress;  
    15.             Runnable mPendingCheckForLongPress;  
    16.               
    17.           
    18.         @Override  
    19.         public boolean onTouch(final View v, MotionEvent event) {  
    20.           
    21.         /*  webView.getSettings().setBuiltInZoomControls(false); 
    22.             webView.getSettings().setSupportZoom(false);*/  
    23.             Log.d(TAG, "event:" + event.getAction());  
    24.               
    25.             switch (event.getAction()) {  
    26.                 case MotionEvent.ACTION_UP:  
    27.                     mIsSelectingText = false;  
    28.                      if (!v.hasFocus()) {  
    29.                          v.requestFocus();  
    30.                         }  
    31.   
    32.                       if (!mHasPerformedLongPress) {  
    33.                           // This is a tap, so remove the longpress check  
    34.                           if (mPendingCheckForLongPress != null) {  
    35.                               v.removeCallbacks(mPendingCheckForLongPress);  
    36.                           }  
    37.                        // v.performClick();  
    38.                       }  
    39.                             
    40.                         break;  
    41.                 case  MotionEvent.ACTION_DOWN:  
    42.                       
    43.                      if (!v.hasFocus()) {  
    44.                          v.requestFocus();  
    45.                        }  
    46.   
    47.                     if( mPendingCheckForLongPress == null) {  
    48.                           
    49.                           
    50.                         mPendingCheckForLongPress = new Runnable() {  
    51.                             public void run() {  
    52.                                 //((WebView)v).performLongClick();  
    53.                                 if(! mIsSelectingText) {  
    54.                                     activity.openContextMenu(webview);  
    55.                                     mHasPerformedLongPress = true;  
    56.                                     mIsSelectingText = false;  
    57.                                 }  
    58.                             }  
    59.                     };  
    60.                           
    61.                     }  
    62.                       
    63.                       
    64.                      mHasPerformedLongPress = false;  
    65.                      v. postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());  
    66.                       
    67.                         break;  
    68.                   
    69.                  case MotionEvent.ACTION_MOVE:  
    70.                       final int x = (int) event.getX();  
    71.                       final int y = (int) event.getY();  
    72.   
    73.                       // Be lenient about moving outside of buttons  
    74.                       int slop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();  
    75.                       if ((x < 0 - slop) || (x >= v.getWidth() + slop) ||  
    76.                               (y < 0 - slop) || (y >= v.getHeight() + slop)) {  
    77.                             
    78.                           if (mPendingCheckForLongPress != null) {  
    79.                              v. removeCallbacks(mPendingCheckForLongPress);  
    80.                           }  
    81.                        
    82.                       }  
    83.                         break;  
    84.                   default:  
    85.                       return false;  
    86.               
    87.             }  
    88.               
    89.              return false;  
    90.               
    91.         }  
    92.     });  
    93.           
    94.       
    95.           
    96.           
    97.           
    98.    }  
    99.      
    100.      public  static synchronized void  emulateShiftHeld(WebView view)  
    101.      {  
    102.            
    103.             try  
    104.             {  
    105.                 KeyEvent shiftPressEvent = new KeyEvent(00, KeyEvent.ACTION_DOWN,  
    106.                                                         KeyEvent.KEYCODE_SHIFT_LEFT, 00);  
    107.                 shiftPressEvent.dispatch(view);  
    108.             }  
    109.             catch (Exception e)  
    110.             {  
    111.                 Log.e(TAG, "Exception in emulateShiftHeld()", e);  
    112.             }  
    113.         }  
    114.        
    115.         public synchronized void onCreateContextMenu(ContextMenu menu, View v,    
    116.                  ContextMenuInfo menuInfo,final int copy,String menuString) {     
    117.                  MenuItem menuitem=menu.add(1, copy, Menu.NONE, menuString);    
    118.                  menuitem.setOnMenuItemClickListener(new OnMenuItemClickListener() {  
    119.                       
    120.                     @Override  
    121.                     public boolean onMenuItemClick(MenuItem item) {  
    122.                         if(item.getItemId()==copy){  
    123.                             //emulateShiftHeld(webview);  
    124.                             selectAndCopyText(webview);  
    125.                         }  
    126.                         return false;  
    127.                     }  
    128.                 });  
    129. }    
    130.         public  static synchronized void selectAndCopyText(WebView v) {  
    131.              try {  
    132.                    
    133.                  mIsSelectingText = true;  
    134.                    
    135.                          //Method m = WebView.class.getMethod("emulateShiftHeld", Boolean.TYPE);   
    136.                   //  m.invoke(v, false);   
    137.                    
    138.                 if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.ECLAIR) {  
    139.                      Method m = WebView.class.getMethod("emulateShiftHeld", Boolean.TYPE);   
    140.                        m.invoke(v, false);   
    141.                  }  
    142.                  else  {  
    143.                      Method m = WebView.class.getMethod("emulateShiftHeld");   
    144.                       m.invoke(v);   
    145.                  }  
    146.                    
    147.                 } catch (Exception e) {  
    148.                     // fallback  
    149.                     emulateShiftHeld(v);  
    150.                 }finally{  
    151.                     //Toast.makeText(activity, "Select text", Toast.LENGTH_SHORT).show();  
    152.                 }  
    153.   
    154.         }  
    155. }  

     下面的代码在activity中写:

         1) 在onCreate中生成 WebViewCopy  实例: copy = new WebViewCopy(this, _webView);

         2) 在onCreateContextMenu中注入复制菜单public void onCreateContextMenu(ContextMenu menu, View v,

    Java代码  收藏代码
    1.     ContextMenuInfo menuInfo) {    
    2.    copy.onCreateContextMenu(menu, v, menuInfo,COPY,getString(R.string.copy));  
    3. super.onCreateContextMenu(menu, v, menuInfo);    

     

     

    回顾与总结:

            OnTouchListener可能在一些时候更本不响应,如Zoom Button出现后。这得让WebView重新获取焦点,

    这是WebView又一已知的Bug.  整个难点在于重新获取焦点:  webview.requestFocus();




    参考:http://blog.csdn.net/djy1992/article/details/49406007


  • 相关阅读:
    jupyter notebook 的快捷键【转】
    jupyter notebook 添加Markdown
    jupyter notebook 更改工作目录
    [转]pycharm的一些快捷键
    Git/github基础 (四) 搭建homepages
    Git/github基础 (三) Issues
    Git/github基础 (二) fork
    Git/github基础 (一)
    《Python基础教程》第2章读书笔记(1)
    jquery1.9学习笔记 之层级选择器(三)
  • 原文地址:https://www.cnblogs.com/new0801/p/6175793.html
Copyright © 2011-2022 走看看