android webview 底层实现的逻辑

其实在不同版本上,webview底层是有所不同的 , 可能你会有webview中复制粘贴等功能的实现,而正常的API无法实现,这时你就需要看看webview的底层了,先提供 API给大伙 :

当然,这是我在做某个项目上遇到的难点,先来看看收集的资料和自我总结。
今天弄了下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(this, 0);
    }

从源码可以看到,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(0, 0, KeyEvent.ACTION_DOWN,  
  106.                                                         KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0);  
  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

时间: 2024-08-01 00:54:37

android webview 底层实现的逻辑的相关文章

Android webView shouldOverrideUrlLoading 方法不执行

问题描述 Android webView shouldOverrideUrlLoading 方法不执行 在android 版本4.4.4以上会执行,测试的4.2.2,4.1.2机器上不会执行: 我要实现的逻辑是点击webview上已经加载好的内容,在shouldOverrideUrlLoading 那里拿到链接地址新开Activity用webview加载,不知道有没有能人异士可以提供解决方案. 解决方案 自己解决了,比较麻烦. 解决方案二: android WebView shouldOverr

Android Webview使用和遇到过的坑总结

本文讲的是Android Webview使用和遇到过的坑总结,WebView 用来显示网页的一个View,它使用WebKit渲染引擎显示web页面,可以加载在线的或者本地的html页面,WebView可以对页面进行一系列操作,如历史页面的向前.向后,放大和缩小,执行文本搜索,与JS交互等等; 在使用Webview时,请记得在AndroidManifest.xml文件中声明INTERNET权限: <uses-permission android:name="android.permissio

android webview加载HTML5网站,监听HTML5视频播放?

问题描述 android webview加载HTML5网站,监听HTML5视频播放? 如题:在android盒子上面自己做了一个apk,布局是一个webview和一个SurfaceView, webview加载一个HTML网站网站代码为: <!DOCTYPE HTML> your browser does not support the video tag 里面播放的是一个avi格式的视频,视频可以播放. 我现在因为要修改视频输出的声道设置,本人有SDK源码,源码的Mediaplayer有设置

[Chromium] Chromium Android WebView层的设计

Chromium Android WebView是Chromium专为Android WebView提供一个对Content的封装层.从整体上来看可以理解为一个特殊化的Embedder, 功能可以概括为:    1. 对Content和部分Browser Components封装到Java实现,供AOSP WebView调用实现WebView功能.    2. 实现Android WebView使用的单进程渲染架构.    3. 配置网络模块,并实现特定需要的scheme解析. Content作

Android Webview中Vue初始化的性能优化

前言 一般来说,你不需要太关心vue的运行时性能,它在运行时非常快,但付出的代价是初始化时相对较慢.在最近开发的一个Hybrid APP里,Android Webview初始化一个较重的vue页面竟然用了1200ms ~ 1400ms,这让我开始重视vue的初始化性能,并最终优化到200 ~ 300ms,这篇文章分享我的优化思路. 性能瓶颈在哪里? 先看一下常见的vue写法:在html里放一个app组件,app组件里又引用了其他的子组件,形成一棵以app为根节点的组件树. <body>    

浏览器-android WebView中进度条只能显示一次,为什么?

问题描述 android WebView中进度条只能显示一次,为什么? public class WebActivity extends Activity implements View.OnClickListener { private WebView webView; private EditText edit_web_adds; private Button btn_webadds_go, btn_web_back; private ProgressBar pb; String Url;

Android Webview使用小结_Android

本文实例为大家分享了Android Webview使用小结,供大家参考,具体内容如下 #采用重载URL的方式实现Java与Js交互 在Android中,常用的Java与Js交互的实现方式是通过函数addJavascriptInterface进行添加在Js中使用的回调代理类. 这种方法虽然方便,但是写出来的js代码并不通用.如果IOS也要实现类似的功能或业务,则IOS要另外写一套Js代码.所以不太推荐. 推荐使用重载URL的方式来实现,因为基本所有的平台都拥有在加载某个URL之前进行一些处理的回调

Android WebView使用方法详解 附js交互调用方法_Android

目前很多Android app都内置了可以显示web页面的界面,会发现这个界面一般都是由一个叫做WebView的组件渲染出来的,学习该组件可以为你的app开发提升扩展性. 先说下WebView的一些优点: --可以直接显示和渲染web页面,直接显示网页 --webview可以直接用html文件(网络上或本地assets中)作布局 --和JavaScript交互调用  一.基本使用 首先layout中即为一个基本的简单控件: <WebView android:id="@+id/webView

Android WebView自定义长按选择实现收藏/分享选中文本功能

效果图(1.3M) 一.前言 ** 戳这里可以去DEMO,来吧 ** 相信刚接触android不久的同志们,在面对产品提出的 : "自定义WebView页面中,长按文本的弹出选项.点击选择后,分享.转发.收藏选择文本" 这样的需求时,第一反应大部分是:这是系统行为,如果实现需要在web端实现. 但是web端实现的局限性太大,曾经也有过监听系统粘贴板,在用户点击复制的时候实现其他的逻辑,但是这样用户体验不好,所以自定义WebView中长按的弹出菜单,并在点击时返回选中文本的小控件闪亮登场