blog

LayoutInflaterの原理

Android開発者にとっては、馴染みのある機能でもあり、馴染みのない機能でもあります。すべてのレイアウトXMLファイルは、対応するViewインスタンスをインスタンス化するために彼を介しているので、お...

Feb 23, 2020 · 10 min. read
シェア
はじめに

LayoutInflaterは、Androidの開発のためにする必要があります。すべてのレイアウトXMLファイルは、対応するViewインスタンスをインスタンス化するために彼を介しているため、おなじみの、基本的に多くの場合、彼に対処するために、開発の過程で直接使用するために引き継がれているため、なじみのない、彼はどこから来たのかのために、レイアウトファイルをインスタンス化するだけでなく、非常によく理解していないことを行うために使用することができます。

そこでこの記事では、LayoutInflaterの原理に焦点を当てます。

LayoutInflater プロセス分析

LayoutInflaterの入手
@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {
		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;
		}
}

上記は、LayoutInflater インスタンスを取得するための LayoutInflater クラスの静的メソッドです。context.getSystemServiceLayoutInflaterは抽象クラスであり、システムが提供するサービスを取得するために、LayoutInflaterメソッドを直接インスタンス化することはできません。

SystemServiceRegistry LayoutInflater サービスの登録

登録メソッドは、SystemServiceRegistryクラスのstaticステートメントブロックにあり、PhoneLayoutInflaterのインスタンスを正確に返します。

static {
 	....
 // LayoutInflaterサービスを登録し、ここではPhoneLayoutInflaterのインスタンスを作成する。
 	registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
 //PhoneLayoutInflater最初にキャッシュされたインスタンスを作成すると、次回はそのインスタンスから
 new CachedServiceFetcher<LayoutInflater>() {
 @Override
 public LayoutInflater createService(ContextImpl ctx) {
 return new PhoneLayoutInflater(ctx.getOuterContext());
 }});
 	....
}
View

ビューをロードするためのいくつかの方法:

