LayoutInflaterはどのようにレイアウトをロードしますか?

16881 ワード

Androidの開発経験がある方には、LayoutInflaterを使ったことがあると思います.inflater()はレイアウトファイルをロードしますが、必ずしもその原理を深く研究しているわけではありません.例えば、
  • 1.LayoutInflaterはなぜlayoutファイルをロードできるのですか?
  • 2.layoutファイルをロードした後、どのようにして私たちが使用するViewになりましたか?
  • 3.ビューを定義するときにレイアウトで使用する必要がある場合は、AttributeSetパラメータ付きの構造方法を実装する必要がありますが、なぜでしょうか.

  • この文章で提起された以上、この3つの問題はLayoutInflaterとは関係がないことを示しています.私たちの分析の過程で、これらの問題を一つ一つ解答します.一歩一歩、まずlayoutからViewをロードする必要がある場合、このメソッドが呼び出されます.
    LayoutInflater.from(context).inflater(R.layout.main_activity,null);

    1.LayoutInflaterの作成方法
    何が言いたいんだ?もしあなたがLayoutInflaterを開けたらJAvaでは、LayoutInflaterは抽象クラスであり、抽象クラスは直接インスタンス化できないことがわかります.つまり、私たちが作成したオブジェクトはLayoutInflaterの実装クラスに違いありません.私たちはLayoutInflaterに入ります.fromメソッドでは
    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から取得して、豚肉を食べたことがなくてまだ豚が走っているのを見たことがありませんか、contextの対象といえば十中八九ContextImplの対象で、そこで私达は直接ContextImplに行きます.JAvaでgetSystemServiceメソッドを見つけます
    @Override
        public Object getSystemService(String name) {
            return SystemServiceRegistry.getSystemService(this, name);
        }
    

    ええ...またSystemServiceRegistryに行きます.JAvaファイル内
        /**
         * Gets a system service from a given context.
         */
        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で入手したものですが、コードを少し見ると、このHashMapはstaticモジュールで付与されているもので、ここには多くのシステムサービスが登録されています.Activity Service、AlarmServiceなどはこのHashMapにあります.LayoutInflater.からfromメソッドではContextであることがわかります.LAYOUT_INFLATER_SERVICE対応サービス
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                    new CachedServiceFetcher() {
                @Override
                public LayoutInflater createService(ContextImpl ctx) {
                    return new PhoneLayoutInflater(ctx.getOuterContext());
                }});

    さあ、主役がついに登場しました.PhoneLayoutInflater、私たちが手に入れたLayoutInflaterはこのクラスの対象です.では、この部分の成果は私たちがPhoneLayoutInflaterを見つけたことです.具体的に何の役割があるのか、後で話します.
    2.inflaterメソッド解析
    これこそ最も重要な方法です.この方法が私たちのlayoutをViewオブジェクトに変換したからです.この方法はLayoutInflater抽象クラスで直接定義されています
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
            return inflate(resource, root, root != null);
        }

    入力されたパラメータはlayoutのidであり、ParentViewを指定するかどうかであり、実際の実装は下を見なければならない.
    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();
            }
        }

    まずcontextからResourcesオブジェクトを取得し、その後、res.getLayout(resource)メソッドでxmlファイル解析器XmlResourceParserを取得します.(Androidでのxmlファイル解析器についてはここでは詳しく説明しませんが、あまり遠くまで話さないように、知らない学生はネットで関連資料を探して読むことができます)、これは実は私たちが定義したlayoutのxmlファイルをロードしたのです.そして、別のinflateメソッドを呼び出し続けました
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
            synchronized (mConstructorArgs) {
                final Context inflaterContext = mContext;
                //  ,View       attrs    !!!
                final AttributeSet attrs = Xml.asAttributeSet(parser);
    
                //       ,         ,            
                mConstructorArgs[0] = inflaterContext;
                View result = root;
    
                try {
                    //      ,     START_TAG   while  ,
                    //    START_TAG,  END_TAG
                    int type;
                    while ((type = parser.next()) != XmlPullParser.START_TAG &&
                            type != XmlPullParser.END_DOCUMENT) {
                        // Empty
                    }
    
                    if (type != XmlPullParser.START_TAG) {
                        throw new InflateException(parser.getPositionDescription()
                                + ": No start tag found!");
                    }
                    //        
                    final String name = parser.getName();
    
                    //      merge  
                    if (TAG_MERGE.equals(name)) {
                        if (root == null || !attachToRoot) {
                            throw new InflateException(" can be used only with a valid "
                                    + "ViewGroup root and attachToRoot=true");
                        }
                        //  
                        rInflate(parser, root, inflaterContext, attrs, false);
                    } else {
                        //        PhoneLayoutInflater    ,         View
                        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
                        ViewGroup.LayoutParams params = null;
                        //     parentView(root),   layoutParams,
                        //       temp   root 
                        if (root != null) {
                            params = root.generateLayoutParams(attrs);
                            if (!attachToRoot) {
                                temp.setLayoutParams(params);
                            }
                        }
    
                        //         ,             
                        rInflateChildren(parser, temp, attrs, true);
    
                        if (root != null && attachToRoot) {
                            root.addView(temp, params);
                        }
    
                        if (root == null || !attachToRoot) {
                            result = temp;
                        }
                    }
    
                }  catch (Exception e) {
    
                } finally {
                    // Don't retain static reference on context.
                    mConstructorArgs[0] = lastContext;
                    mConstructorArgs[1] = null;
                }
    
                return result;
            }
        }

    これは少し長いですが、論理に関係のないコードを少し削除し、コメントを追加しました.辛抱強く見れば読めるはずです.ここでは主に2つの部分について述べるが,まずrInflateChildrenという方法であり,実際には1層1層ですべてのノードを取り出し,createViewFromTag法によりViewオブジェクトに変換する.だからポイントは、どのようにビューオブジェクトに変換するかです.
    3.createViewFromTag
    私たちはコードを次々とフォローして、最後にここに着きます.
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
                ......
                ......
            try {
                ......
    
                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;
                    }
                }
    
                return view;
            } catch (InflateException e) {
                throw e;
                ......
            }
        }

    理解を容易にするために、関係のないコードを削除すると、xmlノードからViewに変換するために呼び出されたcreateViewメソッドが表示されます.nameに'.'が含まれていない場合.onCreateViewメソッドを呼び出します.そうでなければcreateViewメソッドを直接呼び出します.上記のPhoneLayoutInflaterではonCreateViewメソッドが複写されており、書き換えるかどうかにかかわらず、このメソッドは最後にcreateViewを呼び出します.唯一の違いは、システムのViewの完全なクラス名がonCreateViewによって提供され、カスタムコントロールがレイアウトファイルで本来使用されている完全なクラス名である場合です.
    4.createViewメソッド
    public final View createView(String name, String prefix, AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
            //1.       ,        
    
            Constructor extends View> constructor = sConstructorMap.get(name);
            Class extends View> clazz = null;
    
            try {
                if (constructor == null) {
    
                    clazz = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);
    
                    if (mFilter != null && clazz != null) {
                        boolean allowed = mFilter.onLoadClass(clazz);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    }
                    constructor = clazz.getConstructor(mConstructorSignature);
                    constructor.setAccessible(true);
                    sConstructorMap.put(name, constructor);
                } else {
    
                    if (mFilter != null) {
                        Boolean allowedState = mFilterMap.get(name);
                        if (allowedState == null) {          
                            clazz = mContext.getClassLoader().loadClass(
                                    prefix != null ? (prefix + name) : name).asSubclass(View.class);        
                            boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                            mFilterMap.put(name, allowed);
                            if (!allowed) {
                                failNotAllowed(name, prefix, attrs);
                            }
                        } else if (allowedState.equals(Boolean.FALSE)) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    }
                }
    
                //2.        ,  View  
                Object[] args = mConstructorArgs;
                args[1] = attrs;
    
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) {
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                return view;
    
            } catch (NoSuchMethodException e) {
              ......
            } 
        }

    このコードは主に2つのことをしています.まず、ClassNameに基づいてクラスをメモリにロードし、指定したコンストラクタconstructorを取得します.コンストラクタは、入力パラメータのタイプと数によって指定されます.ここではmConstructorSignatureが入力されます.
    static final Class>[] mConstructorSignature = new Class[] {
                Context.class, AttributeSet.class};

    つまり入力パラメータがContextとAttributeSetなのか、はっと悟ったのか!!!これは、私たちがViewをカスタマイズするときに、layoutで私たちのViewを使用するには、View(Context context,AttributeSet attrs)の構造方法を書き直さなければならない理由です.
    第2に、取得したコンストラクタconstructorを使用して、Viewインスタンスを作成します.
    5.質問に答える
    上記の3つの問題を覚えていますか?では、次のように説明します.
    1.LayoutInflaterはなぜlayoutファイルをロードできるのですか?
    LayoutInflaterは実はxml解析器でxmlファイルをロードし、layoutファイルのフォーマットはxmlなので読み取ることができます.
    2.layoutファイルをロードした後、どのようにして私たちが使用するViewになりますか?
    LayoutInflaterをxmlファイルにロードした後、各ラベルの名前を反射によって取り出し、対応するクラス名を生成し、反射によってクラスのコンストラクタ関数を取得します.パラメータはContextとAttributeSetです.次に、コンストラクタを使用してViewオブジェクトを作成します.
    3.Viewを定義するとき、レイアウトで使用する必要がある場合は、AttributeSetパラメータ付きの構築方法を実装する必要がありますが、なぜでしょうか.
    LayoutInflaterはxmlファイルを解析するときに、xmlファイルで設定された属性値を含むAttributeSetオブジェクトにxmlの内容を変換します.これらのプロパティ値をコンストラクション関数で取り出し、インスタンスのプロパティに割り当てる必要があります.