记一次写Intent的扩展 前言 写项目的时候经常会写到跳转Activiey,写到跳转Activit就一定会写到putExtra,就像下面那样
1 2 3 4 intent.putExtra("a" , item .a ) intent.putExtra("b" , item .b ) intent.putExtra("c" , item .c )
挺多哈,每次都要重复前面相同的内容,所以就想着能不能写个扩展来简写。
开始 我们最终实现是这样的:
1 2 3 4 5 6 7 Intent(context, TestActivity::class .java) .putExtraVararg( "a" to item .a, "b" to item .b, "c" to item .c )
仿mapOf 经常用到mapOf()或者mutableMapOf()就会知道,里面是用了Pair类来生成Map的,所以受此启发,打算用Pair来作为参数实现,那么写的时候就应该是这样写,参数为可变参数类型:
1 2 3 4 5 6 7 fun Intent.putExtraVararg ( vararg extras: Pair <String , Any?> ) : Intent { }
putExtra的第一个参数类型肯定是String类型,第二个参数的类型包括Bundle, Boolean, BooleanArray, Byte, ByteArray, Char, CharArray, String等等,所以就用Any类型,因为是可空的,所以加上?。
那么调用的时候,就是这样子的
1 2 3 4 5 6 7 Intent(context, TestActivity::class .java) .putExtraVararg( "a" to item .a, "b" to item .b, "c" to item .c )
是不是就是一开始说的那样,对比平常写的是不是简单了很多。
参数类型匹配 写好了参数,那么就要进行对参数类型的匹配进行对应的putExtra。
vararg参数可以用forEach来循环对每个参数类型进行匹配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 fun Intent.putExtraVararg ( vararg extras: Pair <String , Any?> ) : Intent { if (extras.isEmpty()) return this extras.forEach { (key, value) -> value ?: let { it.putExtra(key, it.toString()) return @forEach } when (value) { is Bundle -> this .putExtra(key, value) is Boolean -> this .putExtra(key, value) is BooleanArray -> this .putExtra(key, value) is Byte -> this .putExtra(key, value) is ByteArray -> this .putExtra(key, value) is Char -> this .putExtra(key, value) is CharArray -> this .putExtra(key, value) is String -> this .putExtra(key, value) is CharSequence -> this .putExtra(key, value) is Double -> this .putExtra(key, value) is DoubleArray -> this .putExtra(key, value) is Float -> this .putExtra(key, value) is FloatArray -> this .putExtra(key, value) is Int -> this .putExtra(key, value) is IntArray -> this .putExtra(key, value) is Long -> this .putExtra(key, value) is LongArray -> this .putExtra(key, value) is Short -> this .putExtra(key, value) is ShortArray -> this .putExtra(key, value) is Parcelable -> this .putExtra(key, value) is Serializable -> this .putExtra(key, value) else -> { throw IllegalArgumentException("Not support $value type ${value.javaClass} .." ) } } } return this }
发现没有,还少了ArrayList<String>、ArrayList<CharSequence>、ArrayList<? extends Parcelable>这三个List的参数类型匹配,因为不能直接is来匹配对应的Array类型,在群里问过之后,才得出最终的方法,Array里面有一个匹配对应类型的扩展函数isArrayOf():
1 2 3 4 5 6 7 @Suppress("REIFIED_TYPE_PARAMETER_NO_INLINE" ) public fun <reified T : Any> Array<*> .isArrayOf () : Boolean = T::class .java.isAssignableFrom(this ::class .java.componentType)
so,这样就简单了,我们先匹配Array,然后在匹配对应的ArrayList<String>、ArrayList<CharSequence>、ArrayList<? extends Parcelable>,最后在对应put方法那里用as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 is Array <*> -> { @Suppress("UNCHECKED_CAST" ) when { value.isArrayOf<String >() -> { this .putStringArrayListExtra(key, value as ArrayList<String ?>) } value.isArrayOf<CharSequence>() -> { this .putCharSequenceArrayListExtra(key, value as ArrayList<CharSequence?>) } value.isArrayOf<Parcelable>() -> { this .putParcelableArrayListExtra(key, value as ArrayList<out Parcelable?>) } } }
这样就实现了不同Array类型的putExtra。
最终 最后完整的代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 fun Intent.putExtraVararg ( vararg extras: Pair <String , Any?> ) : Intent { if (extras.isEmpty()) return this extras.forEach { val key = it.first val value = it.second ?: let { it.putExtra(key, it.toString()) } } extras.forEach { (key, value) -> value ?: let { it.putExtra(key, it.toString()) return @forEach } when (value) { is Bundle -> this .putExtra(key, value) is Boolean -> this .putExtra(key, value) is BooleanArray -> this .putExtra(key, value) is Byte -> this .putExtra(key, value) is ByteArray -> this .putExtra(key, value) is Char -> this .putExtra(key, value) is CharArray -> this .putExtra(key, value) is String -> this .putExtra(key, value) is CharSequence -> this .putExtra(key, value) is Double -> this .putExtra(key, value) is DoubleArray -> this .putExtra(key, value) is Float -> this .putExtra(key, value) is FloatArray -> this .putExtra(key, value) is Int -> this .putExtra(key, value) is IntArray -> this .putExtra(key, value) is Long -> this .putExtra(key, value) is LongArray -> this .putExtra(key, value) is Short -> this .putExtra(key, value) is ShortArray -> this .putExtra(key, value) is Array<*> -> { @Suppress("UNCHECKED_CAST" ) when { value.isArrayOf<String>() -> { this .putStringArrayListExtra(key, value as ArrayList<String?>) } value.isArrayOf<CharSequence>() -> { this .putCharSequenceArrayListExtra(key, value as ArrayList<CharSequence?>) } value.isArrayOf<Parcelable>() -> { this .putParcelableArrayListExtra(key, value as ArrayList<out Parcelable?>) } } } is Parcelable -> this .putExtra(key, value) is Serializable -> this .putExtra(key, value) else -> { throw IllegalArgumentException("Not support $value type ${value.javaClass} .." ) } } } return this }
转成Java是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 public final class IntentExtKt { @NotNull public static final Intent putExtraVararg(@NotNull Intent $receiver , @NotNull Pair... extras) { Intrinsics.checkParameterIsNotNull($receiver , "receiver$0 " ); Intrinsics.checkParameterIsNotNull(extras, "extras" ); if (extras.length == 0 ) { return $receiver ; } else { Pair[] var3 = extras; int var4 = extras.length; int var5; Pair element$iv ; boolean var8; String key; for (var5 = 0 ; var5 < var4; ++var5) { element$iv = var3[var5]; var8 = false ; key = (String )element$iv .getFirst(); if (element$iv .getSecond() == null ) { int var12 = false ; $receiver .putExtra(key, $receiver .toString()); } } var3 = extras; var4 = extras.length; for (var5 = 0 ; var5 < var4; ++var5) { element$iv = var3[var5]; var8 = false ; key = (String )element$iv .component1(); Object value = element$iv .component2(); if (value != null ) { if (value instanceof Bundle) { $receiver .putExtra(key, (Bundle)value); } else if (value instanceof Boolean ) { $receiver .putExtra(key, (Boolean )value); } else if (value instanceof boolean []) { $receiver .putExtra(key, (boolean [])value); } else if (value instanceof Byte) { $receiver .putExtra(key, ((Number)value).byteValue()); } else if (value instanceof byte[]) { $receiver .putExtra(key, (byte[])value); } else if (value instanceof Character) { $receiver .putExtra(key, (Character)value); } else if (value instanceof char[]) { $receiver .putExtra(key, (char[])value); } else if (value instanceof String ) { $receiver .putExtra(key, (String )value); } else if (value instanceof CharSequence) { $receiver .putExtra(key, (CharSequence)value); } else if (value instanceof Double ) { $receiver .putExtra(key, ((Number)value).doubleValue()); } else if (value instanceof double []) { $receiver .putExtra(key, (double [])value); } else if (value instanceof Float ) { $receiver .putExtra(key, ((Number)value).floatValue()); } else if (value instanceof float []) { $receiver .putExtra(key, (float [])value); } else if (value instanceof Integer ) { $receiver .putExtra(key, ((Number)value).intValue()); } else if (value instanceof int []) { $receiver .putExtra(key, (int [])value); } else if (value instanceof Long) { $receiver .putExtra(key, ((Number)value).longValue()); } else if (value instanceof long[]) { $receiver .putExtra(key, (long[])value); } else if (value instanceof Short) { $receiver .putExtra(key, ((Number)value).shortValue()); } else if (value instanceof short[]) { $receiver .putExtra(key, (short[])value); } else if (value instanceof Object []) { if ((Object [])value instanceof String []) { $receiver .putStringArrayListExtra(key, (ArrayList)value); } else if ((Object [])value instanceof CharSequence[]) { $receiver .putCharSequenceArrayListExtra(key, (ArrayList)value); } else if ((Object [])value instanceof Parcelable[]) { $receiver .putParcelableArrayListExtra(key, (ArrayList)value); } } else if (value instanceof Parcelable) { $receiver .putExtra(key, (Parcelable)value); } else { if (!(value instanceof Serializable )) { throw (Throwable )(new IllegalArgumentException("Not support " + value + " type " + value.getClass() + ".." )); } $receiver .putExtra(key, (Serializable )value); } } else { int var13 = false ; $receiver .putExtra(key, $receiver .toString()); } } return $receiver ; } } }
顺手写别的扩展 既然用到Intent的扩展,那么就顺手写下Activity的startActivity的扩展
1 2 3 4 5 6 7 8 9 10 11 12 13 fun Context .to Activity(packageContext : Context?, cls : Class<* >, vararg extras : Pair<String, Any?>) { startActivity(Intent(packageContext , cls ) .putExtraVararg(* extras ) ) }fun Context .to Activity(intent : Intent) { startActivity(intent ) }
调用的时候就是这样子的
1 2 3 4 5 6 7 8 9 10 context .toActivity( context , SearchActivity::class.java, SearchActivity.SEARCH_TYPE to item .type, SearchActivity.SEARCH_UID to item .uid, SearchActivity.SEARCH_NAME to item .name , SearchActivity.SEARCH_KEY to item .key , SearchActivity.SEARCH_GROUP to item .group )
总结 通过这次的扩展,也学到了关于Array的一些扩展函数,可以说是很美好了。
借助Kotlin特性打造一个有Kotlin味道的Activity跳转工具类库 前言 相信同学们都有过这种感受:在日常开发中,每次使用startActivityForResult时,要做的事情都好多,好麻烦:
定义一个requestCode;
重写onActivityResult方法并在里面去判断requestCode和resultCode;
如果有携带参数,还要一个个通过putExtra方法put进Intent 里;
目标Activity 处理完成后还要把数据一个个put进Intent 中,setResult然后finish;
如果参数是可序列化的泛型类对象(如ArrayList ),取出来的时候不但要显式强制转型,还要把 UNCHECKED_CAST 警告抑制;
当然了,在Github上已经有好几个开源库把 “需要重写onActivityResult方法来接收处理结果” 的问题解决了(其中的原理相信很多同学都已经了解过了,这个我们等下也会详细讲解的)。 但如果有携带参数 的话,依然很麻烦:有多少个参数,就要调用多少次putExtra方法 。 而且最烦的是第5点,转成泛型类对象时,一块黄色的警告在那里,看着挺难受。
不过还好,随着Kotlin 越来越普及,越来越多的开发者都体验到了它的魅力,也许我们可以借助它的一些特性,来做一些事情。。。
这个思路我是直接CV了大佬旺的代码,原文链接:https://www.jowanxu.top/2019/02/11/Android-Intent-Extension/
在Kotlin 的项目中,当我们初始化Map 的时候,通常会这样写:
1 2 3 4 5 6 val map = mapOf( "key0" to "value0" , "key1" to "value1" , "key2" to "value2" )
Key和Value之间的to,是一个中缀函数 ,它返回一个Pair 对象。 mapOf方法接收一个类型为Pair 的可变参数:
1 2 fun <K, V> mapOf (vararg pairs: Pair <K , V>)
最终的实现,它会遍历这个数组pairs,并依次把这些键值对put到一个LinkedHashMap 中。
那么我们也可以参照这个mapOf,来为Intent 写一个扩展方法putExtras,让它在使用的时候,就像创建Map 对象那样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 fun Intent.putExtras (vararg params: Pair <String , Any>) : Intent { if (params.isEmpty()) return this params.forEach { (key, value) -> when (value) { is Int -> putExtra(key, value) is Byte -> putExtra(key, value) is Char -> putExtra(key, value) is Long -> putExtra(key, value) is Float -> putExtra(key, value) is Short -> putExtra(key, value) is Double -> putExtra(key, value) is Boolean -> putExtra(key, value) is Bundle -> putExtra(key, value) is String -> putExtra(key, value) is IntArray -> putExtra(key, value) is ByteArray -> putExtra(key, value) is CharArray -> putExtra(key, value) is LongArray -> putExtra(key, value) is FloatArray -> putExtra(key, value) is Parcelable -> putExtra(key, value) is ShortArray -> putExtra(key, value) is DoubleArray -> putExtra(key, value) is BooleanArray -> putExtra(key, value) is CharSequence -> putExtra(key, value) is Array<*> -> { when { value.isArrayOf<String>() -> putExtra(key, value as Array<String?>) value.isArrayOf<Parcelable>() -> putExtra(key, value as Array<Parcelable?>) value.isArrayOf<CharSequence>() -> putExtra(key, value as Array<CharSequence?>) else -> putExtra(key, value) } } is Serializable -> putExtra(key, value) } } return this }
因为Intent 的putExtra方法只接受String 类型的Key,所以我们也直接指定的Pair 的第一个值的类型为String 了。 总体的逻辑很简单,就像mapOf方法那样:遍历Pair 数组,判断每一个参数值的类型(不同于Java的是,在Kotlin中用is关键字检查类型符合之后,会自动转换成对应的类型,无须显式转换 ),并通过Intent 的putExtra方法把参数put进去。
那么现在给Intent 设置多个参数的时候,就可以写成这样:
1 2 3 4 5 6 7 val intent = Intent() intent.putExtras( "key0" to true , "key1" to 1.23F , "key2" to listOf("1" , "2" ) )
哈哈,的确方便又好看了好多。
简化startActivity操作 虽然说,在没有工具类的帮助下,startActivity一样也可以一句代码搞定,如果不携带参数的话。 但如果要携带参数,就至少要三句了:
创建Intent 对象;
把参数put进Intent 中;
调用startActivity方法;
这种情况我们完全可以借助刚刚的扩展方法putExtras,来把它简化成一行,就像这样:
1 2 startActivity(this , TestActivity::class , "key0" to "value0" , "key1" to "value1")
内部是怎么写的呢? 其实超简单:
1 2 3 4 5 6 fun startActivity ( starter: Activity , target: KClass <out Activity >, vararg params: Pair <String , Any> ) = starter.startActivity(Intent(starter, target.java).putExtras(*params))
我们封装的startActivity方法接收三个参数,分别是:发起的Activity、要启动的Activity、可变参数键值对。 target的类型KClass后面的<out Activity>,在Java 中等同于<T extends Activity>。 params前面的vararg,就是定义成可变参数的意思,等同于Java 中的Pair<String, Any>... params 最后可以看到,也是直接调用了Activity 的startActivity方法,并把调用putExtras扩展方法后的Intent 传进去。
就这么简单。
简化startActivityForResult操作 相信很多同学在Java项目上也有用过一些类似的库,就是把重写onActivityResult改成匿名内部类。 不过我们现在用了Kotlin ,就可以把匿名内部类改成Lambda了,就像这样:
1 2 3 4 5 6 7 8 9 10 11 12 startActivityForResult(this , TestActivity::class , "key0" to "value0" , "key1" to 1.23F , "key2" to listOf (true , false ) ) { if (it == null ) { } else { } }
当目标Activity 处理完成之后,就会回调到后面的Lambda 中。
其中的原理很简单,就是临时附加一个无界面的*Fragment*,当这个*Fragment*附加成功之后,调用startActivityForResult方法,并在重写的onActivityResult方法里面去回调外面传进来的Lambda。 来看看代码怎么写: 先是Fragment :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class GhostFragment : Fragment () { private var requestCode = -1 private var intent: Intent? = null private var callback: ((result: Intent?) -> Unit )? = null fun init (requestCode: Int , intent: Intent , callback: ((result : Intent ?) -> Unit )) { this .requestCode = requestCode this .intent = intent this .callback = callback } override fun onAttach (context: Context ) { super .onAttach(context) intent?.let { startActivityForResult(it, requestCode) } } override fun onActivityResult (requestCode: Int , resultCode: Int , data : Intent ?) { super .onActivityResult(requestCode, resultCode, data ) if (requestCode == this .requestCode) { val result = if (resultCode == Activity.RESULT_OK && data != null ) data else null callback?.let { it(result) } } } override fun onDetach () { super .onDetach() intent = null callback = null } }
可以看到有一个init方法,它用来接收外面传进来的requestCode,intent和callback。 那requestCode怎么定义好呢? 为了避免相同的requestCode,我们可以定义一个静态的Int 变量,在每次startActivityForResult之后自增 ,如果次数多到要溢出时,重新设置为1就OK了,看代码:
1 2 3 4 5 private var sRequestCode = 0 set (value) { field = if (value >= Integer.MAX_VALUE) 1 else value }1234
好,现在来创建一个startActivityForResult方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 fun startActivityForResult ( starter: Activity , target: KClass <out Activity >, vararg params: Pair <String , Any>, callback: ((result : Intent ?) -> Unit ) ) { val intent = Intent(starter, target.java).putExtras(*params) val fm = starter.supportFragmentManager val fragment = GhostFragment() fragment.init (++sRequestCode, intent) { result -> callback(result) fm.beginTransaction().remove(fragment).commitAllowingStateLoss() } fm.beginTransaction().add(fragment, GhostFragment::class .java.simpleName).commitAllowingStateLoss() }
emmm,这个startActivityForResult,就比刚刚的startActivity多了一个callback参数。 可以看到我们并没有直接把这个callback传进fragment里面,而是在外面包装了一层。当这个callback执行完成之后,还会把对应的Fragment 从FragmentManager 中移除掉,就是为了能让这个Fragment的onDetach方法尽快回调(把callback的引用置空),一定程度上避免内存泄漏(因为外部的callback可能持有生命周期比较短的对象引用)。 在最后,会把Fragment 附加到Activity 中,当Fragment 的onAttach回调时,就会启动目标Activity 了。
简化finish操作 当startActivityForResult方法启动的Activity 处理任务完成之后,通常要把一些数据传回给启动者,这个操作我们也是可以优化的:
1 2 3 4 5 fun finish (src: Activity , vararg params: Pair <String , Any>) = with(src) { setResult(Activity.RESULT_OK, Intent().putExtras(*params)) finish() }
可以看到,把参数put进Intent 之后,就调用了setResult方法把Intent 放进去,并finish。 那么在使用的时候,就可以这样写了:
1 2 finish(this , "key0" to "value0" , "key1" to "value1" )
哈哈,是不是简洁了许多。
还记不记得findViewById? 哈哈哈哈,在compileSdkVersion为26之前,通过findViewById取得的View 子类实例时都要进行强制类型转换,26之后,才换成了泛型方法。 现在的getSerializableExtra也存在同样的问题,如果取出来的是一个泛型类的实例,比如List<String>,强制转换后还会出一个 UNCHECKED_CAST 警告,黄色的一块,看着很难受。
那么现在我们也可以参照findViewById,给Intent 创建一个扩展方法: 想一下:Intent 的getStringExtra、getIntExtra、getBooleanExtra等一系列方法,内部都是借助Bundle 来实现的,CTRL点进去会看到,Bundle 是通过一个Map 来储存这些键值对的,比如这个getStringExtra方法:
1 2 3 4 5 6 7 8 9 10 11 public String getString(String key) { unparcel(); final Object o = mMap.get (key); try { return (String) o; } catch (ClassCastException e) { typeWarning(key, o, "String" , e); return null ; } }
可以看到,它从mMap中取出值之后,还是会显式地进行强制类型转换的,那等我们给*Intent*加了泛型的扩展方法之后,除了可代替getSerializableExtra之外,剩下的那些getXXXExtra,是不是也一样可以代替掉呢? 答案是肯定的。因为这些键值对,都是存放在同一个Map 的实例里面,如果拿到了这个实例。。。就可以为所欲为了 ,哈哈哈。 那怎么拿到这个存放键值对的实例呢? 用反射。先拿到Intent 里面的mExtras(Bundle的实例),再通过这个mExtras来获取到mMap(存放键值对的Map实例)。
不过,我看了不同版本的Bundle 代码,发现在5.0之后,Bundle 的主要逻辑被抽取到一个叫BaseBundle 的类里面了,而在此之前,是没有BaseBundle 这个类的,所以等下在获取mMap的Field 之前要做一下版本判断。 还有,当我直接用反射拿到mMap的对象引用之后,却发现是null的,再次翻源码后才注意到:Bundle里面的每一个getXXX方法中,第一句都是会调用unparcel方法的,这个方法里面会调用一个叫initializeFromParcelLocked的方法,没错,mMap正是在这个方法里面初始化的,然后通过*Parcel*的readArrayMapInternal方法来填充键值对 。所以等下在获取mMap实例之前,还要先调用一下unparcel方法(也是通过反射)。 那现在要用到的内部属性和方法一共有3个了(mExtras、mMap、unparcel),我们可以把它缓存起来,这样就不用每次都重新获取,可以提升运行效率:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 internal object IntentFieldMethod { lateinit var mExtras: Field lateinit var mMap: Field lateinit var unparcel: Method init { try { mExtras = Intent::class .java.getDeclaredField("mExtras" ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mMap = BaseBundle::class .java.getDeclaredField("mMap" ) unparcel = BaseBundle::class .java.getDeclaredMethod("unparcel" ) } else { mMap = Bundle::class .java.getDeclaredField("mMap" ) unparcel = Bundle::class .java.getDeclaredMethod("unparcel" ) } mExtras.isAccessible = true mMap.isAccessible = true unparcel.isAccessible = true } catch (e: Exception) { e.printStackTrace() } } }
然后在扩展方法中直接引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun <O> Intent.get (key: String ) : O? { try { val extras = IntentFieldMethod.mExtras.get (this ) as Bundle IntentFieldMethod.unparcel.invoke(extras) val map = IntentFieldMethod.mMap.get (extras) as Map<String, Any> return map[key] as O } catch (e: Exception) { } return null }
emmm,有了这个扩展方法之后,在获取Intent参数时就可以写成这样了:
1 2 3 4 var mData: List<String>? = null mData = intent.get ("Key1" )
或者这样:
1 2 3 val result = intent.get <Int >("Key2" )
因为我们定义的是泛型的方法,所以无论取出来什么类型的值都好,都不用显式地强制转换了,也没有 UNCHECKED_CAST 警告了,哈哈,是不是很棒。
上面所提到的内部属性和方法,都是没有被标记@hide的,可放心使用。
刚刚说到把那些*Field*和*Method*缓存起来,可以提高效率,那究竟比没有缓存的情况下提高了多少呢? 我分别对以下操作进行了耗时统计:
直接调用Intent 的getXXExtra来获取值;
重用Field 和Method ,每次先通过反射获取mMap,然后调用mMap.get方法来获取值;
不重用,每次都先通过Class 取得对应的Field 和Method ,然后通过反射获取mMap,再通过mMap.get方法来获取值;
以下是分别循环10W次的总耗时(单位: ms):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 第一次:15 182 1242 第二次:8 100 1145 第三次:8 96 1143 第四次:8 104 1206 第五次:10 115 1365
可以看到,重用Field 和Method 的话,效率会提升10倍左右,虽然远没有不使用反射的效率高,但是,重复10W次才100毫秒左右,也就是平均每次才花费0.001ms左右,牺牲这点微不足道的效率给我们带来方便,是非常划算的。
Kotlin内联函数不为人知的一面 我们在学习Kotlin 的过程中,都会了解到内联函数 ,适当地使用可以提高性能,节省资源。如果一个方法它被标记成了内联函数,那么在编译时,会将它的方法体,替换到调用它的地方,这时候就不算是正常的方法调用了。
当我写完这个工具类之后,把它发给大佬旺看,他说可以在startActivity和startActivityForResult上面加个具体化的泛型,就像这样:
1 2 3 4 5 inline fun <reified TARGET : Activity> startActivity ( starter: Activity , vararg params: Pair <String , Any> ) = starter.startActivity(Intent(starter, TARGET::class .java).putExtras(*params))
跟原来的代码区别就是把target: KClass<out Activity>换成了<reified TARGET : Activity>。 用reified关键字修饰的泛型与普通的泛型区别就是:前者可以获取到泛型的类型信息,而后者不行。 但是reified依赖内联函数,也就是说,如果使用具体化泛型的话,方法必须用inline修饰。
改完之后,就可以这样来使用了:
1 2 startActivity<TestActivity>(this , "key0" to "value0" , "key1" to "value1" )
哈哈,可读性是不是更高了。
当一个方法用inline修饰了之后,表示该方法为内联函数,此时无法访问访问权限 比自己低的成员,比如一个内联函数为public ,那就不能访问该类用private 修饰的成员。 我们接下来要修改的startActivityForResult方法就碰上这个问题了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private var sRequestCode = 0 set (value) { field = if (value >= Integer.MAX_VALUE) 1 else value } inline fun <reified TARGET : Activity> startActivityForResult ( starter: Activity , vararg params: Pair <String , Any>, crossinline callback: ((result : Intent ?) -> Unit ) ) { ...... fragment.init (++sRequestCode, intent) { ...... } ...... }
上面这段代码AS会报错,因为fragment的init方法需要传sRequestCode进去,而这个sRequestCode是private 的,内联函数是public 。 那应该怎么做呢?难道说就没有办法可以解决吗? 大佬旺给出的解决方案是:把private改成@PublishedApi internal。 这样做虽然可以正常编译运行,但是,加了@PublishedApi注解之后的sRequestCode,却可以在任何地方访问和修改!这显然违背了开闭原则。虽然说没有人会手贱去修改它,但每次出代码提示的时候,这个sRequestCode就会显示出来。。。很碍眼。
除了加PublishedApi注解之外,还有别的办法吗? 大佬旺翻了一下资料后说:在方法前加个@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")把它抑制了就能编译通过并正常运行了。。。 试了一下果然可以。但是,为什么呢?
为什么加了Suppress之后就能正常编译和运行? 我看了一下编译成class 之后反编译的代码,发现sRequestCode多了个setter 和getter 方法! 也就是说,在编译过程中,会自动帮我们生成public 的setter 和getter 方法! 这两个public 的静态方法,在编译前是没有的,所以也就不会出代码提示了。 我们刚刚的Kotlin 代码:
1 2 3 4 ...... fragment.init (++sRequestCode, intent) ......
在反编译后,看到的Java 代码是这样的:
1 2 3 access$setSRequestCode$p(access$getSRequestCode$p() + 1 ); fragment.init (access$getSRequestCode$p(), intent);
自动生成的setter 和getter 如下:
1 2 3 4 5 6 7 8 9 10 public static final int access$getSRequestCode$p() { return sRequestCode; }public static final void access$setSRequestCode$p(int var0) { sRequestCode = var0; }
哈哈哈,看来加Suppress 的这个做法,是完全可行的。 查了一下源码,发现我们常用的synchronized方法(在Kotlin 中不是关键字),也是使用了这个抑制的。 以后在项目中的其他地方有类似的需求,相信同学们已经知道要怎么做了吧~
好啦,本篇文章到此结束,有错误的地方请指出,谢谢大家!