Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android Viewビルドパフォーマンス向上について

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Android Viewビルドパフォーマンス向上について

Android Viewビルドパフォーマンス向上について

Avatar for interkenny

interkenny

August 21, 2020
Tweet

Other Decks in Programming

Transcript

  1. レイアウトパフォーマンス  レイアウト階層の最適化  Hierarchy Viewer でレイアウト検査を行う  低コストのレイアウトを採用(ConstraintLayout) 

    レイアウト再利用  ネストされた冗長なレイアウトを削除(include/mergeの利用)  オンデマンドロード  ViewStub の利用
  2. 深く見てみる AppCompatActivity.java public class AppCompatActivity { ... @Override public void

    setContentView(View view) { getDelegate().setContentView(view); } ... } public abstract class LayoutInflater { public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); ... final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } } class AppCompatDelegateImpl { @Override public void setContentView(int resId) { ... LayoutInflater.from(mContext).inflate(resId, contentParent); ... } } Layoutファイル解析用パーサー登場 getLayout()にてResourcesImpl.loadXmlResourceParser()でXML parserを取得
  3. ResourcesImpl.java public class ResourcesImpl { ... XmlResourceParser loadXmlResourceParser(@NonNull String file,

    @AnyRes int id, int assetCookie,@NonNull String type) throws NotFoundException { if (id != 0) { try { synchronized (mCachedXmlBlocks) { ... // AssetManager final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file); if (block != null) { final int pos = (mLastCachedXmlBlockIndex + 1) % num; mLastCachedXmlBlockIndex = pos; final XmlBlock oldBlock = cachedXmlBlocks[pos]; if (oldBlock != null) { oldBlock.close(); } cachedXmlBlockCookies[pos] = assetCookie; cachedXmlBlockFiles[pos] = file; cachedXmlBlocks[pos] = block; return block.newParser(); } } } catch (Exception e) { ... } } ... } } public final class AssetManager implements AutoCloseable { ... @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException { Preconditions.checkNotNull(fileName, ”fileName“); synchronized (this) { ensureOpenLocked(); final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); if (xmlBlock == 0) { throw new FileNotFoundException(“Asset XML file: ” + fileName); } final XmlBlock block = new XmlBlock(this, xmlBlock); incRefsLocked(block.hashCode()); return block; } } } NativeメソッドでXMLファイルをメモ リーに取り込み ここでIO処理発生
  4. LayoutInflater.java public abstract class LayoutInflater { public View inflate(@LayoutRes int

    resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); ... final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } } public abstract class LayoutInflater { public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { ... try { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); 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); } } ... // 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); } ... } catch (XmlPullParserException e) { ... } finally { ... } return result; } } public abstract class LayoutInflater { View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) { ... View view = tryCreateView(parent, name, context, attrs); ... } public final View tryCreateView() { ... view = mFactory2.onCreateView(parent, name, context, attrs) ... return view } } AppCompatDelegateImpl. onCreateView() にて実現する
  5. 最後に AppCompatDelegateImpl.java class AppCompatDelegateImpl{ @Override public View createView(View parent, final

    String name, @NonNull Context context, @NonNull AttributeSet attrs) { ... return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */ true, /* Read read app:theme as a fallback at all times for legacy reasons */ VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */ ); } } public class AppCompatViewInflater { final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) { final Context originalContext = context; ... View view = null; switch (name) { case "TextView": view = createTextView(context, attrs); verifyNotNull(view, name); break; } ... return view; } protected AppCompatTextView createTextView(Context context, AttributeSet attrs) { return new AppCompatTextView(context, attrs); } ... } ここでViewのインスタンスを作成する
  6. 確認  View作成時間を計る class MainActivity : AppCompatActivity() { private var

    sum: Double = 0.0 @OptIn(ExperimentalTime::class) override fun onCreate(savedInstanceState: Bundle?) { LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), object : LayoutInflater.Factory2 { override fun onCreateView(parent: View?,name: String?,context: Context?,attrs: AttributeSet? ): View? { val (view, duration) = measureTimedValue { delegate.createView(parent, name, requireNotNull(context), requireNotNull(attrs)) } sum += duration.inMilliseconds Log.v("onCreateView", "view=${view?.let { it::class.simpleName }} duration=$duration sum=$sum") return view } // Factoryでの利用、nullでいい override fun onCreateView(name: String,context: Context,attrs: AttributeSet): View? { return null } } ) super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } LayoutInflaterCompat. setFactory2()により、Viewのビルドプロセス時間を計ることができる super.onCreate(savedInstanceState)の前に上記のインタフェースを設定 Viewビルドプロセス実施
  7. ConstraintLayout { layout_width = match_parent layout_height = match_parent } inline

    fun ViewGroup.TextView(init: TextView.() -> Unit) = TextView(context).apply(init).also { addView(it) } ConstraintLayout { layout_width = match_parent layout_height = match_parent TextView { layout_width = wrap_content layout_height = wrap_content } } inline var View.background_color: String get() { return "" } set(value) { setBackgroundColor(Color.parseColor(value)) } ConstraintLayout { layout_width = match_parent layout_height = match_parent background_color = "#ffff00" }
  8. var RecyclerView.onItemClick: (View, Int) -> Unit get() { return {

    _, _ -> } } set(value) { setOnItemClickListener(value) } fun RecyclerView.setOnItemClickListener(listener: (View, Int) -> Unit) { addOnItemTouchListener(object : RecyclerView.OnItemTouchListener { val gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener { override fun onShowPress(e: MotionEvent?) { } override fun onSingleTapUp(e: MotionEvent?): Boolean { e?.let { findChildViewUnder(it.x, it.y)?.let { child -> listener(child, getChildAdapterPosition(child)) } } return false } ・・・ }) override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) { } override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { gestureDetector.onTouchEvent(e) return false } ・・・ }) } RecyclerViewにItemクリックリスナーを設定する RecyclerView { layout_id = "rvTest" layout_width = match_parent layout_height = 300 onItemClick = onListItemClick } val onListItemClick = { v: View, i: Int -> ・・・ }