Android Window、PhoneWindow、Activity学习心得第二弹

  • Post author:
  • Post category:其他


Android Window、PhoneWindow、Activity学习心得第二弹

Window 分析


这里先给出部分源码

目录(Android 4.4/frameworks/base/core/java/android/view/Window.java)

public abstract class Window {
public static final int FEATURE_OPTIONS_PANEL = 0;
public static final int FEATURE_NO_TITLE = 1;
.......
public static final int FEATURE_CONTEXT_MENU = 6;
public static final int FEATURE_CUSTOM_TITLE = 7;
public static final int FEATURE_ACTION_BAR = 8;
public static final int FEATURE_ACTION_BAR_OVERLAY = 9;
.......
public interface Callback {
.....
public boolean dispatchKeyEvent(KeyEvent event);
......
public boolean dispatchTouchEvent(MotionEvent event);
.....
public boolean dispatchGenericMotionEvent(MotionEvent event);
....
public View onCreatePanelView(int featureId);
....
public boolean onMenuItemSelected(int featureId, MenuItem item);
.....
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);
public void onContentChanged();
public void onWindowFocusChanged(boolean hasFocus);
public void onAttachedToWindow();
public void onDetachedFromWindow();
public void onPanelClosed(int featureId, Menu menu);
public boolean onSearchRequested();
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback);
public void onActionModeStarted(ActionMode mode);
public void onActionModeFinished(ActionMode mode);
}
public void setCallback(Callback callback) {
mCallback = callback;
}
public void setFlags(int flags, int mask) {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.flags = (attrs.flags&~mask) | (flags&mask);
if ((mask&WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0) {
attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY;
        }
mForcedWindowFlags |= mask;
if (mCallback != null) {
mCallback.onWindowAttributesChanged(attrs);
}
}
public boolean requestFeature(int featureId) {
final int flag = 1<<featureId;
mFeatures |= flag;
mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
return (mFeatures&flag) != 0;
}
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
public abstract void setContentView(int layoutResID);
public abstract void setTitle(CharSequence title);
public abstract LayoutInflater getLayoutInflater();

}

首先,正如我们所知道的通过回调的方式Activity implements  Window.Callback




Callback主要用来处理



按键事件(dispatchKeyEvent)



触摸事件 (dispatchTouchEvent) 滑动事件(dispatchTrackballEvent)等等一系列事件



设置菜单、ActionMOde、监控内容等一系列动作







正是因为




        mWindow = PolicyManager.makeNewWindow(this);
        mWindow.setCallback(this);

监听回调,所以一系列最终的处理交给了Activity



当然,这里对于处理不做具体分析



接下来,我们看看




Window属性






FEATURE_OPTIONS_PANEL = 0; 功能不明,参见后面的说明(默认使能)




FEATURE_NO_TITLE = 1; 无标题栏




FEATURE_PROGRESS = 2; 在标题栏上显示加载进度,例如webview加载网页时(条状进度条)




FEATURE_LEFT_ICON = 3; 在标题栏左侧显示一个图标




FEATURE_RIGHT_ICON = 4; 在标题栏右侧显示一个图标




FEATURE_INDETERMINATE_PROGRESS = 5; 不确定的进度(圆圈状等待图标)




FEATURE_CONTEXT_MENU = 6; 上下文菜单,相当于PC上的右键菜单(默认使能)




FEATURE_CUSTOM_TITLE = 7; 自定义标题栏,该属性不能与其他标题栏属性合用




FEATURE_OPENGL = 8; 如果开启OpenGL,那么2D将由OpenGL处理(OpenGL中2D是3D的子集)




PROGRESS_VISIBILITY_ON = -1; 进度条可见




PROGRESS_VISIBILITY_OFF = -2; 进度条不可见




PROGRESS_INDETERMINATE_ON = -3; 开启不确定模式




PROGRESS_INDETERMINATE_OFF = -4; 关闭不确定模式




PROGRESS_START = 0; 第一进度条的最小值




PROGRESS_END = 10000; 第一进度条的最大值




