Activity/Fragmnet 傳參的新方式
在Android中兩個Activity、Activity與Fragment之間傳參是件很痛苦的事情,因為要定義很多的key。步驟也非常的繁瑣,要存要取。
現在這個問題有了新的解決方案,就是利用Kotlin的屬性代理。
比如有兩個Activity,一個是MainActivity,一個是TestActivity,從MainActivity到TestActivity.
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<Button>(R.id.activityBtn).setOnClickListener { //跳轉到TestActivity } } }
TestActivity程式碼
class TestActivity : AppCompatActivity() { var name:String="" var num:Int=0 private lateinit var textView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_test) textView = findViewById(R.id.textView) textView.text = "$name - $num" } }
如果用之前常規的寫法是這樣的
val intent = Intent(this@MainActivity, TestActivity::class.java); intent.putExtra("name", "王小二") intent.putExtra("age", "25") startActivity(intent)
在TestActivity去取引數也麻煩、這裡就不貼了
那麼新方式是怎麼寫呢?請看
新寫法
跳轉的程式碼
TestActivity().apply { name = "Come from MainActivity" num = 100 startActivity(this@MainActivity, this) }
取值的程式碼
class TestActivity : AppCompatActivity() { var name by ActivityArgument("default") var num by ActivityArgument(0) private lateinit var textView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_test) textView = findViewById(R.id.textView) textView.text = "$name - $num" } }
是不是不用再定義一大堆的靜態變數,然後存進去再取出來了,設定一次就可以了。
實現原理
1.提前建立Activity的例項,當系統要建立Activity例項時把我們之前建立的給它(要hook程式碼,瞭解Activity啟動原理的就很好理解,這裡就不說了,網上有很多這方面的資源)
2.利用Kotlin的屬性代理
show code
儲存Activity例項的單例類
object ActivityInstanceManager { private val activityMap = HashMap<Class<*>, Activity>() fun putActivity(activity: Activity) { activityMap[activity.javaClass] = activity } fun getActivity(clazz: Class<*>): Activity? { return activityMap.remove(clazz) } }
Activity擴充套件StartActivity方法類
fun Activity.startActivity(activity: Activity, nextActivity: Activity) { ActivityInstanceManager.putActivity(nextActivity) activity.startActivity(createIntent(activity, nextActivity)) } fun Activity.startActivityForResult(activity: Activity, nextActivity: Activity, requestCode: Int) { ActivityInstanceManager.putActivity(nextActivity) activity.startActivityForResult(createIntent(activity, nextActivity), requestCode) } private fun createIntent(activity: Activity, nextActivity: Activity): Intent { val intent = IntentInstanceManager.getIntentAndRemove(nextActivity) ?: Intent() intent.setClass(activity, nextActivity::class.java) return intent }
屬性代理類
class ActivityArgument<T : Any>(private val default: T? = null) : ReadWriteProperty<Activity, T> { var value: T? = null override operator fun getValue(thisRef: Activity, property: KProperty<*>): T { if (value == null) { val args = thisRef.intent.extras ?: throw IllegalStateException("Cannot read property ${property.name} if no arguments have been set") if (args.containsKey(property.name)) { @Suppress("UNCHECKED_CAST") value = args.get(property.name) as T } else { value = default } } return value ?: throw IllegalStateException("Property ${property.name} could not be read") } override operator fun setValue(thisRef: Activity, property: KProperty<*>, value: T) { var intent = IntentInstanceManager.getIntent(thisRef) if (intent == null) { intent = Intent().apply { putExtras(Bundle()) } IntentInstanceManager.putIntent(thisRef, intent) } val args = intent.extras val key = property.name when (value) { is String -> args.putString(key, value) is Int -> args.putInt(key, value) is Short -> args.putShort(key, value) is Long -> args.putLong(key, value) is Byte -> args.putByte(key, value) is ByteArray -> args.putByteArray(key, value) is Char -> args.putChar(key, value) is CharArray -> args.putCharArray(key, value) is CharSequence -> args.putCharSequence(key, value) is Float -> args.putFloat(key, value) is Bundle -> args.putBundle(key, value) is Binder -> BundleCompat.putBinder(args, key, value) is android.os.Parcelable -> args.putParcelable(key, value) is java.io.Serializable -> args.putSerializable(key, value) else -> throw IllegalStateException("Type ${value.javaClass.canonicalName} of property ${property.name} is not supported") } intent.putExtras(args) } }
private val intentMap = HashMap<Activity, Intent>() fun putIntent(activity: Activity, intent: Intent) { intentMap[activity] = intent } fun getIntent(activity: Activity): Intent? { return intentMap[activity] } fun getIntentAndRemove(activity: Activity): Intent? { return intentMap.remove(activity) } fun removeIntent(activity: Activity) { intentMap.remove(activity) } }
好了 核心的就是些
還有的就是hook Instrumentation 類建立Activity
class InstrumentationProxy(val mInstrumentation: Instrumentation) : Instrumentation() { override fun newActivity(cl: ClassLoader?, className: String?, intent: Intent?): Activity { println("className = ${className}") val clazz = Class.forName(className) return ActivityInstanceManager.getActivity(clazz) ?: super.newActivity(cl, className, intent) } }
替換系統 Instrumentation
private fun hookActivityThreadInstrumentation() { try { val activityThreadClazz = Class.forName("android.app.ActivityThread") val activityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread") activityThreadField.isAccessible = true val activityThread = activityThreadField.get(null) val instrumentationField = activityThreadClazz.getDeclaredField("mInstrumentation") instrumentationField.isAccessible = true val instrumentation = instrumentationField.get(activityThread) as Instrumentation val proxy = InstrumentationProxy(instrumentation) instrumentationField.set(activityThread, proxy) } catch (e: Exception) { e.printStackTrace() } }
這樣就能把我們提前建立的Activity的例項給系統了
到這裡就完了 以後啟動Activity就可以這樣寫了
TestActivity().apply { name = "Come from MainActivity" num = 100 startActivity(this@MainActivity, this) }
是不是可以提前下班、、、
Fragment也是一樣的,因為不需要hook 就更簡單了
class TestFragment : Fragment() { companion object { fun attach(act: FragmentActivity, containerId: Int) { val fragment = TestFragment().apply { age = 18 name = "小花" } act.supportFragmentManager.beginTransaction().replace(containerId, fragment).commit() } } var name by FragmentArgument("lily") var age by FragmentArgument(16) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_test, container, false) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) val textView = view!!.findViewById<TextView>(R.id.contentTv) textView.text = "$name - $age" } }
看下程式碼就明白了
屬性代理還有SharedPreferences 的應用
可以這樣寫
private var keystore by PreferenceArgument(this, "keystore", "abc")
原理是一樣的 就貼下程式碼吧
class PreferenceArgument<T>(context: Context, private val name: String, private val default: T) : ReadWriteProperty<Any?, T> { private val prefs by lazy { context.getSharedPreferences("${context.packageName}_preferences", Context.MODE_PRIVATE) } override fun getValue(thisRef: Any?, property: KProperty<*>): T = findPreferences(name, default) override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { putPreferences(name, value) } private fun putPreferences(name: String, value: T) = with(prefs.edit()) { when (value) { is Int -> putInt(name, value) is Long -> putLong(name, value) is Float -> putFloat(name, value) is String -> putString(name, value) is Boolean -> putBoolean(name, value) else -> throwIllegalArgumentException("This type can not be saved into preferences") } apply() } private fun findPreferences(name: String, default: T) = with(prefs) { var result = when (default) { is Int -> getInt(name, default) is Long -> getLong(name, default) is Float -> getFloat(name, default) is String -> getString(name, default) is Boolean -> getBoolean(name, default) else -> throwIllegalArgumentException("This type can not be saved into preferences") } result as T } }