<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
    <channel>
            <title>软件编程</title>
            <link>https://www.sunging.cn</link>
        <generator>Halo 1.6.1</generator>
        <lastBuildDate>Thu, 04 Jun 2020 16:59:06 CST</lastBuildDate>
                <item>
                    <title>
                        <![CDATA[使用Kotlin将回调封装为挂起函数]]>
                    </title>
                    <link>https://www.sunging.cn/archives/使用kotlin将回调封装为挂起函数</link>
                    <description>
                            <![CDATA[<p>在Kotlin1.3中，协程终于正式发布了，这也表示Kotlin协程的语言支持与 API 已完全稳定，可以放心地在项目中使用啦~</p><p>然而，目前支持Kotlin协程的第三方库并不多，较多的库还是使用传统的回调来完成各种异步操作。那么，有没有办法将传统库中的回调封装为Kotlin的挂起函数，从而享受到协程带来的便利呢？</p><p>答案当然是肯定的，将回调封装为Kotlin协程中的挂起函数其实很简单，只需要调用Kotlin协程中的suspendCoroutine方法，就可以生成一个可供调用的挂起函数啦！</p><p>举个例子，下面是我创建的一个测试函数，该函数会在一秒的延时后生成一个随机数并作为传入该函数的回调的参数：</p><pre><code>fun test(callback: (number: Int) -&gt; Unit) {    thread(name = &quot;test&quot;) {        Thread.sleep(1000)        callback(Random.nextInt())    }}</code></pre><p>在正常使用回调的场景下，我们需要传入一个对应的lambda表达式：</p><pre><code>fun main() {    test {       println(&quot;Thread=${Thread.currentThread()},result=$it&quot;)    }}</code></pre><p>该程序运行后，将会打印回调执行的线程及收到的随机数，在我的电脑上的一次运行结果如下：</p><pre><code>Thread=Thread[test,5,main],result=-2069838608</code></pre><p>可以看到，随机数在test线程中得到了打印</p><p>现在，我们使用suspendCoroutine来将其封装为一个挂起函数：</p><pre><code>suspend fun coroutineWrap() =    suspendCoroutine&lt;Int&gt; { continuation: Continuation&lt;Int&gt; -&gt;        test {            continuation.resume(it)        }    }</code></pre><p>可以看到，suspendCoroutine函数使用其实很简单，他接收一个参数为Continuation<T>类型的lambda表达式并返回T，以下是其函数声明：</p><pre><code>public suspend inline fun &lt;T&gt; suspendCoroutine(crossinline block: (Continuation&lt;T&gt;) -&gt; Unit): T</code></pre><p>suspendCoroutine会挂起当前的协程，在传入suspendCoroutine函数的lambda表达式中，我们可以通过调用Continuation的resumeWith方法返回异步调用的结果来恢复挂起的协程。由于resumeWith方法接收的是一个Result<T>类型的参数，Kotlin中还为Continuation扩展出了两个便利的方法，分别是resume(恢复协程，返回结果），resumeWithException(恢复协程，返回异常)。</p><p>现在我们已经将test方法包装为一个挂起函数了，现在我们就在协程域中调用一下它试试：</p><pre><code>fun main() {    GlobalScope.launch{        println(&quot;Thread=${Thread.currentThread()},result=${coroutineWrap()}&quot;)    }    runBlocking {        delay(2000)    }}</code></pre><p>在上述代码中，我使用GlobalScope.launch方法创建了一个协程，为了使test方法有足够的时间生成随机数，我在随后将主线程阻塞了两秒钟。在我的电脑上的一次运行结果如下：</p><pre><code>Thread=Thread[DefaultDispatcher-worker-2,5,main],result=-560965533</code></pre><p>可以看到，通过调用coruntineWrap方法成功的获得了随机数，不过打印却是在协程所对应的线程中。</p><p>借助suspendCoroutine方法，我们可以很简单的将一个异步回调封装为挂起函数，但是我们能发现它的不足，那就是挂起函数作为一个函数，它的返回值只能有一个，并且需要一个确定的类型，这就意味着将异步回调转换为挂起函数更适合回调为一个函数并且参数有且仅有一个的场景(或者其它函数都可以使用异常来代替，另外其实这就是大部分使用回调的场景)，如果在异步回调中有多个不同参数类型的函数被使用，那还得将不同类型的结果进行封装返回，此时，使用传统的回调或许更为恰当。</p><p>另外，使用suspendCoroutine很可能还会带来线程切换，因为Continuation.resumeWith方法需要将结果返回给挂起的协程，如果resumeWith方法不是在挂起的协程所在线程中运行，那么一次线程切换就不可避免了，因此在性能要求比较高或者对回调执行线程有要求的场合，一定要注意这个问题（可以通过指定协程上下文或者直接使用回调来解决）。</p>]]>
                    </description>
                    <pubDate>Sat, 14 Sep 2019 23:00:53 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[使用kotlin来启动Android Activity]]>
                    </title>
                    <link>https://www.sunging.cn/archives/使用kotlin来启动androidactivity</link>
                    <description>
                            <![CDATA[<p>在Android开发中，我们经常有启动一个新的Activity的需求。通常，我们都是通过直接调用Intent(Context packageContext, Class&lt;?&gt; cls)方法构造一个Intent，然后再调用Activity类中的startActivity方法来启用新的Activity，但是，我们有办法使用Kotlin来让这一切变得更简单吗，答案当然是肯定的。</p><p>我们知道，在kotlin中我们可以使用inline关键字来创建 一个内联函数，而内联函数有一个很重要的特性就是还可以使用reified来标记一个实化类型参数，这样我们就可以不用传递一个Class引用了，直接传入类型实参即可。另外，由于我们可以一般都是通过Activity来启动一个新的Activity，所以我们可以为Activity创建一个扩展函数，具体代码如下：</p><pre><code>inline fun &lt;reified T : Activity&gt; Activity.startActivity() {    val intent = Intent(this, T::class.java)    startActivity(intent)}</code></pre><p>通过使用这个扩展函数，我们启动一个新的Activity就变得简单多了，在一个Activity中调用以下代码即可启动SecondActiivty：</p><pre><code>startActivity&lt;SecondActivity&gt;()  //没错，只需要这一行代码</code></pre><p>但是，上面的代码还存在一些问题，如果我们需要向新启动的Activityl中传递一些参数怎么办？通过上面的方式我们就没办法去操作创建目标Intent了啊。</p><p>为了解决这个问题，我们可以对上述扩展函数稍加改进，改进过的函数添加了一个带接收者的lambda表达式：</p><pre><code>inline fun &lt;reified T : Activity&gt; Activity.startActivity(intentAction: Intent.() -&gt; Unit = {}) {    val intent = Intent(this, T::class.java)    intent.intentAction()    startActivity(intent)}</code></pre><p>注意到上面的代码我为接收者为Intent的lambda表达式设置了一个默认的空的lambda表达式，这样当我们不需要传递参数时，依然可以像之前一样使用一行代码即可启动一个新的Actiivty。当我们需要传递参数给新的Activity时，我们可以通过传入一个lambda表达式来操作内部创建的Intent：</p><pre><code>class ParamsActivity : AppCompatActivity() {    companion object{        private const val EXTRA_KEY_TEXT=&quot;ParamsActivity.extra.text&quot;        fun startActivity(activity:Activity,text:String){            activity.startActivity&lt;ParamsActivity&gt; {                putExtra(EXTRA_KEY_TEXT,text)            }        }    }    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_params)        text_view.text=intent.getStringExtra(EXTRA_KEY_TEXT)    }}</code></pre><p>上述伴生对象方法 <code>startActivity(activity:Activity,text:String)</code> 即实现了为启动ParamsActivity的Intent添加一个类型为String的extra内容的功能。</p><p>事实上，受制于java的泛型实现，不少地方的函数调用我们都需要传递一个Class实例来传入类型。依靠kotlin的实化类型参数，在不少地方，我们都可以通过类泛型的方式来传入类型，从而免去了Class实例的传入，使代码看起来更简洁，也更具有可读性，推荐大家在合适的地方积极使用实化类型参数来代替Class类型的形参。</p>]]>
                    </description>
                    <pubDate>Fri, 07 Jun 2019 17:46:03 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[如何使用AOP来打印Android日志]]>
                    </title>
                    <link>https://www.sunging.cn/archives/如何使用aop来打印android日志</link>
                    <description>
                            <![CDATA[<p>传统情况下，我们在Android中打印log都是通过类似于Log.d(TAG,&quot;your log&quot;)这样的语句来进行log打印，但是你是否有时会觉得在你的代码逻辑中充斥着太多的log输出语句，想要改变一下呢？其实，我们可以使用AOP技术，来分离Android的log打印逻辑，减少逻辑代码中log打印语句的使用，更快速地打印Android log。</p><h2 id="准备">准备</h2><p>首先，我们需要在项目中引入Aspectj，关于Android项目中添加Aspectj的支持，推荐github上的<a href="https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx">gradle_plugin_android_aspectjx</a>项目，这是一个gradle插件，配置好之后即可启用Android上的AOP支持</p><h2 id="开始">开始</h2><hr /><p>引入Aspectj的支持后，我们就可以进行Android下的AOP开发了，对于日志记录，我们先定义一个LogMethod注解</p><p><code>LogMethod.kt:</code></p><pre><code>package com.example.aop.annotation@Target(AnnotationTarget.FUNCTION)  // 声明此注解的目标为函数@Retention(AnnotationRetention.RUNTIME)  // 由于添加了是否在函数执行前或函数执行后打印log的控制，所以这个注解需要在运行时仍然存在，用于在运行时获取注解参数的值annotation class LogMethod(val before: Boolean = false, val after: Boolean = false)  // 若before为true，则会在函数执行前打印log，若after为true，则会在函数执行后打印log，可同时设置为true</code></pre><p>注解创建完成后，我们还需要定义一个切面来实现注解方法</p><p><code>LogMethodAspect.kt:</code></p><pre><code>package com.example.aopimport android.util.Logimport com.example.aop.annotation.LogMethodimport org.aspectj.lang.ProceedingJoinPointimport org.aspectj.lang.annotation.Aroundimport org.aspectj.lang.annotation.Aspectimport org.aspectj.lang.annotation.Pointcutimport org.aspectj.lang.reflect.MethodSignatureimport java.util.*@Aspect  // 声明这是一个切面以启用Aspectjclass LogMethodAspect {    // 定义切点为添加了该注解的函数    @Pointcut(&quot;execution(@com.example.aop.annotation.LogMethod * *(..))&quot;)    fun onLogMethod() {    }    // 因为需要在函数执行前后打印log，所以需要创建一个环绕通知    @Around(&quot;onLogMethod()&amp;&amp; @annotation(logMethod)&quot;)    @Throws(Throwable::class)    fun logMethod(joinPoint: ProceedingJoinPoint, logMethod: LogMethod): Any? {        val signature = joinPoint.signature as MethodSignature        // 在函数执行前打印log，并记录参数列表        if (logMethod.before) {            Log.d(signature.toShortString(), &quot; Args : ${joinPoint.args?.let { Arrays.deepToString(joinPoint.args)}}&quot;)        }        // 执行函数        val result = joinPoint.proceed()        // 在函数执行后打印结果，并记录返回值，若无返回值，则打印void        if (logMethod.after) {            val type = signature.returnType.toString()            Log.d(signature.toShortString(), &quot; Result : ${if (&quot;void&quot;.equals(type, ignoreCase = true)) &quot;void&quot; else result}&quot;)        }        // 返回函数执行结果        return result    }}</code></pre><p>就这样，我们的注解就写好了，现在，只需要在需要记录log的函数前面加上@LogMethod(before = true, after = true)注解就行了，当然，可以根据需要，仅在函数执行前或执行后打印log</p><h2 id="使用">使用</h2><p>我们可以创建一个MainActivity来测试我们编写的注解是否生效（部分无关代码已省略）<br /><code>MainActivity.kt:</code></p><pre><code>    ......    @LogMethod(before = true,after = true)    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)    }    @LogMethod(before = true)    override fun onStart() {        super.onStart()    }    @LogMethod(after = true)    override fun onStop() {        super.onStop()    }    ......</code></pre><p>运行此MainActivity，log的输出结果如下：</p><pre><code>com.example.demo D/MainActivity.onCreate(..):  Args : [null]com.example.demo D/MainActivity.onCreate(..):  Result : voidcom.example.demo D/MainActivity.onStart():  Args : []com.example.demo D/MainActivity.onStop():  Result : void</code></pre><p>可以看到，我们并未在代码中使用任何log打印语句，即可成功监测函数的执行情况，达到了使用log进行代码执行情况跟踪的目的</p><h2 id="总结">总结</h2><p>通过使用AOP技术，可以实现Android应用中log模块与应用业务逻辑分离的目的，减少了代码中log输出语句的目的，实现了log输出的集中管理，在实际使用中，还可以根据情况添加额外的判断（如自定义log的输出级别）,增加了代码的可读性与可维护性。</p><p>但是由于受Android AOP实现的限制，目前在Android中仅支持基于注解的切入方式，这也意味着我们仅能针对类、属性或函数进行面向切面编程，所以本log记录注解只能针对函数使用，如果需要在代码块中输出log，仍需使用Android中的log打印函数来实现。</p>]]>
                    </description>
                    <pubDate>Sun, 08 Apr 2018 22:41:17 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Java 8 之 Lambda 表达式]]>
                    </title>
                    <link>https://www.sunging.cn/archives/java8之lambda表达式</link>
                    <description>
                            <![CDATA[<p>Lambda表达式（闭包）的支持可以说是Java 8带来的最大的、最激动人心的改变。Lambda允许把函数作为一个方法的参数（函数作为参数传递进方法中），或者把代码看成数据。很多JVM平台上的语言自诞生之初就支持lambda表达式，如groovy、scala等，但是Java开发者却一直都只能用匿名类来代替，现在，Java 8终于把Lambda表达式引入了，Java开发者现在多了一个选择。</p><p>但是在Java中，函数是不能直接作为参数传递的，但是Lambda表达式却是一个匿名函数，那么Java中是如何解决这个问题的呢？为了让现有的功能与Lambda表达式良好兼容，Java 8中引入了<a href="https://blog.sunging.cn/article/java-functionalInterfaces">函数式接口</a>的概念。函数式接口指的是只有一个抽象方法的接口，这样的接口可以隐式转换为Lambda表达式。</p><p>Lambda表达式由三个部分组成：第一部分为一个括号内用逗号分隔的形式参数，参数是函数式接口里面方法的参数；第二部分为一个箭头符号：-&gt;；第三部分为方法体，可以是表达式和代码块。语法如下：</p><p>1.方法体为表达式，该表达式的值作为返回值返回。</p><pre><code>(parameters) -&gt; expression</code></pre><p>2.方法体为代码块，必须用 {} 来包裹起来，且需要一个 return 返回值，但若函数式接口里面方法返回值是 void，则无需返回值。</p><pre><code>(parameters) -&gt; { statements; }</code></pre><p>如果参数只有一个，那么参数列表的括号可以省略，这样Lambda表达式就简化为：</p><pre><code>parameter -&gt; expression</code></pre><p>注意，Lambda表达式中参数的类型可以由编译器推断出来，无需显式指定（当然，显式指定也是可以的），下面是一个在foreach中使用Lambda表达式的列子，可以看到，参数无需指定类型：</p><pre><code>Arrays.asList( &quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot; ).forEach( w -&gt; System.out.println( w ) );</code></pre><p>其实对比起Java中传统的匿名类，Lambda表达式的一个重要特点的代码简洁，下面的代码是一个使用Lambda表达式来创建并启动一个Java线程的例子：</p><pre><code>new Thread(() -&gt; System.out.println(&quot;Hello, Lambda Expressions ！&quot;)).start();</code></pre><p>对，你没有看错，使用Lambda来创建一个线程就是如此简单，对比以往传统创建线程的方法，显然要简洁得多，以下是使用传统的匿名类来创建并启动一个线程的方法：</p><pre><code>new Thread(new Runnable() {    @Override    public void run() {        System.out.println(&quot;I create a thread !&quot;);    }}).start();</code></pre><p>是不是对如此简洁的写法心动了？其实Lambda表达式带来的不仅仅是更加简洁的代码，它还带来了更高的效率，并且还在Java开发中带来了一个全新的概念——函数式编程。很快，你将会知道Java 8中许多的新特性都是围绕着Lambda表达式而来的，如需了解更多关于Lambda表达式的细节，可以参考<a href="http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html">官方文档</a>。</p>]]>
                    </description>
                    <pubDate>Fri, 02 Feb 2018 23:18:12 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Java 8 之 函数式接口]]>
                    </title>
                    <link>https://www.sunging.cn/archives/java8之函数式接口</link>
                    <description>
                            <![CDATA[<p>Java 8 引入了函数式接口（Functional Interfaces）的概念：如果一个接口只定义了唯一一个抽象方法，那么这个接口就成为函数式接口。同时，Java 8中还引入了一个新的注解：@FunctionalInterface。可以把这个注解放在一个接口前，用于表示这个接口是一个函数式接口。这个注解是非必须的，只要接口只定义了唯一一个抽象方法，虚拟机就会自动判断出该接口为一个函数式接口，不过最好在定义的函数式接口上加上 @FunctionalInterface 注解。对于加了 @FunctionalInterface 注解的接口，Java编译器会在编译时对接口进行检查，如果发现该接口中定义的抽象方法不是一个，那么编译器会报错，这样就可以保证该接口的定义符合预期。</p><p>java.lang.Runnable就是一个典型的函数式接口：</p><pre><code>@FunctionalInterfacepublic interface Runnable {    void run();}</code></pre><p>另外，为了避免开发者创建大量的仅为 Lambda 表达式服务的函数式接口，Java 8 在 java.util.function 中增加了不少新的函数式通用接口。例如：</p><p>Function&lt;T,R&gt; :将 T 作为输入，返回 R 作为输出，他还包含了和其他函数组合的默认方法。</p><p>Predicate<T> ：将 T 作为输入，返回一个布尔值作为输出，该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑（与、或、非）。</p><p>Consumer<T> ：将 T 作为输入，不返回任何内容，表示在单个参数上的操作。</p><p>简单讲解一下Function&lt;T,R&gt;接口，它的定义如下：</p><pre><code>@FunctionalInterfacepublic interface Function {    R apply(T var1);        default  Function compose(Functionsuper V, ? extends T&gt; var1) {        Objects.requireNonNull(var1);        return (var2) -&gt; {            return this.apply(var1.apply(var2));        };    }        default  Function andThen(Functionsuper R, ? extends V&gt; var1) {        Objects.requireNonNull(var1);        return (var2) -&gt; {            return var1.apply(this.apply(var2));        };    }        static  Function identity() {        return (var0) -&gt; {            return var0;        };    }}</code></pre><p>可以看到Function接口中只有一个抽象方法R apply(T var1) 这个方法唯一一个待实现的方法（抽象方法），这个方法将Function对象应用到输入的参数上，然后返回计算结果。</p><p>default Function compose(Functionsuper V, ? extends T&gt; var1) 为一个默认方法，如不需要重写可以保留其默认实现。它返回一个先执行传入的函数对象的apply方法再执行当前函数对象的apply方法的函数对象。</p><p>default  Function andThen(Functionsuper R, ? extends V&gt; var1) 方法与它的上一个方法很相似，它返回一个先执行当前函数对象的apply方法再执行传入的函数对象的apply方法的函数对象。</p><p>static  Function identity() 为一个静态方法，它返回一个执行了apply()方法之后只会返回输入参数（即apply()方法中直接输出输入的数据）的函数对象。</p>]]>
                    </description>
                    <pubDate>Fri, 02 Feb 2018 22:50:17 CST</pubDate>
                </item>
    </channel>
</rss>