AppcompatActivity拦截View创建方法

本文详细介绍了如何通过拦截LayoutInflater创建View的过程来实现自定义View的创建流程,并特别关注AppcompatActivity中的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

FragementActivity&AppcompatActivity拦截View创建方法

在拦截之前,我们要先清楚 系统是怎么创建各个View的,如果看过LayoutInflate的源码的话,可能这部分就比较容易理解

本文源码版本: API 25

1 LayoutInflater创建一个View的过程

LayoutInflater inflater = LayoutInflater.from(context).inflate(LayoutResId,ViewRoot);

大家一定用过上面这行代码,这行代码在ListView或者RecycleView中尤为常见,那么系统是如何将这个layout文件变成View的呢?还记得setContentView(LayoutResId)么,Activity在加载这个View的时候是不是一样呢?

  • LayoutInflater.inflate
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
}

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        //获取Resources对象
        final Resources res = getContext().getResources();
        //拿到 一个layout文件的 解析器
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
 }  


//我删除了一部分代码,其实这里主要就是 对xml文件的解析
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {

            //这部分代码很值得借鉴,
            final Context inflaterContext = mContext;
            //获取root节点的属性,这里就不进去看了,咱们关注的重点不在这里
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            // new View(Context context,AttributeSet attr)
            //这个参数就是 context
            mConstructorArgs[0] = inflaterContext;


            View result = root;

            try {

                final String name = parser.getName();


                if (TAG_MERGE.equals(name)) {
                    //这里是 marge标签的处理哟,marge标签不需要创建
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //递归处理直接点的View,其实最后真正创建View还是走的createViewFromTag方法
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    //这里是rootView的处理,第一个节点的处理
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;
                    if (root != null) {

                        //为rootView创建LayoutParams
                        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);
                        }
                    }


                    // 创建子节点的View
                    rInflateChildren(parser, temp, attrs, true);
                    ....
                }

            } catch (XmlPullParserException e) {
            } catch (Exception e) {
            } finally {
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }
            return result;
        }
    }

以上是处理 layout.xml文件的方式,下面来看 真正创建View的方法

  private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }

//同样省略一些代码
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // 应用主题...省略

        try {
            View view;
            //factory 咱们关心的重点
            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 {
                    if (-1 == name.indexOf('.')) {
                        //是系统的View,前面没有包名
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
        } catch (InflateException e) {

        } catch (ClassNotFoundException e) {

        } catch (Exception e) {
        }
    }

从上面可以看出,只有当factory不为空的时候,并且创建View没有成功的时候,才会走下面的createView和onCreateView,那么factory是什么,其实看起来更像是一个过滤器,为什么有factory和factory2呢,因为版本兼容.
这里的分析到这里为止,我简单说下createView和onCreateView 里面的逻辑

onCreateView 创建系统自带的View

createView 创建自定义的View

2 AppcompatActivity

要想拦截 AppcompatActivity 创建View的过程,可以用下面的方法来做,其实就是给LayoutInflate重新设置factory

ublic class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LayoutInflater inflater = LayoutInflater.from(this);

        LayoutInflaterCompat.setFactory(inflater, new LayoutInflaterFactory() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

                return null;
            }
        });
        setContentView(R.layout.activity_main);
    }

这个时候,我们就要明白AppCompatActivity 有没有用factory,我们只是添加自己的逻辑,进行拦截修改,而不是完全重写,所以这里我们要先完成AppCompatActivity 所做的事情,然后拿到View之后再做处理,当然也可以在之前做处理

我们来看AppCompatActivity是怎么创建View的

  • AppCompatActivity

private AppCompatDelegate mDelegate;
 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        //在这里初始化了factory
        delegate.installViewFactory();
        ....
    }

AppCompatDelegate是一个抽象方法,installViewFactory也是一个抽象方法

public abstract class AppCompatDelegate

 public abstract void installViewFactory();

ctrl+shift+h,我们来看这个方法在哪里实现了

AppCompatDelegate

上面的图很有意思,可以看到google为了兼容做的处理

  • AppCompatDelegateImplV9 实现 了这个方法
@Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            //factory是本身
            LayoutInflaterCompat.setFactory(layoutInflater, this);
        } else {
           //已经设置了factory,并且不是本类的实现,那么不做任何处理
        }
    }