//  
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) {
 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();
 }
}
//  
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
 synchronized (mConstructorArgs) {
 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
 final Context inflaterContext = mContext;
 	//1.XMLファイルからプロパティセットを取得する
 final AttributeSet attrs = Xml.asAttributeSet(parser);
 Context lastContext = (Context) mConstructorArgs[0];
 mConstructorArgs[0] = inflaterContext;
 View result = root;
 try {
 // Look for the root node.
 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();
 if (TAG_MERGE.equals(name)) {
 if (root == null || !attachToRoot) {
 throw new InflateException("<merge /> can be used only with a valid "
 + "ViewGroup root and attachToRoot=true");
 }
 rInflate(parser, root, inflaterContext, attrs, false);
 } else {
 // Temp is the root view that was found in the xml
 	//2.プロパティセットを解析し、返されたxmlにルートView tempを作成する。
 final View temp = createViewFromTag(root, name, inflaterContext, attrs);
 ViewGroup.LayoutParams params = null;
 if (root != null) {
 // Create layout params that match root, if supplied
 //3.テンポラリ・ビューのパラメータを作成する
 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.
 	//4.すべての子Viewを持つテンポラリViewを作成し、それらの階層関係を確立する。
 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(parser.getPositionDescription()
 + ": " + 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;
 }
}
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
 boolean finishInflate) throws XmlPullParserException, IOException {
 rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
 AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
 final int depth = parser.getDepth();
 int type;
 boolean pendingRequestFocus = false;
	
 	// ziViewを繰り返し処理する
 while (((type = parser.next()) != XmlPullParser.END_TAG ||
 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
 if (type != XmlPullParser.START_TAG) {
 continue;
 }
 final String name = parser.getName();
 if (TAG_REQUEST_FOCUS.equals(name)) {
 pendingRequestFocus = true;
 consumeChildElements(parser);
 } else if (TAG_TAG.equals(name)) {
 parseViewTag(parser, parent, attrs);
 } else if (TAG_INCLUDE.equals(name)) {
 if (parser.getDepth() == 0) {
 throw new InflateException("<include /> cannot be the root element");
 }
 parseInclude(parser, context, parent, attrs);
 } else if (TAG_MERGE.equals(name)) {
 throw new InflateException("<merge /> must be the root element");
 } else {
 	//5.createViewFromTagを繰り返し、子Viewを作成する。
 final View view = createViewFromTag(parent, name, context, attrs);
 final ViewGroup viewGroup = (ViewGroup) parent;
 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
 rInflateChildren(parser, view, attrs, true);
 viewGroup.addView(view, params);
 }
 }
		.....
}

最初の2つの方法は、最終的に3つ目の方法にチューニングされるので、5つの重要なステップに焦点を当てた3つ目の方法を主に見てください:

  1. XMLから属性の初期セットを解析し、ビューを作成します。
  2. createViewFromTag メソッドを使用して、属性のコレクションからテンポラリ・ビューを作成します。
  3. 初期化がattachToRoot変数を渡してレイアウトパラメータをtemp Viewに関連付けるかどうかに応じて、temp Viewのレイアウトパラメータを作成します。
  4. rInflateChildrenを実行して子Viewを解析し、それらの階層関係を確立します。
  5. すべての子Viewは、2番目のステップと同様に、createViewFromTagで作成する必要があります。
createViewFromTag機能概要
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);
 }
 	//1.インターセプターを作成してビューを作成しない場合、以下のデフォルトロジックを入力することができる。
 if (view == null) {
 final Object lastContext = mConstructorArgs[0];
 mConstructorArgs[0] = context;
 try {
 	//名前がビューのフルネームかどうかを判断する(例:com.view.TextView
 if (-1 == name.indexOf('.')) {
 	//name名前はフルネームに継ぎ足され、最終的な呼び出しはcreateViewである。
 view = onCreateView(parent, name, attrs);
 } else {
 	//2.Reflectionでビューを作成する
 view = createView(name, null, attrs);
 }
 } finally {
 mConstructorArgs[0] = lastContext;
 }
 }
 return view;
 } catch (InflateException e) {
 	....
 } catch (ClassNotFoundException e) {
 	....
 } catch (Exception e) {
 	....
 }
}

Viewの作成では、mFactory2、mFactory、mPrivateFactoryの3つのLayoutInflaterメンバ変数がView作成のフックになっています。つまり、Apiはアイデアに従ってViewを作成する可能性を提供し、コードを通じてLayoutInflaterインスタンスに設定するだけでよいので、このデザインパターンのコードはあまり埋め込まれません。

Viewの作成をインターセプトしなければ、デフォルトのプロセスを経て、最後にcreateViewメソッドを呼び出してViewを作成します。

Factory