PROGRESS_SECONDARY_START = 20000; 第二进度条的最小值




PROGRESS_SECONDARY_END = 30000; 第二进度条的最大值









这些 Feature 和 Flag  有什么用呢,其实很明显他们决定了我们Activity外貌(风格和样式)



那么,接下来我们就来看看Window的具体实现类PhoneWindow



目录(Android 4.4/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java)




public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
.....
}

还记得我们第一弹中给出的关于Window、PhoneWindow、DecorView的关系图吗,这里我们稍作回顾










同样,我们知道


知道,每一个应用程序窗口的视图对象都有一个关联的ViewRoot对象,这些关联关系是由窗口管理器来维护的


















简单来说,ViewRoot相当于是MVC模型中的Controller,它有以下职责:







1. 负责为应用程序窗口视图创建Surface。


2. 配合WindowManagerService来管理系统的应用程序窗口。


3. 负责管理、布局和渲染应用程序窗口视图的UI。

既然如此,那么接下来我们便开始构建视图UI



例如:我们创建自己的Activity,并在其中写道




@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
}

同样我们跟踪发现




public class Activity extends ContextThemeWrapper  
        implements LayoutInflater.Factory,  
        Window.Callback, KeyEvent.Callback,  
        OnCreateContextMenuListener, ComponentCallbacks {  
    ......  
  
    private Window mWindow;  
    ......  
  
    public Window getWindow() {  
        return mWindow;  
    }  
    ......  
  
public final boolean requestWindowFeature(int featureId) {
    return getWindow().requestFeature(featureId);
}
    public void setContentView(int layoutResID) {  
        getWindow().setContentView(layoutResID);  
    }  
  
    ......  
}  

他们最终交给了PhoneWindow(Window)处理



同样在PhoneWindow中




public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ......

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;
    ......

public boolean requestFeature(int featureId) {

//这里 requestFeature  必须在 setContentView之前
      if (mContentParent != null) {
            throw new AndroidRuntimeException("requestFeature() must be called before adding content");
}

final int features = getFeatures();
//最终在Window中处理
return super.requestFeature(featureId);
}

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }

    ......
}


PhoneWindow类的成员变量mContentParent用来描述一个类型为DecorView的视图对象,或者这个类型为DecorView的视图对象的一个子视图对象,用作UI容器。当它的值等于null的时候,就说明正在处理的应用程序窗口的视图对象还没有创建。在这种情况下,就会调用成员函数installDecor来创建应用程序窗口视图对象。否则的话,就说明是要重新设置应用程序窗口的视图。在重新设置之前,首先调用成员变量mContentParent所描述的一个ViewGroup对象来移除原来的UI内空。



由于我们是在Activity组件启动的过程中创建应用程序窗口视图的,因此,我们就假设此时PhoneWindow类的成员变量mContentParent的值等于null。接下来,函数就会调用成员函数installDecor来创建应用程序窗口视图对象,接着再通过调用PhoneWindow类的成员变量mLayoutInflater所描述的一个LayoutInflater对象的成员函数inflate来将参数layoutResID所描述的一个UI布局设置到前面所创建的应用程序窗口视图中去,最后还会调用一个Callback接口的成员函数onContentChanged来通知对应的Activity组件,它的视图内容发生改变了。








那么接下来我们便开始初始化DecorView




public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ......

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    ......

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;
    ......

    private TextView mTitleView;
    ......

    private CharSequence mTitle = null;
    ......

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            ......
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer =
                            findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            }
        }
    }

    ......
}

接下来,我们便开始构建




 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
......

public DecorView(Context context, int featureId) {
       super(context);
       mFeatureId = featureId;
}
}


protected DecorView generateDecor() {
             return new DecorView(getContext(), -1);
}

