Android 关于LayoutInflater类onCreateView方法prefix传入android.view.依然能初始化成功原生控件的问题

Posted 瞌睡先生想睡觉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 关于LayoutInflater类onCreateView方法prefix传入android.view.依然能初始化成功原生控件的问题相关的知识,希望对你有一定的参考价值。

问题可能有点长,主要问题就是,在LayoutInflater中通过inflate方法来加载布局,然后在createViewFromTag方法中将解析的xml文件中控件的name通过 . 字符来区分系统控件还是自定义控件

LayoutInflater.class

            if (view == null) 
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try 
                    if (-1 == name.indexOf('.')) 
                        view = onCreateView(parent, name, attrs);
                     else 
                        view = createView(name, null, attrs);
                    
                 finally 
                    mConstructorArgs[0] = lastContext;
                
            
			

然后在onCreateView中传入的前缀是android.view.,但是实际上TextView,Button这种都是在android.widget.包下面的,那么为什么它还能加载成功呢?

LayoutInflater.class

    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException 
        return createView(name, "android.view.", attrs);
    

这个结论还是比较简单的,由于我比较菜所以还是让我觉得有些头疼.首先我们从初始化开始看起

LayoutInflater.from(this);

这个方法传入的对象是Context对象,然后我们来看这个方法内部来看

LayoutInflater.class

    public static LayoutInflater from(Context context) 
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) 
            throw new AssertionError("LayoutInflater not found.");
        
        return LayoutInflater;
    

我们来看下 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);这个方法的内部

Context.class

    public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);

可以看到在Context类中这个方法是个抽象方法, 通常情况下我们能得到的Context对象就是Activity、Application、Service这几种,现在我们就以最常用的Activity来说一下,Activity对象是继承自Context我们再来看下最终从Context到Activity中是谁实现了这个方法.

Activity.class

    @Override
    public Object getSystemService(@ServiceName @NonNull String name) 
        if (getBaseContext() == null) 
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        

        if (WINDOW_SERVICE.equals(name)) 
            return mWindowManager;
         else if (SEARCH_SERVICE.equals(name)) 
            ensureSearchManager();
            return mSearchManager;
        
        return super.getSystemService(name);
    

可以看到虽然有在Activity中虽然有实现但是并不是我们需要的哪一种,它初始化的是Windows相关实现,然后我们根据super.getSystemService(name)再向上找

ContextThemeWrapper.class

	@Override
    public Object getSystemService(String name) 
        if (LAYOUT_INFLATER_SERVICE.equals(name)) 
            if (mInflater == null) 
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            
            return mInflater;
        
        return getBaseContext().getSystemService(name);
    

在ContextThemeWrapper中看到了我们要的实现,但是在这里面调用的还是.from方法,传入的是getBaseContext(),也就是说我们还要去找这个getBaseContext()是怎么来的

ContextWrapper.class

    Context mBase;

    public ContextWrapper(Context base) 
        mBase = base;
    

    /**
     * @return the base context as set by the constructor or setBaseContext
     */
    public Context getBaseContext() 
        return mBase;
    

最终看到了ContextWrapper.class中,初始化ContextWrapper会传入一个Context,稍稍看一下ContextWrapper这个类,这个类就很有意思了,虽然它继承自Context并且实现了全部的Context方法
但是其所有的实现都是调用mBase去实现的,mBase可以在初始化的时候传入,也可以调用attachBaseContext方法传入,ContextThemeWrapper继承自ContextWrapper 其中实现了一个初始化的方法,
使得子类在初始化的时候不用传入Context对象

ContextThemeWrapper.class

    public ContextThemeWrapper() 
        super(null);
    

Activty继承自ContextThemeWrapper,在Activity中有一个方法attach,这个方法传入了一个Context对象,并且调用attachBaseContext方法将Context传入,这也就是ContextWrapper中mBase的来源

Activity.class

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) 
        attachBaseContext(context);

		...
    

那么这个方法又是在什么时候调用的呢?这就要从Activity的初始化开始说起了,但是我们挑重要的说,了解过Handler机制的人都知道Android依靠Handler机制来
处理程序中的各种事件,包括Activity的初始化,具体的代码就在ActivityThread类中有一个H类继承自Handler,里面handleMessage中写了对LAUNCH_ACTIVITY时间的处理

