美文网首页
源码:LayoutInflater ,Hook 拦截 View

源码:LayoutInflater ,Hook 拦截 View

作者: xqiiitan | 来源:发表于2024-07-01 08:28 被阅读0次

3. LayoutInflater 源码阅读

layoutInflater 主要用来inflater 解析layout 布局

// 使用的三种方式:
// ----第一个方式,实际调用的下面的方法2。----
View layoutView = View.inflate(this, R.layout.activity_main, null); 
// ----第二个方式,实际调用的第三个方法。----
layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
// ----第三个方式。----
layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main, null, false);

inflate() 就是调用的factory.inflate(resource, root);

// 第一个方法使用,调用第二个方法。
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root); // 调用第二个方法。
}
// 第二种方法,最后调用第三种方法。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root!=null); // 调用第三种方法。
}
// 所以最终只要看第三个方法即可。

3.1 LayoutInflater.from(this) 实例 怎么获取的。

// LayoutInflater
public static LayoutInflater from(@UiContext Context context) {
    // 拿到系统的服务。
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    return LayoutInflater;
}
// context -> 实现类ContextImpl.getSystemService()
@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}
// SystemServiceRegistry 
HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new HashMap<>();
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); // 从静态map集合中拿 单例的设计模式
    return fetcher!=null ? fetcher.getService(ctx) :null;
}
// 添加服务--注册服务, 让HashMap中有值。
static {
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
        new CachedServiceFetcher<LayoutInflater>() {
    @Override
    public LayoutInflater createService(ContextImpl ctx) {
        return new PhoneLayoutInflater(ctx.getOuterContext());
    }});
}

// LayoutInflater.from(this) 它是一个系统Service服务;它是单例的设计模式。内存中只有一个实例。
// LayoutInflaterCompat.setFactory2(layoutInflater, this); 给单例设置Factory。

3.2 inflater(resId, contentParent) 方法的源码

// LayoutInflater
-- inflate(int resource, ViewGroup root, boolean attachToRoot)
-- public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {}
-- final View temp = createViewFromTag(root, name, inflaterContext, attrs);
-- createViewFromTag(parent, name, context, attrs, false);
-- View view = tryCreateView(parent, name, context, attrs);

    View view;
    // 前提是看是否有设置mFactory2 和 mFactory。AppCompatActivity中设置了mFactory。
    if (mFactory2 != null) { // 【关键代码】:TODO:这里就会走自己设置的Factory2 的逻辑。
        view = mFactory2.onCreateView(parent, name, context, attrs);
    } else if (mFactory != null) { // TODO:这里就会走自己设置的Factory 的逻辑。
        view = mFactory.onCreateView(name, context, attrs);
    } else {
        view = null;
    }
    // 通过反射创建view
    // ...

-- mFactory.onCreateView()--> 就会走自己的onCreateView()方法,
--> return mAppCompatViewInflater.createView(...);
-- -- AppCompatViewInflater.createView()
-- -- switch-case,拦截View的创建。

createViewFromTag

系统的View组件,

try {
    View view = tryCreateView(parent, name, context, attrs);

    if (view == null) {
        final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
        try {
            if (-1 == name.indexOf('.')) { // 系统的View组件
                view = onCreateView(context, parent, name, attrs);
            } else {
                view = createView(context, name, null, attrs);
            }
        } finally {
            mConstructorArgs[0] = lastContext;
        }
    }

--> onCreateView(@NonNull Context viewContext, @Nullable View parent,
@NonNull String name, @Nullable AttributeSet attrs)
--> onCreateView(parent, name, attrs);
--> onCreateView(name, attrs);
--> createView(name, "android.view.", attrs) // 拼接系统view的默认前缀

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Context context = (Context) mConstructorArgs[0];
    if (context == null) {
        context = mContext; // 设置context上下文。
    }
    return createView(context, name, prefix, attrs);
}
public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
        
    Objects.requireNonNull(viewContext);
    Objects.requireNonNull(name);
    Constructor<? extends View> constructor = sConstructorMap.get(name); // 构造方法缓存在map中。
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;
    try {
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);

            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
            // mConstructorSignature 是2个参数的View的 构造方法[xml中设置的布局]
            constructor = clazz.getConstructor(mConstructorSignature); // 拿到构造函数
            constructor.setAccessible(true); // 私有公有都可访问
            sConstructorMap.put(name, constructor); // 加入缓存map中
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                // Have we seen this name before?
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    // New class -- remember whether it is allowed
                    clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                            mContext.getClassLoader()).asSubclass(View.class);
                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
        }

        Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = viewContext;
        Object[] args = mConstructorArgs;
        args[1] = attrs;

        try {
            final View view = constructor.newInstance(args); // 构造函数new一个实例,返回
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
        } finally {
            mConstructorArgs[0] = lastContext;
        }
    } 
}
// so:不要布局嵌套,因为view都是通过反射来创建view的。
// View创建的方式:
// 系统的Button, 不包含'.'一般会拼接 android.view;
// 自定义的com.jd.MyTextView; 
//     包含点'.', 拼接
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    XmlResourceParser parser = res.getLayout(resource); // 解析器
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext; // 构造函数的 参数
        View result = root;

        if (root != null && root.getViewRootImpl() != null) {
            root.getViewRootImpl().notifyRendererOfExpensiveFrame();
        }
        try {
            advanceToRootNode(parser);
            final String name = parser.getName();

            if (TAG_MERGE.equals(name)) { // 处理merge
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                if (root == null && temp != null && temp.getViewRootImpl() != null) {
                    temp.getViewRootImpl().notifyRendererOfExpensiveFrame();
                }
                ViewGroup.LayoutParams params = null;
                if (root != null) { // 这里决定了有时候设置的属性无效问题。
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }

                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            final InflateException ie = new InflateException(e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(inflaterContext, attrs)
                    + ": " + e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        return result;
    }
}

4. Hook 拦截 View 的创建,仿照系统的

仿照系统的AppCompatActivity 去设置Factory就可以。

public abstract class BaseSkinActivity extends BaseActivity {
    private static final String TAG = "BaseSkinActivity";
    // TODO 后期添加插件换肤功能。预留层。每次换肤,都遍历集合View,然后修改
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 添加换肤相关, 在super之前调用。
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        LayoutInflaterCompat.setFactory(layoutInflater, new LayoutInflaterFactory() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                // 拦截到View的创建
                Log.e(TAG, "拦截了View的创建");
                if (name.equals("Button")) { // Button 变 TextView 
                    TextView tv = new TextView(BaseSkinActivity.this);
                    tv.setText("555 我被拦截了,并修改了");
                    return tv;
                }
                return null;
            }
        });
        super.onCreate(savedInstanceState);
    }
}

5. 总结:

  1. Activity是怎么加载布局的
  2. AppCompatActivity怎么加载布局的
  3. 为什么5.0的MaterialDesign 看起来不一样,但是又兼容低版本。且低版本能使用高版本的一些特性
  4. 界面是怎么加载的。LayoutInflater.from(this)
  5. View 是通过反射创建的,但是可以拦截。看有没有设置Factory。

相关文章

网友评论

      本文标题:源码:LayoutInflater ,Hook 拦截 View

      本文链接:https://www.haomeiwen.com/subject/ngdpcjtx.html