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 ->
                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
    }
}

完整代码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容