Activity/Fragmnet 传参的新方式

作者: 暗影1 | 来源:发表于2019-02-21 11:16 被阅读19次

    在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 ->
                    throw  IllegalArgumentException("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 ->
                    throw  IllegalArgumentException("This type can not be saved into preferences")
            }
    
            result as T
        }
    }
    

    完整代码

    相关文章

      网友评论

        本文标题:Activity/Fragmnet 传参的新方式

        本文链接:https://www.haomeiwen.com/subject/htalyqtx.html