那么我们去看重写的createView方法


 /**
     * From {@link android.support.v4.view.LayoutInflaterFactory}
     */
    @Override
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        // First let the Activity's Factory try and inflate the view
        //首先使用activity的factory去加载view
        final View view = callActivityOnCreateView(parent, name, context, attrs);
        if (view != null) {
            return view;
        }

        // 如果activity的factory没有创建这个View
        return createView(parent, name, context, attrs);
    }

 @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        }
        ....

        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
        );
    }

上面还不是创建的方法,

  • mAppCompatViewInflater.java

public final View createView(View parent, final String name, @NonNull Context context,
        View view = null;
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break;
            case "EditText":
                view = new AppCompatEditText(context, attrs);
                break;
            case "Spinner":
                view = new AppCompatSpinner(context, attrs);
                break;
            case "ImageButton":
                view = new AppCompatImageButton(context, attrs);
                break;
            case "CheckBox":
                view = new AppCompatCheckBox(context, attrs);
                break;
            case "RadioButton":
                view = new AppCompatRadioButton(context, attrs);
                break;
            case "CheckedTextView":
                view = new AppCompatCheckedTextView(context, attrs);
                break;
            case "AutoCompleteTextView":
                view = new AppCompatAutoCompleteTextView(context, attrs);
                break;
            case "MultiAutoCompleteTextView":
                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                break;
            case "RatingBar":
                view = new AppCompatRatingBar(context, attrs);
                break;
            case "SeekBar":
                view = new AppCompatSeekBar(context, attrs);
                break;
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check it's android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }

从上面就可以看到 我们其实创建的一些View被系统替换成了AppcompatView,好吧,为了兼容

上面都是一些系统提供的View,然后又转入了 createViewFromTag的方法,之后的事情就是咱们再第一个模块说的事情了

那么我们如何拦截呢,其实很简单,通过反射,调用delegate的createView方法



 LayoutInflaterCompat.setFactory(inflater, new LayoutInflaterFactory() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

                //之前做什么事情

                View view=null;
                AppCompatDelegate delegate = getDelegate();

                try {

                    Method createViewFd = delegate.getClass().getMethod("createView", View.class, String.class, Context.class, AttributeSet.class);
                    createViewFd.setAccessible(true);
                    view = (View) createViewFd.invoke(delegate, new Object[]{parent, name, context, attrs});
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

                if(view==null){

                }

                //之后做什么事情
                return view;
            }
        });

我看张弘洋的视屏说,上面的过程只是针对特定的View,虽然我没有找到相关的代码判断在那里,这里还是做一下处理,其实就是复制两个方法,然后我们单开一个类


public class MyLayoutInflaterFactory implements LayoutInflaterFactory {
    AppCompatDelegate delegate;

    private static final Map<String, Constructor<? extends View>> sConstructorMap
            = new ArrayMap<>();

    private final Object[] mConstructorArgs = new Object[2];

    private static final Class<?>[] sConstructorSignature = new Class[]{
            Context.class, AttributeSet.class};

    private static final String[] sClassPrefixList = {
            "android.widget.",
            "android.view.",
            "android.webkit."
    };


    public MyLayoutInflaterFactory(AppCompatDelegate delegate) {
        this.delegate = delegate;
    }

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        //之前做什么事情
        View view = null;
        try {

            Method createViewFd = delegate.getClass().getMethod("createView", View.class, String.class, Context.class, AttributeSet.class);
            createViewFd.setAccessible(true);
            view = (View) createViewFd.invoke(delegate, new Object[]{parent, name, context, attrs});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        //我看张弘洋的视屏说,上面的过程只是针对特定的View,虽然我没有找到相关的代码判断在那里,这里还是做一下处理

        if (view == null) {
            createViewFromTag(context, name, attrs);
        }
        //之后做什么事情


        return view;
    }