ActivityThread.class

                case LAUNCH_ACTIVITY: 
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                 break;
                

其中主要调用的是handleLaunchActivity方法,handleLaunchActivity通过调用performLaunchActivity来获取Activity对象,performLaunchActivity中Activity调用了attach方法来设置Context,这个就是我们在上面说到
设置ContextWrapper中mBase的方法,ContextImpl对象通过createBaseContextForActivity方法获取,内部又是调用ContextImpl.createActivityContext方法来初始化ContextImpl,ContextImpl这个类继承自Context,Context的
功能都是通这个类来具体实现的

ActivityThread.class

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) 
	
	    ...

        Activity a = performLaunchActivity(r, customIntent);
		
		...
    


    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) 
	
	    ...

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
		
		...

        try 
		
		    ...

            if (activity != null) 
                ...
				
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
						
						
				...
            
            r.paused = true;

            mActivities.put(r.token, r);

         catch (SuperNotCalledException e) 
            throw e;

         catch (Exception e) 
            if (!mInstrumentation.onException(activity, e)) 
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            
        

        return activity;
    
	
	
    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) 
	
	    ...

        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
		
		...
		
        return appContext;
    

在来看我们所关心的getSystemService代码:

ContextImpl.class

    @Override
    public Object getSystemService(String name) 
        return SystemServiceRegistry.getSystemService(this, name);
    

在往里面看:

SystemServiceRegistry.class

    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
			
    static 
    	...

        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() 
            @Override
            public LayoutInflater createService(ContextImpl ctx) 
                return new PhoneLayoutInflater(ctx.getOuterContext());
            );

        ...
    

			
    public static Object getSystemService(ContextImpl ctx, String name) 
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    

可以发现我们要的值是存储在SYSTEM_SERVICE_FETCHERS这个HashMap中的,Map集合又是在静态代码块中初始化各种值的,也就是说虽然我们调用 LayoutInflater.from(context)方法得到的是LayoutInflater但是实际上
系统是返回给了我们一个PhoneLayoutInflater,这个类才是我们问题最终的关键

代码不多就直接全部贴出来了:

public class PhoneLayoutInflater extends LayoutInflater 
    private static final String[] sClassPrefixList = 
        "android.widget.",
        "android.webkit.",
        "android.app."
    ;

    /**
     * Instead of instantiating directly, you should retrieve an instance
     * through @link Context#getSystemService
     *
     * @param context The Context in which in which to find resources and other
     *                application-specific things.
     *
     * @see Context#getSystemService
     */
    public PhoneLayoutInflater(Context context) 
        super(context);
    

    protected PhoneLayoutInflater(LayoutInflater original, Context newContext) 
        super(original, newContext);
    

    /** Override onCreateView to instantiate names that correspond to the
        widgets known to the Widget factory. If we don't find a match,
        call through to our super class.
    */
    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException 
        for (String prefix : sClassPrefixList) 
            try 
                View view = createView(name, prefix, attrs);
                if (view != null) 
                    return view;
                
             catch (ClassNotFoundException e) 
                // In this case we want to let the base class take a crack
                // at it.
            
        

        return super.onCreateView(name, attrs);
    

    public LayoutInflater cloneInContext(Context newContext) 
        return new PhoneLayoutInflater(this, newContext);
    


可以看到PhoneLayoutInflater中有一个数组sClassPrefixList,在这个数组中能看到我们最关心的android.widget.这个包名,然后在重写的onCreateView中,会首先遍历这个数组,调用createView方法去创建View
如果三个包下都没有我们要的控件才会调用父类也就是LayoutInflater类的onCreateView方法去创建View.这样就解释了我们的疑惑,为什么传入android.view.也能初始化成功android.widget包下面的控件

以上是关于Android 关于LayoutInflater类onCreateView方法prefix传入android.view.依然能初始化成功原生控件的问题的主要内容,如果未能解决你的问题,请参考以下文章

[Android FrameWork 6.0源码学习] LayoutInflater 类分析

Android LayoutInflater

Android 中的 LayoutInflater 有啥作用?

Android - LayoutInflater和inflate方法的用法

Android LayoutInflater详解

Android LayoutInflater详解