简述: 这应该是 2019 年的第一篇文章了,临近过年回家一个月需求是真的很多,正如康少说的那样,一年的需求几乎都在最后一两月写完了。所以写文章也搁置了很久,当然再忙每天都会刷掘金。很久就一直在使用 Kotlin 写项目,说实话到目前为止 Kotlin 用的是越来越顺手了 (心里只能用美滋滋来形容了)。当然这次依然讲的是 Kotlin,说下我这次需求开发中自己一些思考和实践。其中让自己感受最深的就是: "Don't Repeat Yourself"。当你经常写一些重复性的代码,不妨停下来想下是否要去改变这样一种状态。

今天我们来讲个非常非常简单的东西,那就是回调俗称 Callback, 在 Android 开发以及一些客户端开发中经常会使用回调。其实如果端的界面开发当做一个黑盒的话,无非就是输入和输出,输入数据,输出 UI 的渲染以及用户的交互事件,那么这个交互事件大多数场景会采用回调来实现。那么今天一起来说说如何让你的回调更具 kotlin 风味:

  • 1、Java 中的回调实现
  • 2、使用 Kotlin 来改造 Java 中的回调
  • 3、进一步让你的回调更具 Kotlin 风味
  • 4、Object 对象表达式回调和 DSL 回调对比
  • 5、Kotlin 中回调使用建议
  • 6、Don't Repeat Yourself(DSL 回调配置太模板化了,不妨来撸个自动生成代码的 AS 插件吧)
  • 7、DslListenerBuilder 插件基本介绍和使用
  • 8、DslListenerBuilder 插件源码和 Velocity 模板引擎基本介绍
  • 9、总结

一、Java 中的回调实现

Java 中的回调一般处理步骤都是写一个接口,然后在接口中定义一些回调函数;然后再暴露一个设置回调接口的函数,传入函数实参就是回调接口的一个实例,一般情况都是以匿名对象形式存在。例如以 Android 中 OnClickListener 和 TextWatcher 源码为例:

  • 1、OnClickListener 回调的 Java 实现
//OnClickListener 的定义
public interface OnClickListener {
    void onClick(View v);
}

public void setOnClickListener(OnClickListener listener) {
this.clickListener = listener;
}

//OnClickListener 的使用
mBtnSubmit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//add your logic code
}
});

复制代码
  • 2、TextWatcher 回调的 Java 实现
//TextWatcher 的定义
public interface TextWatcher extends NoCopySpan {
    public void beforeTextChanged(CharSequence s, int start,int count, int after);
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onTextChanged</span><span class="hljs-params">(CharSequence s, <span class="hljs-keyword">int</span> start, <span class="hljs-keyword">int</span> before, <span class="hljs-keyword">int</span> count)</span></span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterTextChanged</span><span class="hljs-params">(Editable s)</span></span>;

}

public void addTextChangedListener(TextWatcher watcher) {
if (mListeners == null) {
mListeners = new ArrayList<TextWatcher>();
}

mListeners.add(watcher);

}

//TextWatcher 的使用
mEtComment.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//add your logic code
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onTextChanged</span><span class="hljs-params">(CharSequence s, <span class="hljs-keyword">int</span> start, <span class="hljs-keyword">int</span> before, <span class="hljs-keyword">int</span> count)</span> </span>{
        <span class="hljs-comment">//add your logic code</span>
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterTextChanged</span><span class="hljs-params">(Editable s)</span> </span>{
        <span class="hljs-comment">//add your logic code</span>
}

});

复制代码

二、使用 Kotlin 来改造 Java 中的回调

针对上述 Java 中的回调写法,估计大部分人转到 Kotlin 后,估计会做如下处理:

1、如果接口只有一个回调函数可以直接使用 lamba 表达式实现回调的简写。

2、如果接口中含有多个回调函数,都会使用object 对象表达式来实现的。

以改造上述代码为例:

  • 1、(只有一个回调函数简写形式)OnClickListener 回调 Kotlin 改造
// 只有一个回调函数普通简写形式: OnClickListener 的使用
mBtnSubmit.setOnClickListener { view ->
    //add your logic code
}

// 针对 OnClickListener 监听设置 Coroutine 协程框架中 onClick 扩展函数的使用
mBtnSubmit.onClick { view ->
//add your logic code
}

//Coroutine 协程框架: onClick 的扩展函数定义
fun android.view.View.onClick(
context: CoroutineContext = UI,
handler: suspend CoroutineScope.(v: android.view.View?)
-> Unit
) {
setOnClickListener { v ->
launch(context) {
handler(v)
}
}
}

