Android 系统源码分析之View(一)
View类两万一千多行,在这里逐步分析。建议这篇blog对照Android View源码观看!!
/* <tr> * <td><code>{@link #onFinishInflate()}code>td> * <td>Called after a view and all of its children has been inflated * from XML.td> * tr> * * <tr> * <td rowspan="3">Layouttd> * <td><code>{@link #onMeasure(int, int)}code>td> * <td>Called to determine the size requirements for this view and all * of its children. * td> * tr> * <tr> * <td><code>{@link #onLayout(boolean, int, int, int, int)}code>td> * <td>Called when this view should assign a size and position to all * of its children. * td> * tr> * <tr> * <td><code>{@link #onSizeChanged(int, int, int, int)}code>td> * <td>Called when the size of this view has changed. * td> * tr> * * <tr> * <td>Drawingtd> * <td><code>{@link #onDraw(android.graphics.Canvas)}code>td> * <td>Called when the view should render its content. * td> * tr> * * <tr> * <td rowspan="4">Event processingtd> * <td><code>{@link #onKeyDown(int, KeyEvent)}code>td> * <td>Called when a new hardware key event occurs. * td> * tr> * <tr> * <td><code>{@link #onKeyUp(int, KeyEvent)}code>td> * <td>Called when a hardware key up event occurs. * td> * tr> * <tr> * <td><code>{@link #onTrackballEvent(MotionEvent)}code>td> * <td>Called when a trackball motion event occurs. * td> * tr> * <tr> * <td><code>{@link #onTouchEvent(MotionEvent)}code>td> * <td>Called when a touch screen motion event occurs. * td> * tr> * * <tr> * <td rowspan="2">Focustd> * <td><code>{@link #onFocusChanged(boolean, int, android.graphics.Rect)}code>td> * <td>Called when the view gains or loses focus. * td> * tr> * * <tr> * <td><code>{@link #onWindowFocusChanged(boolean)}code>td> * <td>Called when the window containing the view gains or loses focus. * td> * tr> * * <tr> * <td rowspan="3">Attachingtd> * <td><code>{@link #onAttachedToWindow()}code>td> * <td>Called when the view is attached to a window. * td> * tr> * * <tr> * <td><code>{@link #onDetachedFromWindow}code>td> * <td>Called when the view is detached from its window. * td> * tr> * * <tr> * <td><code>{@link #onWindowVisibilityChanged(int)}code>td> * <td>Called when the visibility of the window containing the view * has changed. * td> * tr> */
View通过构造方法创建,onMeasure测量,Layout方法测量需要测量自身和子View,onLayout方法分配子View,onSizeChanged方法当视图大小发生改变时调用,onDraw方法用于Canvas的绘制内容,onTouchEvent触屏时调用,onFocusChanged焦点改变时调用,onWindowFocusChanged同理,onWindowVisibilityChanged视图可见性onDetachedFromWindow取消监听和相关事件,onAttachedToWindow绑定相关事件监听。
/* android:id="@+id/my_button" * android:layout_width="wrap_content" * android:layout_height="wrap_content" * android:text="@string/my_button_text" */
XML引用View控件,并分配ID
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ....... }
这里实现的三个接口,先来看Drawable.Callback
/** * Implement this interface if you want to create an animated drawable that * extends {@link android.graphics.drawable.Drawable Drawable}. * Upon retrieving a drawable, use * {@link Drawable#setCallback(android.graphics.drawable.Drawable.Callback)} * to supply your implementation of the interface to the drawable; it uses * this interface to schedule and execute animation changes. */ public static interface Callback { /** * Called when the drawable needs to be redrawn. A view at this point * should invalidate itself (or at least the part of itself where the * drawable appears). * * @param who The drawable that is requesting the update. */ public void invalidateDrawable(Drawable who); /** * A Drawable can call this to schedule the next frame of its * animation. An implementation can generally simply call * {@link android.os.Handler#postAtTime(Runnable, Object, long)} with * the parameters (what, who, when) to perform the * scheduling. * * @param who The drawable being scheduled. * @param what The action to execute. * @param when The time (in milliseconds) to run. The timebase is * {@link android.os.SystemClock#uptimeMillis} */ public void scheduleDrawable(Drawable who, Runnable what, long when); /** * A Drawable can call this to unschedule an action previously * scheduled with {@link #scheduleDrawable}. An implementation can * generally simply call * {@link android.os.Handler#removeCallbacks(Runnable, Object)} with * the parameters (what, who) to unschedule the drawable. * * @param who The drawable being unscheduled. * @param what The action being unscheduled. */ public void unscheduleDrawable(Drawable who, Runnable what); }
View内部对外公开两个方法setCallback 、getCallback
/** * Bind a {@link Callback} object to this Drawable. Required for clients * that want to support animated drawables. * * @param cb The client's Callback implementation. * * @see #getCallback() */ public final void setCallback(Callback cb) { mCallback = new WeakReference(cb); } /** * Return the current {@link Callback} implementation attached to this * Drawable. * * @return A {@link Callback} instance or null if no callback was set. * * @see #setCallback(android.graphics.drawable.Drawable.Callback) */ public Callback getCallback() { if (mCallback != null) { return mCallback.get(); } return null; }
如果你想扩展View动画,可以实现Drawable.Callback,然后调用setCallback传入Callback的实现类即可invalidateDrawable()方法,在drawable重画时触发,展示Drawable部分将不可用。scheduleDrawable()用于控制动画的下一帧,用户可以通过mHandler.postAtTime(Runnable,Object)调用,unscheduleDrawable()方法则是取消scheduleDrawable()该方法定义的下一帧动画。这里的setCallback并不是我们常用的this.mCallback=mCallBack而是通过弱引用(引用类型:强引用 、软引用、弱引用、虚引用)引用,以便于GC回收,避免了内存泄露。getCallback()方法通过软引用调用抽象父类的get方法间接的调用native方法getReferent()。下面再来看KeyEvent.Callback接口定义。
public interface Callback { /** * Called when a key down event has occurred. If you return true, * you can first call {@link KeyEvent#startTracking() * KeyEvent.startTracking()} to have the framework track the event * through its {@link #onKeyUp(int, KeyEvent)} and also call your * {@link #onKeyLongPress(int, KeyEvent)} if it occurs. * * @param keyCode The value in event.getKeyCode(). * @param event Description of the key event. * * @return If you handled the event, return true. If you want to allow * the event to be handled by the next receiver, return false. */ boolean onKeyDown(int keyCode, KeyEvent event); /** * Called when a long press has occurred. If you return true, * the final key up will have {@link KeyEvent#FLAG_CANCELED} and * {@link KeyEvent#FLAG_CANCELED_LONG_PRESS} set. Note that in * order to receive this callback, someone in the event change * must return true from {@link #onKeyDown} and * call {@link KeyEvent#startTracking()} on the event. * * @param keyCode The value in event.getKeyCode(). * @param event Description of the key event. * * @return If you handled the event, return true. If you want to allow * the event to be handled by the next receiver, return false. */ boolean onKeyLongPress(int keyCode, KeyEvent event); /** * Called when a key up event has occurred. * * @param keyCode The value in event.getKeyCode(). * @param event Description of the key event. * * @return If you handled the event, return true. If you want to allow * the event to be handled by the next receiver, return false. */ boolean onKeyUp(int keyCode, KeyEvent event); /** * Called when multiple down/up pairs of the same key have occurred * in a row. * * @param keyCode The value in event.getKeyCode(). * @param count Number of pairs as returned by event.getRepeatCount(). * @param event Description of the key event. * * @return If you handled the event, return true. If you want to allow * the event to be handled by the next receiver, return false. */ boolean onKeyMultiple(int keyCode, int count, KeyEvent event); }
KeyEnvent类继承自InputEvent,该类定义了许多keyCode值,例如:
public static final int KEYCODE_SOFT_LEFT = 1; /** Key code constant: Soft Right key. * Usually situated below the display on phones and used as a multi-function * feature key for selecting a software defined function shown on the bottom right * of the display. */ public static final int KEYCODE_SOFT_RIGHT = 2; /** Key code constant: Home key. * This key is handled by the framework and is never delivered to applications. */ public static final int KEYCODE_HOME = 3; /** Key code constant: Back key. */ public static final int KEYCODE_BACK = 4; /** Key code constant: Call key. */ public static final int KEYCODE_CALL = 5;
用户操作是只执行相应的回调函数,例如按住返回键,那么就会执行KeyEvent.Callback 的onKeyDown(KEYCODE_BACK,KeyEvent),在我们开发时,判断KeyEvent.getAction判断Action类型和keyCode值选择执行相应函数,return boolean 如果返回值为true代表处理该事件不在向下分发,false则不处理该事件。(Action是在KeyEvent构造参数时传入)Action在KeyEvent的定义如下:
/** * {@link #getAction} value: the key has been pressed down. */ public static final int ACTION_DOWN = 0; /** * {@link #getAction} value: the key has been released. */ public static final int ACTION_UP = 1; /** * {@link #getAction} value: multiple duplicate key events have * occurred in a row, or a complex string is being delivered. If the * key code is not {#link {@link #KEYCODE_UNKNOWN} then the * {#link {@link #getRepeatCount()} method returns the number of times * the given key code should be executed. * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then * this is a sequence of characters as returned by {@link #getCharacters}. */ public static final int ACTION_MULTIPLE = 2; //..........................此处略....................................
KeyEvent.Callback的onKeyUp方法也onKeyDown差别不大,区别在于事件的响应时间,一个是按下一个是抬起。View和Activity同时重写onKeyDown onkeyUp方法会先执行View的。
/** * This interface is implemented by classes source of {@link AccessibilityEvent}s. * * * Developer Guides
* For more information about making applications accessible, read the * Accessibility * developer guide.
* */public interface AccessibilityEventSource { /** * Handles the request for sending an {@link AccessibilityEvent} given * the event type. The method must first check if accessibility is on * via calling {@link AccessibilityManager#isEnabled() AccessibilityManager.isEnabled()}, * obtain an {@link AccessibilityEvent} from the event pool through calling * {@link AccessibilityEvent#obtain(int) AccessibilityEvent.obtain(int)}, populate the * event, and send it for dispatch via calling * {@link AccessibilityManager#sendAccessibilityEvent(AccessibilityEvent) * AccessibilityManager.sendAccessibilityEvent(AccessibilityEvent)}. * * @see AccessibilityEvent * @see AccessibilityManager * * @param eventType The event type. */ public void sendAccessibilityEvent(int eventType); /** * Handles the request for sending an {@link AccessibilityEvent}. The * method does not guarantee to check if accessibility is on before * sending the event for dispatch. It is responsibility of the caller * to do the check via calling {@link AccessibilityManager#isEnabled() * AccessibilityManager.isEnabled()}. * * @see AccessibilityEvent * @see AccessibilityManager * * @param event The event. */ public void sendAccessibilityEventUnchecked(AccessibilityEvent event);}
sendAccessibilityEventUnchecked() (API级别4)当调用代码需要直接控制检查辅助功能设备的启用时,调用该方法。 sendAccessibilityEvent()(API级别4)当用户在一个视图操作时调用此方法。事件是按照用户操作类型分类,如TYPE_VIEW_CLICKED。你通常不需要实现该方法,除非你是创建一个自定义视图。
private static final boolean DBG = false;
定义全局final变量DBG,如果是调试模式,在调用某些方法调试时输出日志,例如:
protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (DBG) { Log.d("View", this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } //.......................此处略....................... }
Log.i/e/w(TAG,”“)方法中TAG定义:
/** * The logging tag used by this class with android.util.Log. */ protected static final String VIEW_LOG_TAG = "View";
下面的再来看一组变量定义:
/** * When set to true, apps will draw debugging information about their layouts. * * @hide */ public static final String DEBUG_LAYOUT_PROPERTY = "debug.layout"; /** * Show where the margins, bounds and layout bounds are for each view. */ boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false);
SystemProperties类位置在framework层,查看请点击SystemProperties,这里面的内容简单的封装了set get 方法,set get方法内部分别调用了native 方法代码如下:
/** * Gives access to the system properties store. The system properties * store contains a list of string key-value pairs. * * {@hide} */public class SystemProperties{ public static final int PROP_NAME_MAX = 31; public static final int PROP_VALUE_MAX = 91; private static final ArrayList sChangeCallbacks = new ArrayList(); private static native String native_get(String key); private static native String native_get(String key, String def); private static native int native_get_int(String key, int def); private static native long native_get_long(String key, long def); private static native boolean native_get_boolean(String key, boolean def); private static native void native_set(String key, String def); private static native void native_add_change_callback(); /** * Get the value for the given key, returned as a boolean. * Values 'n', 'no', '0', 'false' or 'off' are considered false. * Values 'y', 'yes', '1', 'true' or 'on' are considered true. * (case sensitive). * If the key does not exist, or has any other value, then the default * result is returned. * @param key the key to lookup * @param def a default value to return * @return the key parsed as a boolean, or def if the key isn't found or is * not able to be parsed as a boolean. * @throws IllegalArgumentException if the key exceeds 32 characters */ public static boolean getBoolean(String key, boolean def) { if (key.length() > PROP_NAME_MAX) { throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); } return native_get_boolean(key, def); } //.......................其他略...............................}
该类的作用是获取设置系统属性,进行系统属性设置的程序也必须有system或root权限,
如何将android程序的权限提升到system权限?方法是这样的:
1.在AndroidManifest.xml中,在manifest加入android:sharedUserId=”android.uid.system”。
2.在Android.mk中,將LOCAL_CERTIFICATE := XXX修改成LOCAL_CERTIFICATE :=platform。
该类参考资料:http://blog.csdn.net/ameyume/article/details/8056492
/** * Used to mark a View that has no ID. */ public static final int NO_ID = -1;
NO_ID变量让属性的初始值引用,代码实例如下:
public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { //.............此处略....................... case com.android.internal.R.styleable.View_id: mID = a.getResourceId(attr, NO_ID); break; case com.android.internal.R.styleable.View_tag: mTag = a.getText(attr); break;}
再来看一下三个变量定义:sCompatibilityDone 、sUseBrokenMakeMeasureSpec 、sIgnoreMeasureCache
/** * Signals that compatibility booleans have been initialized according to * target SDK versions. */ private static boolean sCompatibilityDone = false; /** * Use the old (broken) way of building MeasureSpecs. */ private static boolean sUseBrokenMakeMeasureSpec = false; /** * Ignore any optimizations using the measure cache. */ private static boolean sIgnoreMeasureCache = false;
这三个参数在构造方法里面进行了初始化赋值
public View(Context context) { //..............此处略.................. if (!sCompatibilityDone && context != null) { final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; // Older apps may need this compatibility hack for measurement. sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1; // Older apps expect onMeasure() to always be called on a layout pass, regardless // of whether a layout was requested on that View. sIgnoreMeasureCache = targetSdkVersion < KITKAT; sCompatibilityDone = true; } }
sCompatibilityDone 有何意义我还没发现,待后续研究,sUseBrokenMakeMeasureSpec 变量用于判断使用哪一种方式(老版本的方法还是新版本的方法)构建MeasureSpec,MeasureSpec是测量相关,稍后再提。
public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } }
sIgnoreMeasureCache 忽略任何优化使用缓存,在measure测量方法里会用到该参数,稍后再细说measure方法。接着来看一组焦点相关的变量定义:
/** * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when * calling setFlags. */ private static final int NOT_FOCUSABLE = 0x00000000; /** * This view wants keystrokes. Use with TAKES_FOCUS_MASK when calling * setFlags. */ private static final int FOCUSABLE = 0x00000001; /** * Mask for use with setFlags indicating bits used for focus. */ private static final int FOCUSABLE_MASK = 0x00000001;
在我们平时控制View的焦点响应方法setFocusable(boolean focusable)其本质就是在根据传入参数获取一个int 型 flag值,随后调用了setFlags隐藏方法;
/** * Set whether this view can receive the focus. * * Setting this to false will also ensure that this view is not focusable * in touch mode. * * @param focusable If true, this view can receive the focus. * * @see #setFocusableInTouchMode(boolean) * @attr ref android.R.styleable#View_focusable */ public void setFocusable(boolean focusable) { if (!focusable) { setFlags(0, FOCUSABLE_IN_TOUCH_MODE); } setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK); }
View.setVisibility也是如此(同时还判断选择调用Drawale的setVisibilty,通过回调接口Drawable.Callback的 invalidateDrawable(this)方法控制重绘)
/** * Set the enabled state of this view. * * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. * @attr ref android.R.styleable#View_visibility */ @RemotableViewMethod public void setVisibility(@Visibility int visibility) { setFlags(visibility, VISIBILITY_MASK); if (mBackground != null) mBackground.setVisible(visibility == VISIBLE, false); }
隐藏方法setFlag对我来说无疑是神秘的,位运算搞得我头大,查了一番资料,马马虎虎明白其原理,位运算相关知识链接点击这里,该方法的具体实现如下:
/** * Set flags controlling behavior of this view. * * @param flags Constant indicating the value which should be set * @param mask Constant indicating the bit range that should be changed */ void setFlags(int flags, int mask) { final boolean accessibilityEnabled = AccessibilityManager.getInstance(mContext).isEnabled(); final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility(); //old保存mViewFlags之前的值, (mViewFlags在构造方法里初始化了) int old = mViewFlags; mViewFlags = (mViewFlags & ~mask) | (flags & mask); //old和mViewFlags异或后得到一个新的changed 凡是为1 说明发送了变化, 就需要View系统进行调整了! int changed = mViewFlags ^ old; if (changed == 0) { /********* 什么时候能有中文版注释的系统源码,偶滴神啊,头大了 flags = 0x0000000100 int old = mViewFlags; //旧的flag = 0x00000000 mViewFlags = (mViewFlags & ~mask) | (flags & mask); //新的flag 0x00000000 & ~(00000001100) | & 00000001100 = 0x0000000100 int changed = mViewFlags ^ old; = 0x0000000100 if (changed == 0) { return; //hey ! 没有变化! 直接return! ************/ return; } //...............此处略.................. /* Check if the GONE bit has changed */ if ((changed & GONE) != 0) { needGlobalAttributesUpdate(false); requestLayout(); if (((mViewFlags & VISIBILITY_MASK) == GONE)) { //清除view的cache if (hasFocus()) clearFocus(); clearAccessibilityFocus(); destroyDrawingCache(); if (mParent instanceof View) { // GONE views noop invalidation, so invalidate the parent //重绘 ((View) mParent).invalidate(true); } // Mark the view drawn to ensure that it gets invalidated properly the next // time it is visible and gets invalidated mPrivateFlags |= PFLAG_DRAWN; } if (mAttachInfo != null) { mAttachInfo.mViewVisibilityChanged = true; } } //........此处略....方法说明requestLayout():重新布局;invalidate(true):重绘.......... }
半天多时间已去,第一篇View blog也出来了,我也先学先写,差了一些资料发现了很多以前不懂的知识,向下等看完View的所有源代码,对自己会有一个很大的提升,逗比的一天接近尾声,看会儿爱哥的设计模式去,坐等下班。