factory
public interface Factory {
 public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
 public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

setFactory2はSDK>11で追加されたもので、11以上をベースにしている場合はsetFactory2を、そうでない場合はsetFactoryを使いますが、基本的にはどちらの関数も同じです。もちろん、互換性の問題を考慮したくない場合は、直接LayoutInflaterCompat.setFactory()を使用することができます。

ファクトリーの設定
/**
* Attach a custom Factory interface for creating views while using
* this LayoutInflater. This must not be null, and can only be set once;
* after setting, you can not change the factory. This is
* called on each element name as the xml is parsed. If the factory returns
* a View, that is added to the hierarchy. If it returns null, the next
* factory default {@link #onCreateView} method is called.
*
* <p>If you have an existing
* LayoutInflater and want to add your own factory to it, use
* {@link #cloneInContext} to clone the existing instance and then you
* can use this function (once) on the returned new instance. This will
* merge your own factory with whatever factory the original instance is
* using.
*/
public void setFactory(Factory factory) {
 if (mFactorySet) {
 throw new IllegalStateException("A factory has already been set on this LayoutInflater");
 }
 if (factory == null) {
 throw new NullPointerException("Given factory can not be null");
 }
 mFactorySet = true;
 if (mFactory == null) {
 mFactory = factory;
 } else {
 mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
 }
}
/**
 * Like {@link #setFactory}, but allows you to set a {@link Factory2}
 * interface.
 */
public void setFactory2(Factory2 factory) {
 if (mFactorySet) {
 throw new IllegalStateException("A factory has already been set on this LayoutInflater");
 }
 if (factory == null) {
 throw new NullPointerException("Given factory can not be null");
 }
 mFactorySet = true;
 if (mFactory == null) {
 mFactory = mFactory2 = factory;
 } else {
 	// ファクトリーがある場合は、FactoryMergerを使用してファクトリーをパッケージ化する。
 mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
 }
}
/**
 * @hide for use by framework
 */
public void setPrivateFactory(Factory2 factory) {
 if (mPrivateFactory == null) {
 mPrivateFactory = factory;
 } else {
 mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
 }
}
//PhoneLayoutInflater のオーバーロードされたメソッド
public LayoutInflater cloneInContext(Context newContext) {
 return new PhoneLayoutInflater(this, newContext);
}
//Chain of Responsibility(責任の連鎖)デザインパターンを使用して、2つのファクトリーを一緒にパッケージ化する:優先順位は、新しくセットされたファクトリーのメソッドを呼び出すことであり、リターンがNULLの場合は、ファクトリーのメソッドを使用する。
private static class FactoryMerger implements Factory2 {
 private final Factory mF1, mF2;
 private final Factory2 mF12, mF22;
 FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
 mF1 = f1;
 mF2 = f2;
 mF12 = f12;
 mF22 = f22;
 }
 public View onCreateView(String name, Context context, AttributeSet attrs) {
 View v = mF1.onCreateView(name, context, attrs);
 if (v != null) return v;
 return mF2.onCreateView(name, context, attrs);
 }
 public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
 View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
 : mF1.onCreateView(name, context, attrs);
 if (v != null) return v;
 return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
 : mF2.onCreateView(name, context, attrs);
 }
}

上記3つの工場設立方法を以下にまとめました:

  • setPrivateFactoryはフレームワーク層で使われるので、使わないでください。
  • クローニングInContextによってインスタンスをコピーしてファクトリを設定するには、彼はFactoryMergerは、ファクトリを一緒にパッケージ化される作成され、ファクトリのメソッドの新しいセットを呼び出すことを優先するときにonCreateViewを呼び出します。
  • 以上の3点をまとめると、LayoutInflaterにグローバルファクトリを設定したい場合は、Applicationに保証を設定する必要があります。
Factory どんな機能?

LayoutInflater Factoryのこの機能でできることはたくさんあります:

  • ビューの構築効率の改善
    • カスタムビューを使用する場合、xmlで完全なクラス名を使用する必要がありますが、システムは実際にはリフレクションの完全なクラス名に基づいてビルドします。独自のビューを作成することで、システムのリフレクション呼び出しを回避し、効率を向上させることができます。
  • デフォルトのビュー実装の置き換え、プロパティの変更または追加
    • システムコントロールの置き換え
    • ワンクリックスキニングソリューション
まとめ

LayoutInflaterファクトリーは、上記以上のことができる非常に強力な機能です。例えば、「ワンクリックスキニング」のほとんどはこの機能を使って実現されています。

Read next

UIPageViewControllerとUITableViewCellの左スライド削除ジェスチャ間の競合を解決する。

あるプロジェクトで、2 つのテーブルビューを水平にネストする必要があり、右側のテーブルビューで左スワイプによるセルの削除をサポートする必要がありました。 UI をレイアウトした後、セルを削除するための左スワイプはトリガーされず、クイックスワイプでのみ編集モードに入ることができることがわかります。

Feb 23, 2020 · 2 min read