复制代码
  • 2、(多个回调函数 object 表达式)TextWatcher 回调的 Kotlin 改造 (object 对象表达式)
mEtComment.addTextChangedListener(object: TextWatcher{
    override fun afterTextChanged(s: Editable?) {
       //add your logic code
    }
<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">beforeTextChanged</span><span class="hljs-params">(s: <span class="hljs-type">CharSequence</span>?, start: <span class="hljs-type">Int</span>, count: <span class="hljs-type">Int</span>, after: <span class="hljs-type">Int</span>)</span></span> {
   <span class="hljs-comment">//add your logic code</span>
} 

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onTextChanged</span><span class="hljs-params">(s: <span class="hljs-type">CharSequence</span>?, start: <span class="hljs-type">Int</span>, before: <span class="hljs-type">Int</span>, count: <span class="hljs-type">Int</span>)</span></span> {
   <span class="hljs-comment">//add your logic code</span>
}

})

复制代码

关于 object 对象表达式实现的 Kotlin 中回调,有不少的 Kotlin 的小伙伴在公众号留言向我吐槽过,感觉这样的写法是直接从 Java 中的翻译过来的一样,完全看不出 Kotlin 的优势在哪。问我有没有什么更加具有 Kotlin 风味的写法,当然是有的,请接着往下看。

三、进一步让你的回调更具 Kotlin 风味 (DSL 配置回调)

其实如果你看过很多国外大佬的有关 Koltin 项目的源码,你就会发现他们写回调很少去使用 object 表达式去实现回调,而是采用另一种方式去实现,并且整体写法看起来更具有 Kotlin 风味。即使内部用到 object 表达式,暴露给外层中间都会做一层 DSL 配置转换,让外部调用起来更加 Kotlin 化。以 Github 中的MaterialDrawer 项目 (目前已经有 1W 多 star)中官方指定 MatrialDrawer 项目 Kotlin 版本实现的MaterialDrawerKt 项目中间一段源码为例:

  • 1、DrawerImageLoader 回调定义
// 注意: 这个函数参数是一个带返回值的 lambda 表达式
public fun drawerImageLoader(actions: DrawerImageLoaderKt.() -> Unit): DrawerImageLoader.IDrawerImageLoader {
    val loaderImpl = DrawerImageLoaderKt().apply(actions).build() //
    DrawerImageLoader.init(loaderImpl)
    return loaderImpl
}

//DrawerImageLoaderKt: DSL listener Builder 类
public class DrawerImageLoaderKt {
// 定义需要回调的函数 lamba 成员对象
private var setFunc: ((ImageView, Uri, Drawable?, String?) -> Unit)? = null
private var placeholderFunc: ((Context, String?) -> Drawable)? = null

<span class="hljs-keyword">internal</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">build</span><span class="hljs-params">()</span></span> = <span class="hljs-keyword">object</span> : AbstractDrawerImageLoader() {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> setFunction: (ImageView, Uri, Drawable?, String?) -&gt; <span class="hljs-built_in">Unit</span> = setFunc
            ?: <span class="hljs-keyword">throw</span> IllegalStateException(<span class="hljs-string">"DrawerImageLoader has to have a set function"</span>)

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> placeholderFunction = placeholderFunc
            ?: { ctx, tag -&gt; <span class="hljs-keyword">super</span>.placeholder(ctx, tag) }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">set</span><span class="hljs-params">(imageView: <span class="hljs-type">ImageView</span>, uri: <span class="hljs-type">Uri</span>, placeholder: <span class="hljs-type">Drawable</span>?, tag: <span class="hljs-type">String</span>?)</span></span> = setFunction(imageView, uri, placeholder, tag)

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">placeholder</span><span class="hljs-params">(ctx: <span class="hljs-type">Context</span>, tag: <span class="hljs-type">String</span>?)</span></span> = placeholderFunction(ctx, tag)

}

<span class="hljs-comment">//暴露给外部调用的回调函数,在构建类中类似setter,getter方法</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">set</span><span class="hljs-params">(setFunction: (<span class="hljs-type">imageView</span>: <span class="hljs-type">ImageView</span>, uri: <span class="hljs-type">Uri</span>, placeholder: <span class="hljs-type">Drawable</span>?, tag: <span class="hljs-type">String</span>?)</span></span> -&gt; <span class="hljs-built_in">Unit</span>) {
    <span class="hljs-keyword">this</span>.setFunc = setFunction
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">placeholder</span><span class="hljs-params">(placeholderFunction: (<span class="hljs-type">ctx</span>: <span class="hljs-type">Context</span>, tag: <span class="hljs-type">String</span>?)</span></span> -&gt; Drawable) {
    <span class="hljs-keyword">this</span>.placeholderFunc = placeholderFunction
}
复制代码
  • 2、DrawerImageLoader 回调使用
 drawerImageLoader {
   // 内部的回调函数可以选择性重写
    set { imageView, uri, placeholder, _ ->
        Picasso.with(imageView.context)
               .load(uri)
               .placeholder(placeholder)
               .into(imageView)
        }
cancel { imageView -&gt;
    Picasso.with(imageView.context)
           .cancelRequest(imageView)
}

}

