Kotlin Android Extensions
原文地址: https://proandroiddev.com/kotlin-android-extensions-the-definitive-guide-786d190b30e7
Kotlin Android Extensions是一個Kotlin外掛,將會生成一些額外的程式碼然你跟可以訪問佈局檔案中的views。他會構建一個本地的view快取,如果首次使用一個屬性,他將會執行findViewById,但是下次執行時,view將會被替換為快取中,訪問速度也會更快。
在專案中整合Kotlin Android Extensions
在專案的app/build.gradle檔案中新增如下:
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions'
直接在activity中使用佈局中定義的id
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/welcomeMessage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Hello World!"/> </FrameLayout>
TextView有一個welcomeMessage id。
然後在MainActivity中使用它:
override fun onCreate(savedInstanceState: Bundle?){ super.onCreate(savedInstance) setContentView(R.layout.activity_main) welcomeMessage.text = "Hello Kotlin!" }
IDE會自動匯入一個特殊的包
import kotlinx.android.synthetic.main.activity_main.*
實現原理
在kotlin -> Tools下面點選show kotlin bytecode可以檢視生成的位元組碼。
位元組碼有時候不容易理解,所以可以點選 Decompile 。
看一下activity生成的程式碼:
private HashMap _$_findViewCache; ... public View _$_findCachedViewById(int var1) { if(this._$_findViewCache == null) { this._$_findViewCache = new HashMap(); } View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1)); if(var2 == null) { var2 = this.findViewById(var1); this._$_findViewCache.put(Integer.valueOf(var1), var2); } return var2; } public void _$_clearFindViewByIdCache() { if(this._$_findViewCache != null) { this._$_findViewCache.clear(); } }
當我們需要一個view的時候,首先會去快取中查詢,如果沒有,將會通過findViewById查詢他並且新增到快取中。
他還額外的生成了一個清除快取的方法: clearFindViewByIdCache ,如果必須重建檢視,可以使用該方法,因為舊檢視將失效。
下面這行程式碼:
welcomeMessage.text = "Hello Kotlin!"
將會被替換為:
((TextView)this._$_findCachedViewById(id.welcomeMessage)).setText((CharSequence)"Hello Kotlin!");
所以這個屬性不是真實的,外掛不會為每個view生成一個屬性。只是會在編譯期間替換程式碼以訪問檢視快取,將其轉換為正確的型別並呼叫該方法。
在fragment中的使用方法
問題是fragment中的檢視可以重建,但是fragment例項將保持活動狀態。這意味著快取中的檢視將不再有效。
將上面的程式碼移動到fragment中看看會發生什麼:
class Fragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment, container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) welcomeMessage.text = "Hello Kotlin!" } }
接下來看一下生成的位元組碼,除了下面的一部分其餘的都與activity中的一樣:
// $FF: synthetic method public void onDestroyView() { super.onDestroyView(); this._$_clearFindViewByIdCache(); }
當view被銷燬的時候, onDestroyView 方法將會呼叫 clearFindViewByIdCache .
自定義view中使用方法
比如說,我們有如下佈局檔案:
<merge xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/itemImage" android:layout_width="match_parent" android:layout_height="200dp"/> <TextView android:id="@+id/itemTitle" android:layout_width="match_parent" android:layout_height="wrap_content"/> </merge>
建立一個簡單的自定義佈局,使用@JvmOverloads生成建構函式:

image.png
class CustomView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : LinearLayout(context, attrs, defStyleAttr) { init { LayoutInflater.from(context).inflate(R.layout.view_custom, this, true) itemTitle.text = "Hello Kotlin!" } }
接下來看一下生成的位元組碼:
((TextView)this._$_findCachedViewById(id.itemTitle)).setText((CharSequence)"Hello Kotlin!");
從另一個檢視訪問屬性
比如說adapter中:
val itemView = ... itemView.itemImage.setImageResource(R.mipmap.ic_launcher) itemView.itemTitle.text = "My Text"
外掛會幫助你匯入如下的包:
import kotlinx.android.synthetic.main.view_item.view.*
- 這種情況下檢視不會像activity或fragment那樣進行快取
- 你可以在任意檢視引用任意佈局的view
然後看一下生成的位元組碼:
((TextView)itemView.findViewById(id.itemTitle)).setText((CharSequence)"My Text");
並沒有任何快取的操作。如果佈局比較複雜的情況下可能會影響效能。
Kotlin Android Extensions in 1.1.4
在1.1.4中任何類中的view屬性都會被快取包括ViewHolder。還提供了一個新註解@Parcelize,還有一種方法可以自定義生成的快取。
需要在build.gradle檔案中啟用這些功能:
androidExtensions { experimental = true }
在ViewHolder中使用(或任意自定義類)
你可以使用簡單的方法為任意類構建一個快取,唯一需要做的就是讓你的類實現 LayoutContainer 介面,比如說上面的示例就可以改成如下這樣:
class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer { fun bind(title: String) { itemTitle.text = "Hello Kotlin!" } }
containerView是從LayoutContainer介面覆蓋的那個。
接下來看一下生成的位元組碼:
((TextView)this._$_findCachedViewById(id.itemTitle)).setText((CharSequence)"Hello Kotlin!");
可以發現已經出現了cache。
Kotlin Android Extension實現Parcelable
使用@Parcelize註解,可以讓任何類以一種簡單的方式實現Parcelable。
你只需要在類上添加註解,然後外掛就會將你做其餘的工作:
@Parcelize class Model(val title: String, val amount: Int): Parcelable
接著你就可以在任意的Intent中使用如下程式碼:
val intent = Intent(this, DetailActivity::class.java) intent.putExtra(DetailActivity.EXTRA, model)
從intent中獲取資料:
val model: Model = intent.getParcelableExtra(EXTRA)
定製快取
@ContainerOptions註解可以允許你自定義快取的構建方式,甚至可以阻止類建立快取。
預設情況下會使用Hashmap,就像我們上面看到的那樣,可以改為使用Android框架中的SparseArray,在某些情況下可能更有效,如果你不需要為類新增快取,也可以使用該註解。
@ContainerOptions(CacheImplementation.SPARSE_ARRAY) class MainActivity : AppCompatActivity() { ... } public enum class CacheImplementation { SPARSE_ARRAY, HASH_MAP, NO_CACHE; ... }