    private View createViewFromTag(Context context, String name, AttributeSet attrs) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        try {
            mConstructorArgs[0] = context;
            mConstructorArgs[1] = attrs;

            if (-1 == name.indexOf('.')) {
                for (int i = 0; i < sClassPrefixList.length; i++) {
                    final View view = createView(context, name, sClassPrefixList[i]);
                    if (view != null) {
                        return view;
                    }
                }
                return null;
            } else {
                return createView(context, name, null);
            }
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        } finally {
            // Don't retain references on context.
            mConstructorArgs[0] = null;
            mConstructorArgs[1] = null;
        }
    }


    private View createView(Context context, String name, String prefix)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);

        try {
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                Class<? extends View> clazz = context.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                constructor = clazz.getConstructor(sConstructorSignature);
                sConstructorMap.put(name, constructor);
            }
            constructor.setAccessible(true);
            return constructor.newInstance(mConstructorArgs);
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        }
    }

在mainAct中设置上就好

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LayoutInflater inflater = LayoutInflater.from(this);

        LayoutInflaterCompat.setFactory(inflater,new MyLayoutInflaterFactory(getDelegate()));

        setContentView(R.layout.activity_main);
    }
### 可能的原因分析 `registerForActivityResult` 的回调方法未被调用可能由以下几个常见原因引起: 1. **未正确设置 `Intent` 或结果码** 如果目标 Activity 没有通过 `setResult()` 设置正确的结果码(如 `RESULT_OK`),或者根本没有调用 `finish()` 方法结束该 Activity,则不会触发回调[^4]。 2. **配置错误的请求键** 当使用 `registerForActivityResult` 注册回调时,通常会将其绑定到某个特定的操作或按钮点击事件。如果注册时使用的键值与实际发起操作时不匹配,可能导致回调无法正常执行。 3. **生命周期问题** 若当前组件(Activity 或 Fragment)在其生命周期中提前销毁(例如由于屏幕旋转或其他配置更改),可能会导致回调丢失。这是因为 `registerForActivityResult` 倚赖于宿主组件的生命周期管理机制。 4. **Fragment 层级冲突** 对于嵌套的 Fragment 场景,父 Fragment 可能拦截了子 Fragment 的回调逻辑,从而阻止了预期的结果传递。这种情况下需要特别注意如何处理嵌套结构中的回调关系[^5]。 --- ### 解决方案 #### 1. 确保目标 Activity 正确返回结果 在目标 Activity 中,在完成所需操作后务必调用以下代码以确保结果能够成功发送回去: ```java Intent resultData = new Intent(); resultData.putExtra("key", value); setResult(Activity.RESULT_OK, resultData); // 使用 RESULT_OK 表明操作成功 finish(); // 结束当前活动 ``` #### 2. 验证请求键的一致性 确认用于启动新 Activity 并等待其结果的键值在整个流程中保持一致。可以采用如下形式定义唯一标识符: ```java private final String REQUEST_KEY = "unique_request_key"; // 后续在此基础上构建 RegisterForActivityResult 实例... ``` #### 3. 调整生命周期行为 为了防止因配置变化而导致的潜在问题,可以在应用清单文件 (`AndroidManifest.xml`) 中为相关 Activity 添加属性声明保留状态: ```xml <activity android:name=".YourActivity" android:configChanges="orientation|screenSize"> </activity> ``` 这一步并非总是必要,但如果确实遇到频繁崩溃的情况可尝试此法作为临时补救措施之一。 #### 4. 处理复杂场景下的特殊需求 针对多层嵌套 Fragments 的情况,建议显式指定哪个具体实例负责接收最终反馈信息。一种通用做法是从顶层往下逐层转发直至找到合适的接受者为止;另一种更优雅的办法则是利用接口回调模式重构现有架构设计思路。 --- ### 示例代码片段展示正确实现方式 以下是基于最新推荐实践的一个完整例子演示如何有效运用 `registerForActivityResult` 功能模块: ```java public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private final ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { Intent data = result.getData(); if (data != null && data.hasExtra("returned_data")) { Log.d(TAG, "Received Data:" + data.getStringExtra("returned_data")); } } else { Log.w(TAG, "Operation Cancelled or Failed!"); } }); public void launchSecondActivity(View view){ Intent intent = new Intent(this, SecondActivity.class); mStartForResult.launch(intent); } } ``` 上述示例展示了如何创建一个 `ActivityResultLauncher` 实例并与之关联自定义处理器逻辑。每当用户交互引发对应动作时都会自动激活预设好的响应链条。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值