复制代码

可以看到使用 DSL 配置的回调更加具有 Kotlin 风味,让整个回调看起来非常的舒服,那种效果岂止丝滑。

四、DSL 配置回调基本步骤

在 Kotlin 的一个类中实现了 DSL 配置回调非常简单主要就三步:

  • 1、定义一个回调的 Builder 类,并且在类中定义回调 lamba 表达式对象成员,最后再定义 Builder 类的成员函数,这些函数就是暴露给外部回调的函数。个人习惯把它作为一个类的内部类。类似下面这样
class AudioPlayer(context: Context){
     //other logic ...
 inner <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ListenerBuilder</span> </span>{
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">var</span> mAudioPlayAction: ((AudioData) -&gt; <span class="hljs-built_in">Unit</span>)? = <span class="hljs-literal">null</span>
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">var</span> mAudioPauseAction: ((AudioData) -&gt; <span class="hljs-built_in">Unit</span>)? = <span class="hljs-literal">null</span>
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">var</span> mAudioFinishAction: ((AudioData) -&gt; <span class="hljs-built_in">Unit</span>)? = <span class="hljs-literal">null</span>

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onAudioPlay</span><span class="hljs-params">(action: (<span class="hljs-type">AudioData</span>)</span></span> -&gt; <span class="hljs-built_in">Unit</span>) {
        mAudioPlayAction = action
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onAudioPause</span><span class="hljs-params">(action: (<span class="hljs-type">AudioData</span>)</span></span> -&gt; <span class="hljs-built_in">Unit</span>) {
        mAudioPauseAction = action
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onAudioFinish</span><span class="hljs-params">(action: (<span class="hljs-type">AudioData</span>)</span></span> -&gt; <span class="hljs-built_in">Unit</span>) {
        mAudioFinishAction = action
    }
}

}

复制代码
  • 2、然后,在类中声明一个 ListenerBuilder 的实例引用,并且暴露一个设置该实例对象的一个方法,也就是我们常说的注册事件监听或回调的方法,类似 setOnClickListenter 这种。但是需要注意的是函数的参数是带 ListenerBuilder 返回值的 lamba,类似下面这样:
class AudioPlayer(context: Context){
      //other logic ...
 <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> mListener: ListenerBuilder
 <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">registerListener</span><span class="hljs-params">(listenerBuilder: <span class="hljs-type">ListenerBuilder</span>.()</span></span> -&gt; <span class="hljs-built_in">Unit</span>) {<span class="hljs-comment">//带ListenerBuilder返回值的lamba</span>
    mListener = ListenerBuilder().also(listenerBuilder)
 }

}

复制代码
  • 3、最后在触发相应事件调用 Builder 实例中 lamba 即可
class AudioPlayer(context: Context){
      //other logic ...
     val mediaPlayer = MediaPlayer(mContext)
        mediaPlayer.play(mediaItem, object : PlayerCallbackAdapter() {
            override fun onPlay(item: MediaItem?) {
                if (::mListener.isInitialized) {
                    mListener.mAudioPlayAction?.invoke(mAudioData)
                }
            }
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onPause</span><span class="hljs-params">(item: <span class="hljs-type">MediaItem</span>?)</span></span> {
            <span class="hljs-keyword">if</span> (::mListener.isInitialized) {
                mListener.mAudioPauseAction?.invoke(mAudioData)
            }
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onPlayCompleted</span><span class="hljs-params">(item: <span class="hljs-type">MediaItem</span>?)</span></span> {
            <span class="hljs-keyword">if</span> (::mListener.isInitialized) {
                mListener.mAudioFinishAction?.invoke(mAudioData)
            }
        }
    })  

}

复制代码
  • 4、外部调用
val audioPlayer = AudioPlayer(context)
    audioPlayer.registerListener {
       // 可以任意选择需要回调的函数,不必要完全重写
        onAudioPlay {
            //todo your logic
        }
    onAudioPause {
       <span class="hljs-comment">//todo your logic</span>
    }

    onAudioFinish {
       <span class="hljs-comment">//todo your logic</span>
    }
}
复制代码

相比 object 表达式回调写法,有没有发现 DSL 回调配置更懂 Kotlin. 可能大家看起来确实不错,但是不知道它具体原理,毕竟这样写法太语法糖化,不太好理解,让我们接下来一起揭开它的糖衣。

五、揭开 DSL 回调配置的语法糖衣

