Hook拦截View的创建

首先看activity的setContentView的源码

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

我们点击setContetnView会发现此是个抽象方法,这时候我们看下getWindow的方法我们最终会发现它实际是PhoneWindow

 mWindow = new PhoneWindow(this, window, activityConfigCallback);

我们看PhoneWindow的setContentView的源码

 @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } 
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //最后我们将我们设置的布局设置mContentParent进去
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
    }

installDecor源码分析

 if (mDecor == null) {
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) { 
            mContentParent = generateLayout(mDecor);
}

我们进去后我们会发现实际mDecor是继承于FrameLayout,重点我们看这个 mContentParent = generateLayout(mDecor);

  protected ViewGroup generateLayout(DecorView decor) {
          int layoutResource;
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else {
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
     ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
     return contentParent;
}

因为源码太长就不全部粘贴了,直接贴重要的,我们看 R.layout.screen_simple;布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

好了这里我们可以看到了mContentParent = generateLayout(mDecor);实际返回的是系统里面的FrameLayout,最后我们发现setContentView调用的是 - mLayoutInflater.inflate(layoutResID, mContentParent);将自己的布局设置进去

activity源码阅读.png

AppCompatActivity的setContentView源码分析

 @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

最终我的源码是调用的是AppCompatDelegateImplV9中的setContentView,用的源码可能是AppCompatDelegateImplV7

 @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

我们发现实际和Activity类似

我们来做个小实验,假设我们有个ImageView,当MainActivity继承于Activity的时候,我们打印imageview的时候,我们会发现是imageview,接下来我们将MainActivity继承于AppCompatActivity,我们会发现ImageView被换成了 android.support.v7.widget.AppCompatImageView,这时候我们会很好奇,怎么被换了,其实是被拦截了,我们来看下AppCompatActivity拦截的源码

回到AppCompatDelegateImplV9我们会发现 LayoutInflater.Factory2

class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
       implements MenuBuilder.Callback, LayoutInflater.Factory2 

LayoutInflater.Factory2这个方法就是拦截的重要方法我们可以看下它是怎么设置的,直接搜setFactory2

 @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

我们点击setFactor2进去后我们会发现这个方法会重新复写onCreateView

    @Override
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        // First let the Activity's Factory try and inflate the view
        final View view = callActivityOnCreateView(parent, name, context, attrs);
        if (view != null) {
            return view;
        }

        // If the Factory didn't handle it, let our createView() method try
        return createView(parent, name, context, attrs);
    }

一直走下去我们最终会发现源码


源码.png

看到这里我们知道它实际是被拦截了,这时候我们看下AppCompat在设置布局的时候是怎么拦截的

我们设置布局有很多方式 LayoutInflater.from(this).inflate()或者setContentView最终调用的都是这个方法

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

接下来我们找到以下的方法

  final View temp = createViewFromTag(root, name, inflaterContext, attrs);

最后走到这里

  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
try {
            View view;
            if (mFactory2 != null) {//如果设置拦截则会走向这里
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                   //是不是自己定义的View
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
  }
}

由上面我们可以做个小案例进行拦截View

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        LayoutInflater layoutInflater = LayoutInflater.from(this);

        LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                Log.e("TAG", "拦截view的创建");
                if ("Button".equals(name)) {
                    TextView textView = new TextView(BaseSkinActivity.this);
                    textView.setText("拦截美女");
                    return textView;
                }
                return null;
            }

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                return null;
            }
        });
        //一定要放在下面否则报错
        super.onCreate(savedInstanceState);
        //这方法已经过时了
        /*LayoutInflaterCompat.setFactory(layoutInflater, new LayoutInflaterFactory() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                return null;
            }
        });*/
    }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容