protected ViewGroup generateLayout(DecorView decor) {

        TypedArray a = getWindowStyle();
        mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
        mIsTranslucent = a.getBoolean(com.android.internal.R.styleable.Window_windowIsTranslucent, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
                setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) {
            requestFeature(FEATURE_ACTION_BAR);
}
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) {
            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowTranslucentStatus,
                false)) {
            setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                    & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowTranslucentNavigation,
                false)) {
            setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
                    & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowOverscan, false)) {
            setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
            setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowEnableSplitTouch,
                getContext().getApplicationInfo().targetSdkVersion
                        >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
            setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
        }

        a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
        a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
        if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor)) {
            if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
            a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor,
                    mFixedWidthMajor);
        }
        if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor)) {
            if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
            a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor,
                    mFixedWidthMinor);
        }
        if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor)) {
            if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
            a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor,
                    mFixedHeightMajor);
        }
        if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor)) {
            if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
            a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor,
                    mFixedHeightMinor);
        }

        final Context context = getContext();
        final int targetSdk = context.getApplicationInfo().targetSdkVersion;
        final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
        final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
        final boolean targetHcNeedsOptions = context.getResources().getBoolean(
                com.android.internal.R.bool.target_honeycomb_needs_options_menu);
        final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);

        if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
            addFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);
        } else {
            clearFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);
        }

        if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
                >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            if (a.getBoolean(
                    com.android.internal.R.styleable.Window_windowCloseOnTouchOutside,
                    false)) {
                setCloseOnTouchOutsideIfNotSet(true);
            }
        }

        WindowManager.LayoutParams params = getAttributes();

        if (!hasSoftInputMode()) {
            params.softInputMode = a.getInt(
                    com.android.internal.R.styleable.Window_windowSoftInputMode,
                    params.softInputMode);
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,
                mIsFloating)) {
            /* All dialogs should have the window dimmed */
            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            }
            if (!haveDimAmount()) {
                params.dimAmount = a.getFloat(
                        android.R.styleable.Window_backgroundDimAmount, 0.5f);
            }
        }

        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
        }

        // The rest are only done if this window is not embedded; otherwise,
        // the values are inherited from our container.
        if (getContainer() == null) {
            if (mBackgroundDrawable == null) {
                if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            com.android.internal.R.styleable.Window_windowBackground, 0);
                }
                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);
                }
                if (false) {
                    System.out.println("Background: "
                            + Integer.toHexString(mBackgroundResource) + " Frame: "
                            + Integer.toHexString(mFrameResource));
                }
            }
            mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);
        }

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = com.android.internal.R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = com.android.internal.R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = com.android.internal.R.layout.screen_action_bar;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = com.android.internal.R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "DecorView-inflate");
        View in = mLayoutInflater.inflate(layoutResource, null);
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        
        //add for Multi window
        if (mMultiWindow && !mIsFloating){  
          if (context.getPackageName().contains("launcher") || mIsTranslucent){
                Log.d(TAG, "MW phoneWindow do nothing"); 
           } else {
               Log.d(TAG, "MW PhoneWindow decor start "); 
               in = prepareMultiWindow(context,in);
               Log.d(TAG, "MW PhoneWindow decor end "); 
           }
        }
        //end
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        //Remaining setup - of background and title - that only applies
        //to top-level windows.
        if (getContainer() == null) {
            Drawable drawable = mBackgroundDrawable;
            if (mBackgroundResource != 0) {
                drawable = getContext().getResources().getDrawable(mBackgroundResource);
            }
            mDecor.setWindowBackground(drawable);
            drawable = null;
            if (mFrameResource != 0) {
                drawable = getContext().getResources().getDrawable(mFrameResource);
            }
            mDecor.setWindowFrame(drawable);

            // System.out.println("Text=" + Integer.toHexString(mTextColor) +
            // " Sel=" + Integer.toHexString(mTextSelectedColor) +
            // " Title=" + Integer.toHexString(mTitleColor));

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }

            if (mTitle != null) {
                setTitle(mTitle);
            }
            setTitleColor(mTitleColor);
        }

        mDecor.finishChanging();

        return contentParent;

到了这里,我们依据不同的Feature和Flag 来初始化layout  算是初步完成









版权声明:本文为jaysong2012原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。