  • 1、原理阐述

DSL 回调配置其实挺简单的,实际上就一个 Builder 类中维护着多个回调 lambda 的实例,然后在外部回调的时候再利用带 Builder 类返回值实例的 lamba 特性,在该 lambda 作用域内 this 可以内部表达为 Builder 类实例,利用 Builder 类实例调用它内部定义成员函数并且赋值初始化 Builder 类回调 lambda 成员实例,而这些被初始化过的 lambda 实例就会在内部事件被触发的时候执行 invoke 操作。如果在该 lambda 内部没有调用某个成员方法,那么在该 Builder 类中这个回调 lambda 成员实例就是为 null, 即使内部事件触发,为空就不会回调到外部。

换句话就是外部回调的函数 block 块会通过 Builder 类中成员函数初始化 Builder 类中回调 lambda 实例 (在上述代码表现就是 mXXXAction 实例),然后当内部事件触发后,根据当前 lambda 实例是否被初始化,如果初始化完毕,就是立即执行这个 lambda 也就是执行传入的 block 代码块

  • 2、代码拆解 为了更加清楚论证上面的阐述,我们可以把代码拆解一下:
mAudioPlayer.registerListener({
    //registerListener 参数是个带 ListenerBuilder 实例返回值的 lambda
    // 所以这里 this 就是内部指代为 ListenerBuilder 实例
    this.onAudioPlay ({  
        //logic block 
    })
    this.onAudioPause ({ 
        // logic block
    })
    this.onAudioFinish({ 
        // logic block
    })
  })
复制代码

onAudioPlay为例其他同理,调用ListenerBuilderonAudioPlay函数,并传入block块来赋值初始化ListenerBuilder类中的mAudioPlayActionlambda 实例,当AudioPlayer中的onPlay函数被回调时,就执行mAudioPlayActionlambda。

貌似看起来 object 对象表达式回调相比 DSL 回调表现那么一无是处,是不是完全可以摒弃 object 对象表达式这种写法呢?其实不然,object 对象表达式这种写法也是有它优点的,具体有什么优点,请接着看它们两种形式对比。

六、object 对象表达式回调和 DSL 回调对比

  • 1、调用写法上对比
// 使用 DSL 配置回调
val audioPlayer = AudioPlayer(context)
    audioPlayer.registerListener {
       // 可以任意选择需要回调的函数,不必要完全重写
        onAudioPlay {
            //todo your logic
        }
    onAudioPause {
       <span class="hljs-comment">//todo your logic</span>
    }

    onAudioFinish {
       <span class="hljs-comment">//todo your logic</span>
    }
}

// 使用 object 对象表达式回调
val audioPlayer = AudioPlayer(context)
audioPlayer.registerListener(object: AudioPlayListener{
override fun onAudioPlay(audioData: AudioData) {
//todo your logic
}
override fun onAudioPause(audioData: AudioData) {
//todo your logic
}
override fun onAudioFinish(audioData: AudioData) {
//todo your logic
}
})

复制代码

调用写法对比明显感觉 DSL 配置更加符合 Kotlin 风格,所以 DSL 配置回调更胜一筹

  • 2、使用上对比

使用上 DSL 有个明显优势就是对于不需要监听的回调函数可以直接省略,而对于 object 表达式是直接实现一个接口回调必须重写,虽然它也能做到任意选择自己需要方法回调,但是还是避免不了一层 callback adapter 层的处理。所以与其做个 adapter 层还不如一步到位。所以 DSL 配置回调更胜一筹

