使用 Kotlin 优化 Intent 数据传递

gdmutou 7年前
   <p>首先,这篇文章可能会涉及到的知识点有:</p>    <ul>     <li>伴生对象</li>     <li>对象表达式与对象声明</li>     <li>扩展函数</li>     <li>扩展属性</li>     <li>高阶函数( lambda 表达式)</li>     <li>带接收者的函数字面值</li>     <li>委托属性</li>    </ul>    <h2>Java 的实现方式</h2>    <p>在 Android 开发中, Activity 之间的数据传递是不可避免的。初次接触 Android 编程的时候,许多教程会写出这样的代码:</p>    <pre>  <code class="language-java">// 第一个 Activity   Intent intent = new Intent(context, AimActivity.class);  intent.putExtra("msg", message);  startActivity(intent);    // AimActivity   this.message = getIntent().getStringExtra("msg");  </code></pre>    <p>这样写当然没有错,的确达到了传递数据的目的,但是,这却会让代码难以维护。稍微有些开发经验的,都会选择将 Extra 信息的键值抽出变为常量,并且把许多类似的键值放在同一个地方,避免出现键值的冲突。</p>    <p>为了更好的可读性,还有一些人会这样去封装 Intent :</p>    <pre>  <code class="language-java">private static final String MSG_KEY = "key for message";    @Nullable publicstaticStringgetMessage(@NonNull Intent intent){    return intent.getStringExtra(MSG_KEY);  }    publicstaticvoidsetMessage(@NonNull Intent intent, String message){    intent.putExtra(MSG_KEY, message);  }  </code></pre>    <p>这样一来,读写 Intent 的确如读写类成员属性一般了,可是,不得不说,这也极大地增加了代码的编写量。当然,或者可以通过注解、动态生成代码的方式来解决这一问题,但这不是本篇文章的解决方案。</p>    <h2>Kotlin 的登场</h2>    <p>在 Java 的实现方式中,最适合阅读、最符合面向对象思想的数据传递方式当属最后一种,那,下面就用一些 Kotlin 的特性来实现:</p>    <pre>  <code class="language-java">object IntentOptions {    private const val MSG_KEY = "key for message"        funIntent.getMessage(): String? = getStringExtra(MSG_KEY)        funIntent.setMessage(message:String?) {      putExtra(MSG_KEY, message)    }  }    // 数据的存储与获取  with(IntentOptions) {    intent.setMessage("message")    message = intent.getMessage()  }  </code></pre>    <p>可以看到,利用 Kotlin 提供的扩展函数的特性,我们就可以在 Intent 对象上直接使用 getter/setter 了,这是 Java 语言做不到的。</p>    <p>需要注意的是 with 表达式的使用,这涉及到 <strong>扩展函数</strong> 的作用域问题。在 Kotlin 中,扩展函数的作用范围只在其定义范围内。一般而言,我们把许多扩展函数定义在了顶级作用域中,这样在整个 App ,被扩展的对象实例都可以使用该函数。但是考虑到,我们不能在 Intent 对象上无限制地添加 getter/setter ,所以利用 object ,让这些扩展函数只在该对象的作用域内有效。在实际使用中,这个 object 可以是某个 Activity 的伴生对象。</p>    <p>到这里,我们可以更进一步,使用 <strong>扩展属性</strong> ,让赋值/取值的过程更符合 Kotlin 的语言规范:</p>    <pre>  <code class="language-java">object IntentOptions {    private const val MSG_KEY = "key for message"        var Intent.message: String?     get() = getStringExtra(MSG_KEY)     set(message) {        putExtra(MSG_KEY, message)     }  }    // 使用  with(IntentOptions) {    intent.message = "message"    message = intent.message  }  </code></pre>    <p>嗯,不论写那个 object 的过程怎样,至少,在使用这样的 Intent 方面,体验到了读写原生对象的便利啊。</p>    <h2>Kotlin 中的委托</h2>    <p>委托?为什么要用委托?上面的代码我们使用委托的模式改写一下:</p>    <pre>  <code class="language-java">classIntentExtraStringDelegate(val key: String) {    fungetValue(intent:Intent): String? =      intent.getStringExtra(name)        funsetValue(intent:Intent, value:String?) {      intent.putExtra(name, value)    }  }    object IntentOptions {    private val messageDelegate = IntentExtraStringDelegate("key for message")        var Intent.message: String?     get() = messageDelegate.getValue(this)     set(message) = messageDelegate.setValue(this, message)  }  </code></pre>    <p>可以看到,委托就是把 Intent 内容的读写操作抽取出来,以便复用。进行了这样的抽取之后,我们又可以在 Kotlin 的语言特性中找到这样的实现:</p>    <pre>  <code class="language-java">classIntentExtraString(private val key: String) {    operator fungetValue(intent:Intent, property:KProperty<*>): String? =      intent.getStringExtra(key)        operator funsetValue(intent:Intent, property:KProperty<*>, value:String?) {      intent.putExtra(key, value)    }  }    // 使用  object IntentOptions {    var Intent.message by IntentExtraString("key for message")  }  </code></pre>    <p>啊哈,这样一来,相当于仅仅是对 Intent 内的变量进行了一下声明,就可以在对应的范围内使用了!而存储的键值,也完全可以省略,直接使用字段名:</p>    <pre>  <code class="language-java">classIntentExtraString(private val key: String? = null) {    private val KProperty<*>.extraName: String     get() = this@IntentExtraString.key ?: name        operator fungetValue(intent:Intent, property:KProperty<*>): String? =      intent.getStringExtra(property.extraName)        operator funsetValue(intent:Intent, property:KProperty<*>, value:String?) {      intent.putExtra(property.extraName, value)    }  }  </code></pre>    <p>一般而言,在 Activity 可以这样使用:</p>    <pre>  <code class="language-java">classActivity:AppCompatActivity() {    companion object IntentOptions {      var Intent.id by IntentExtraString()      var Intent.name by IntentExtraString()      var Intent.message by IntentExtraString()    }        funtest(intent:Intent) {      intent.id = "1"      intent.name = "pass"      intent.message = "message"    }  }    funtestOutSide(intent:Intent) = with(Activity.IntentOptions) {    val id = intent.id    val name = intent.name    val message = intent.message  }  </code></pre>    <p>以上,就是使用 Kotlin 优化 Intent 数据传输的基本思路。当然,在这个思路下,还有许多可以封装的,比如,自定义类型数据的传输——可以去看一看原作者的开源库 <a href="/misc/goto?guid=4959742362036784377" rel="nofollow,noindex"> <strong>android-extras-delegates</strong> </a> 。</p>    <p> </p>    <p>来自:http://blog.saplf.top/2017/03/19/kotlin-intent-extension/</p>    <p> </p>