  • 3、性能上对比

其实通过上述调用写法上看,一眼就能看出来,DSL 配置回调这种方式会针对每个回调函数都会创建 lambda 实例对象,而 object 对象表达式不管内部回调的方法有多少个,都只会生成一个匿名对象实例。区别就在这里,所以在性能方面 object 对象表达式这种方式会更优一点,但是通过问过一些 Kotlin 社区的大佬们他们还是更倾向于 DSL 配置这种写法。所以其实这两种方式都挺好的,看不同需求,自己权衡选择即可, 反正我个人挺喜欢 DSL 那种。为了验证我们上述所说的,不妨来看下两种方式下反编译的代码,看看是否是我们所说的那样:

//DSL 配置回调反编译 code
   public final void setListener(@NotNull Function1 listener) {
      Intrinsics.checkParameterIsNotNull(listener, "listener");
      ListenerBuilder var2 = new ListenerBuilder();
      listener.invoke(var2);
      ListenerBuilder var10000 = this.mListener;
      // 获取 AudioPlay 方法对应的实例对象
      Function0 var3 = var10000.getMAudioPlayAction$Coroutine_main();
      Unit var4;
      if (var3 != null) {
         var4 = (Unit)var3.invoke();
      }
      // 获取 AudioPause 方法对应的实例对象
      var3 = var10000.getMAudioPauseAction$Coroutine_main();
      if (var3 != null) {
         var4 = (Unit)var3.invoke();
      }
      // 获取 AudioFinish 方法对应的实例对象
      var3 = var10000.getMAudioFinishAction$Coroutine_main();
      if (var3 != null) {
         var4 = (Unit)var3.invoke();
      }
   }

//object 对象表达式反编译 code
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, “args”);
int count = true;
PlayerPlugin player = new PlayerPlugin();
//new Callback 一个实例
player.setCallback((Callback)(new Callback() {
public void onAudioPlay() {
}

     <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAudioPause</span><span class="hljs-params">()</span> </span>{
     }

     <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAudioFinish</span><span class="hljs-params">()</span> </span>{
     }
  }));

}

复制代码

七、Don't Repeat Yourself(所以顺便使用 kotlin 来撸个自动生成 ListenerBuilder 的插件吧)

使用过 DSL 配置回调的小伙伴们有没有觉得写这些代码没有任何技术含量的,且浪费时间, 那么 Don't Repeat Yourself 从现在开始。如果整个 DSL 配置回调的过程可以做成类似 toString、setter、getter 方法那样自动生成,岂不美滋滋,所以来撸个插件吧。所以接下来大致介绍下 DslListenerBuilder 插件的开发。

开发整体思路:

实际上就是通过 Swing 的 UI 窗口配置需要信息参数,然后通过 Velocity 模板引擎生成模板代码,然后通过 Intellij Plugin API 将生成的代码插入到当前代码文件中。所以所有需要自动生成代码的需求都类似这样流程。下次需要生成不一样的代码只需要修改 Velocity 模板即可。

使用到技术点:

  • 1、Kotlin 基础开发知识
  • 2、Kotlin 扩展函数
  • 3、Kotlin 的 lambda 表达式
  • 4、Swing UI 组件开发知识
  • 5、Intellij Plugin 开发基本知识
  • 6、IntelliJ Plugin 常用开发 API(Editor、WriteCommandAction、PsiDocumentManager、Document 等 API 的使用)
  • 7、Velocity 模板基本语法 (#if,#foreach,#set 等)
  • 8、Velocity 模板引擎 API 的基本使用

基本介绍和使用:

这是一款自动生成 DSL ListenerBuilder 回调模板代码的 IDEA 插件,支持 IDEA、AndroidStudio 以及 JetBrains 全家桶。

第一步: 首先按照 IDEA 一般插件安装流程安装好 DslListenerBuilder 插件。

第二步: 然后打开具体某个类文件,将光标定位在具体代码生成的位置,

第三步: 使用快捷键调出 Generate 中的面板,选择其中的“Listener Builder”, 然后就会弹出一个面板,可以点击 add 按钮添加一个或多个回调函数的 lamba, 也可以从面板中选择任一一条不需要的 Item 进行删除。

第四步: 最后点击 OK 就可以在指定光标位置生成需要的代码。

九、DslListenerBuilder 插件源码和 Velocity 模板引擎学习资源

这里推荐一些有关 Velocity 模板引擎的学习资源,此外有关插件的更多具体实现内容请查看下面 GitHub 中的源码,如果觉得不错欢迎给个 star~~~

DslListenerBuilder 插件下载地址

DslListenerBuilder 插件源码地址

Velocity 模板基本语法

使用 Velocity 模板引擎快速生成代码

十、总结

到这里有关 Kotlin 回调相关内容已经讲得很清楚了,然后还给大家介绍了如何去开发一个自动生成代码的插件。整个插件开发流程同样适用于其他的代码生成需求。为什么要写这么个插件呢,主要是由于最近需求太多,每次写回调的时候都需要不断重复去写很多类似的代码。有时候当我们在重复性做一些操作的时候,不妨去思考下用什么工具能否把整个流程给自动化。归根结底一句话: Don't Repeat Yourself.

  • Android

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...