Jekyll2022-05-19T19:43:38+00:00https://ebnbin.github.io/feed.xmlBin’s BlogLeakCanary 2 初始化方式分析——ContentProvider 妙用2020-06-10T00:00:00+00:002020-06-10T00:00:00+00:00https://ebnbin.github.io/2020/06/10/leak-canary-content-provider<p>LeakCanary 2 是一次重大的版本升级。与旧版本相比,依赖与配置 LeakCanary 库的方式变得更加简单,只需要在 app 模块的 <code class="language-plaintext highlighter-rouge">build.gradle</code> 文件中添加:</p> <div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">dependencies</span> <span class="o">{</span> <span class="n">debugImplementation</span> <span class="s1">'com.squareup.leakcanary:leakcanary-android:2.3'</span> <span class="o">}</span> </code></pre></div></div> <p>官方文档也特别突出了这一点:</p> <blockquote> <p><strong>That’s it, there is no code change needed!</strong></p> </blockquote> <p>本篇文章结合 <a href="https://github.com/square/leakcanary/tree/v2.3">LeakCanary v2.3 源码</a>,分析其“无代码侵入”初始化库的实现方式,以及在自己的库中使用这一特性的注意事项。</p> <hr /> <h2 id="使用-contentprovider-初始化库">使用 <code class="language-plaintext highlighter-rouge">ContentProvider</code> 初始化库</h2> <p>我们可以在 <code class="language-plaintext highlighter-rouge">leakcanary-object-watcher-android</code> 模块的 <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> 文件中发现注册了一个 <code class="language-plaintext highlighter-rouge">provider</code>:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;provider</span> <span class="na">android:name=</span><span class="s">"leakcanary.internal.AppWatcherInstaller$MainProcess"</span> <span class="na">android:authorities=</span><span class="s">"${applicationId}.leakcanary-installer"</span> <span class="na">android:exported=</span><span class="s">"false"</span><span class="nt">/&gt;</span> </code></pre></div></div> <p>其源码如下:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** * Content providers are loaded before the application class is created. [AppWatcherInstaller] is * used to install [leakcanary.AppWatcher] on application start. */</span> <span class="k">internal</span> <span class="k">sealed</span> <span class="kd">class</span> <span class="nc">AppWatcherInstaller</span> <span class="p">:</span> <span class="nc">ContentProvider</span><span class="p">()</span> <span class="p">{</span> <span class="cm">/** * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process. */</span> <span class="k">internal</span> <span class="kd">class</span> <span class="nc">MainProcess</span> <span class="p">:</span> <span class="nc">AppWatcherInstaller</span><span class="p">()</span> <span class="cm">/** * When using the `leakcanary-android-process` artifact instead of `leakcanary-android`, * [LeakCanaryProcess] automatically sets up the LeakCanary code */</span> <span class="k">internal</span> <span class="kd">class</span> <span class="nc">LeakCanaryProcess</span> <span class="p">:</span> <span class="nc">AppWatcherInstaller</span><span class="p">()</span> <span class="p">{</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">():</span> <span class="nc">Boolean</span> <span class="p">{</span> <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">()</span> <span class="nc">AppWatcher</span><span class="p">.</span><span class="n">config</span> <span class="p">=</span> <span class="nc">AppWatcher</span><span class="p">.</span><span class="n">config</span><span class="p">.</span><span class="nf">copy</span><span class="p">(</span><span class="n">enabled</span> <span class="p">=</span> <span class="k">false</span><span class="p">)</span> <span class="k">return</span> <span class="k">true</span> <span class="p">}</span> <span class="p">}</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">():</span> <span class="nc">Boolean</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">application</span> <span class="p">=</span> <span class="n">context</span><span class="o">!!</span><span class="p">.</span><span class="n">applicationContext</span> <span class="k">as</span> <span class="nc">Application</span> <span class="nc">InternalAppWatcher</span><span class="p">.</span><span class="nf">install</span><span class="p">(</span><span class="n">application</span><span class="p">)</span> <span class="k">return</span> <span class="k">true</span> <span class="p">}</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">query</span><span class="p">(</span> <span class="n">uri</span><span class="p">:</span> <span class="nc">Uri</span><span class="p">,</span> <span class="n">strings</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;?,</span> <span class="n">s</span><span class="p">:</span> <span class="nc">String</span><span class="p">?,</span> <span class="n">strings1</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;?,</span> <span class="n">s1</span><span class="p">:</span> <span class="nc">String</span><span class="p">?</span> <span class="p">):</span> <span class="nc">Cursor</span><span class="p">?</span> <span class="p">{</span> <span class="k">return</span> <span class="k">null</span> <span class="p">}</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">getType</span><span class="p">(</span><span class="n">uri</span><span class="p">:</span> <span class="nc">Uri</span><span class="p">):</span> <span class="nc">String</span><span class="p">?</span> <span class="p">{</span> <span class="k">return</span> <span class="k">null</span> <span class="p">}</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">insert</span><span class="p">(</span> <span class="n">uri</span><span class="p">:</span> <span class="nc">Uri</span><span class="p">,</span> <span class="n">contentValues</span><span class="p">:</span> <span class="nc">ContentValues</span><span class="p">?</span> <span class="p">):</span> <span class="nc">Uri</span><span class="p">?</span> <span class="p">{</span> <span class="k">return</span> <span class="k">null</span> <span class="p">}</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">delete</span><span class="p">(</span> <span class="n">uri</span><span class="p">:</span> <span class="nc">Uri</span><span class="p">,</span> <span class="n">s</span><span class="p">:</span> <span class="nc">String</span><span class="p">?,</span> <span class="n">strings</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;?</span> <span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">0</span> <span class="p">}</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">update</span><span class="p">(</span> <span class="n">uri</span><span class="p">:</span> <span class="nc">Uri</span><span class="p">,</span> <span class="n">contentValues</span><span class="p">:</span> <span class="nc">ContentValues</span><span class="p">?,</span> <span class="n">s</span><span class="p">:</span> <span class="nc">String</span><span class="p">?,</span> <span class="n">strings</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;?</span> <span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">0</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">MainProcess</code> 是 <code class="language-plaintext highlighter-rouge">AppWatcherInstaller</code> 的一个内部类,继承了 <code class="language-plaintext highlighter-rouge">AppWatcherInstaller</code>,用于在主进程注册。<code class="language-plaintext highlighter-rouge">AppWatcherInstaller</code> 中还有一个 <code class="language-plaintext highlighter-rouge">LeakCanaryProcess</code> 内部类,同样继承了 <code class="language-plaintext highlighter-rouge">AppWatcherInstaller</code>,用于在 <code class="language-plaintext highlighter-rouge">:leakcanary</code> 进程注册。</p> <p><code class="language-plaintext highlighter-rouge">AppWatcherInstaller</code> 是一个 <code class="language-plaintext highlighter-rouge">ContentProvider</code>,却并没有任何 CRUD 操作,只在 <code class="language-plaintext highlighter-rouge">onCreate()</code> 中获取应用 <code class="language-plaintext highlighter-rouge">Application</code> 并在第 26 行执行了 LeakCanary 的初始化操作 <code class="language-plaintext highlighter-rouge">InternalAppWatcher.install(application)</code>。这相当于 LeakCanary 旧版本中需要我们手动调用的 <code class="language-plaintext highlighter-rouge">LeakCanary.install(Application)</code> 方法。</p> <p>如果打 log 可以发现,<code class="language-plaintext highlighter-rouge">ContentProvider</code> 的 <code class="language-plaintext highlighter-rouge">onCreate</code> 方法要早于 <code class="language-plaintext highlighter-rouge">Application</code> 的 <code class="language-plaintext highlighter-rouge">onCreate</code> 方法执行。相关方法的调用顺序是:</p> <ol> <li> <p><code class="language-plaintext highlighter-rouge">Application.attachBaseContext</code></p> </li> <li> <p><code class="language-plaintext highlighter-rouge">ContentProvider.onCreate</code></p> </li> <li> <p><code class="language-plaintext highlighter-rouge">Application.onCreate</code></p> </li> </ol> <p>因此在 <code class="language-plaintext highlighter-rouge">ContentProvider</code> 的 <code class="language-plaintext highlighter-rouge">onCreate</code> 中初始化库是可行的。LeakCanary 2 利用了这一特性完成了初始化,同时避免了代码侵入。</p> <p>不得不说,这并不是 <code class="language-plaintext highlighter-rouge">ContentProvider</code> 的“正确”打开方式,不过用起来,真香!</p> <hr /> <h2 id="初始化依赖问题">初始化依赖问题</h2> <p>上面的分析已经有很多文章做过了,不过在实践过程中发现一个小问题。</p> <p>我自己需要一个 base 库,上层 app 依赖这个 base 库。像 LeakCanary 这样的基础工具库可以放在 base 库中,不需要让每个使用库的 app 分别依赖。而我的 base 库也希望像 LeakCanary 2 一样,使用 <code class="language-plaintext highlighter-rouge">ContentProvider</code> 做初始化,于是乎我添加了一个 <code class="language-plaintext highlighter-rouge">BaseInstaller</code>:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** * 用于获取应用 [Application] 并初始化库. */</span> <span class="k">internal</span> <span class="kd">class</span> <span class="nc">BaseInstaller</span> <span class="p">:</span> <span class="nc">ContentProvider</span><span class="p">()</span> <span class="p">{</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">():</span> <span class="nc">Boolean</span> <span class="p">{</span> <span class="c1">// 自定义配置 LeakCanary.</span> <span class="nf">initLeakCanary</span><span class="p">()</span> <span class="c1">// 其他初始化操作.</span> <span class="c1">// ...</span> <span class="k">return</span> <span class="k">true</span> <span class="p">}</span> <span class="k">private</span> <span class="k">fun</span> <span class="nf">initLeakCanary</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// 不显示 LeakCanary 启动器图标.</span> <span class="nc">LeakCanary</span><span class="p">.</span><span class="nf">showLeakDisplayActivityLauncherIcon</span><span class="p">(</span><span class="k">false</span><span class="p">)</span> <span class="p">}</span> <span class="c1">// ...</span> <span class="p">}</span> </code></pre></div></div> <p>在我自己的 <code class="language-plaintext highlighter-rouge">BaseInstaller</code> 中我对 LeakCanary 做了一些额外的自定义配置工作。</p> <p>运行起来应用却崩溃了,异常信息是:</p> <pre><code class="language-logcat">Caused by: kotlin.UninitializedPropertyAccessException: lateinit property application has not been initialized at leakcanary.internal.InternalLeakCanary.setEnabledBlocking(InternalLeakCanary.kt:330) at leakcanary.LeakCanary.showLeakDisplayActivityLauncherIcon(LeakCanary.kt:337) </code></pre> <p>根据异常栈大概能猜到原因了,在 <code class="language-plaintext highlighter-rouge">BaseInstaller</code> 中配置 LeakCanary 时,LeakCanary 还没有完成初始化。</p> <p>在 <code class="language-plaintext highlighter-rouge">InternalLeakCanary</code> 中可以看到,<code class="language-plaintext highlighter-rouge">application</code> 是一个 <code class="language-plaintext highlighter-rouge">lateinit</code> 字段,在 <code class="language-plaintext highlighter-rouge">invoke(Application)</code> 方法中第一次赋值,<code class="language-plaintext highlighter-rouge">InternalLeakCanary</code> 实现了 <code class="language-plaintext highlighter-rouge">(Application) -&gt; Unit</code> 接口,在 <code class="language-plaintext highlighter-rouge">InternalAppWatcher</code> 的 <code class="language-plaintext highlighter-rouge">install(Application)</code> 方法中调用。</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="kd">object</span> <span class="nc">InternalLeakCanary</span> <span class="p">:</span> <span class="p">(</span><span class="nc">Application</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nc">Unit</span><span class="p">,</span> <span class="nc">OnObjectRetainedListener</span> <span class="p">{</span> <span class="c1">// ...</span> <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">application</span><span class="p">:</span> <span class="nc">Application</span> <span class="c1">// ...</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">invoke</span><span class="p">(</span><span class="n">application</span><span class="p">:</span> <span class="nc">Application</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="n">application</span> <span class="p">=</span> <span class="n">application</span> <span class="c1">// ...</span> <span class="p">}</span> <span class="c1">// ...</span> <span class="k">fun</span> <span class="nf">setEnabledBlocking</span><span class="p">(</span> <span class="n">componentClassName</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">enabled</span><span class="p">:</span> <span class="nc">Boolean</span> <span class="p">)</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">component</span> <span class="p">=</span> <span class="nc">ComponentName</span><span class="p">(</span><span class="n">application</span><span class="p">,</span> <span class="n">componentClassName</span><span class="p">)</span> <span class="c1">// ...</span> <span class="p">}</span> <span class="c1">// ...</span> <span class="p">}</span> </code></pre></div></div> <p>之所以抛“未初始化”异常是因为,我们的 <code class="language-plaintext highlighter-rouge">base</code> 库同样通过注册一个 <code class="language-plaintext highlighter-rouge">ContentProvider</code> 的方式实现初始化,而 <code class="language-plaintext highlighter-rouge">base</code> 库依赖了 LeakCanary,相当于在 app 的 <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> 中注册了两个 <code class="language-plaintext highlighter-rouge">ContentProvider</code>。根据依赖关系,<code class="language-plaintext highlighter-rouge">base</code> 库中的自定义 <code class="language-plaintext highlighter-rouge">ContentProvider</code> 会优先于 LeakCanary 中的初始化,因此在 <code class="language-plaintext highlighter-rouge">BaseInstaller</code> 中配置 LeakCanary 的时机过早。</p> <p>如何解决这一问题?<code class="language-plaintext highlighter-rouge">provider</code> 在 <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> 中注册时提供一条属性 <code class="language-plaintext highlighter-rouge">android:initOrder</code>,可以用来指定初始化顺序。</p> <p>我们可以先查看一下所有已注册的 <code class="language-plaintext highlighter-rouge">ContentProvider</code> 以及它们的 <code class="language-plaintext highlighter-rouge">initOrder</code>:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">context</span><span class="p">.</span><span class="n">packageManager</span> <span class="p">.</span><span class="nf">getPackageInfo</span><span class="p">(</span><span class="n">context</span><span class="p">.</span><span class="n">packageName</span><span class="p">,</span> <span class="nc">PackageManager</span><span class="p">.</span><span class="nc">GET_PROVIDERS</span><span class="p">)</span> <span class="p">.</span><span class="n">providers</span> <span class="p">.</span><span class="nf">forEach</span> <span class="p">{</span> <span class="nf">log</span><span class="p">(</span><span class="s">"${it.name}: ${it.initOrder}"</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>输出:</p> <pre><code class="language-logcat">D: *.BaseInstaller: 0 D: leakcanary.internal.LeakCanaryFileProvider: 0 D: leakcanary.internal.AppWatcherInstaller$MainProcess: 0 </code></pre> <p>可以看到,第一条是 <code class="language-plaintext highlighter-rouge">base</code> 库的 <code class="language-plaintext highlighter-rouge">BaseInstaller</code>,之后有 LeakCanary 的 <code class="language-plaintext highlighter-rouge">FileProvider</code> 以及我们上面分析的 <code class="language-plaintext highlighter-rouge">AppWatcherInstaller$MainProcess</code>。默认情况下 <code class="language-plaintext highlighter-rouge">provider</code> 的 <code class="language-plaintext highlighter-rouge">android:initOrder</code> 属性值为 0,数值越大越早执行初始化,因此为了保证 LeakCanary 优先初始化完成,我们只要为 <code class="language-plaintext highlighter-rouge">BaseInstaller</code> 指定一个小于 0 的数:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;provider</span> <span class="na">android:name=</span><span class="s">".BaseInstaller"</span> <span class="na">android:authorities=</span><span class="s">"..."</span> <span class="na">android:exported=</span><span class="s">"false"</span> <span class="na">android:initOrder=</span><span class="s">"-1"</span><span class="nt">/&gt;</span> </code></pre></div></div> <p>如果依赖了更多的使用 <code class="language-plaintext highlighter-rouge">ContentProvider</code> 的库并且存在初始化顺序问题,只需要保证被依赖的 <code class="language-plaintext highlighter-rouge">provider</code> 的 <code class="language-plaintext highlighter-rouge">android:initOrder</code> 大于调用者的值就可以了。</p> <hr /> <h2 id="小结">小结</h2> <ul> <li> <p><code class="language-plaintext highlighter-rouge">ContentProvider</code> 的初始化早于 <code class="language-plaintext highlighter-rouge">Application</code> 的 <code class="language-plaintext highlighter-rouge">onCreate</code> 且可以由系统自行注册调用,LeakCanary 2 使用了这一特性用来执行初始化操作。</p> </li> <li> <p>在使用自定义 <code class="language-plaintext highlighter-rouge">ContentProvider</code> 初始化其他第三方库时需要注意依赖关系,使用 <code class="language-plaintext highlighter-rouge">android:initOrder</code> 属性可以改变初始化顺序。</p> </li> </ul>LeakCanary 2 是一次重大的版本升级。与旧版本相比,依赖与配置 LeakCanary 库的方式变得更加简单,只需要在 app 模块的 build.gradle 文件中添加:Kotlin 高阶函数和 lambda 表达式2018-03-07T00:00:00+00:002018-03-07T00:00:00+00:00https://ebnbin.github.io/2018/03/07/kotlin-lambdas<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">button</span><span class="o">.</span><span class="na">setOnClickListener</span><span class="o">(</span><span class="k">new</span> <span class="nc">View</span><span class="o">.</span><span class="na">OnClickListener</span><span class="o">()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onClick</span><span class="o">(</span><span class="nc">View</span> <span class="n">v</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"clicked"</span><span class="o">);</span> <span class="o">}</span> <span class="o">});</span> </code></pre></div></div> <p>以上这段代码在 Java 8 中可以这么写:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">button</span><span class="o">.</span><span class="na">setOnClickListener</span><span class="o">(</span><span class="n">v</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"clicked"</span><span class="o">));</span> </code></pre></div></div> <p>在 Kotlin 中可以这么写:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">button</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"clicked"</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>如何理解这段代码,一步一步来简化。</p> <h5 id="1-匿名内部类">1. 匿名内部类</h5> <p>要创建一个继承自某类型的匿名类的对象,写法如下(这段代码相当于 Java 8 之前的匿名内部类写法):</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">button</span><span class="p">.</span><span class="nf">setOnClickListener</span><span class="p">(</span><span class="kd">object</span> <span class="err">: </span><span class="nc">View</span><span class="p">.</span><span class="nc">OnClickListener</span> <span class="p">{</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">onClick</span><span class="p">(</span><span class="n">v</span><span class="p">:</span> <span class="nc">View</span><span class="p">?)</span> <span class="p">{</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="nf">println</span><span class="p">(</span><span class="s">"clicked"</span><span class="p">)</span> <span class="err"> </span> <span class="p">}</span> <span class="p">})</span> </code></pre></div></div> <h5 id="2-使用-lambda-表达式">2. 使用 lambda 表达式</h5> <p>约定如下:</p> <ul> <li>lambda 表达式总是括在花括号中;</li> <li>其参数(如果有的话)在 -&gt; 之前声明;</li> <li>函数体(如果存在的话)在 -&gt; 后面。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">button</span><span class="p">.</span><span class="nf">setOnClickListener</span><span class="p">({</span> <span class="n">v</span><span class="p">:</span> <span class="nc">View</span><span class="p">?</span> <span class="p">-&gt;</span> <span class="nf">println</span><span class="p">(</span><span class="s">"clicked"</span><span class="p">)</span> <span class="p">})</span> </code></pre></div></div> <h5 id="3-省略参数类型">3. 省略参数类型</h5> <p>(参数类型可以省略)</p> <p>省略类型,形式参数 v 没有使用时可以简写为 <code class="language-plaintext highlighter-rouge">_</code>:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">button</span><span class="p">.</span><span class="nf">setOnClickListener</span><span class="p">({</span> <span class="n">_</span> <span class="p">-&gt;</span> <span class="nf">println</span><span class="p">(</span><span class="s">"clicked"</span><span class="p">)</span> <span class="p">})</span> </code></pre></div></div> <h5 id="4-省略参数">4. 省略参数</h5> <p>如果函数字面值只有一个参数, 那么它的声明可以省略(连同 -&gt;),其名称是 it:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">button</span><span class="p">.</span><span class="nf">setOnClickListener</span><span class="p">({</span> <span class="nf">println</span><span class="p">(</span><span class="s">"clicked"</span><span class="p">)</span> <span class="p">})</span> </code></pre></div></div> <h5 id="5-将花括号移出圆括号">5. 将花括号移出圆括号</h5> <p>在 Kotlin 中有一个约定,如果函数的最后一个参数是一个函数,并且你传递一个 lambda 表达式作为相应的参数,你可以在圆括号之外指定它。如果 lambda 是该调用的唯一参数,则调用中的圆括号可以完全省略:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">button</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"clicked"</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>一个 lambda 表达式或匿名函数是一个“函数字面值”,即一个未声明的函数,但立即做为表达式传递。接受一个函数作为参数的函数是一个函数。对于接受另一个函数作为参数的函数,我们必须为该参数指定函数类型。</p> <p><code class="language-plaintext highlighter-rouge">setOnClickListener</code> 的函数类型为</p> <p>比如:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">captureScreenshot</span><span class="p">(</span><span class="n">view</span><span class="p">:</span> <span class="nc">View</span><span class="p">,</span> <span class="n">viewToBitmap</span><span class="p">:</span> <span class="p">(</span><span class="nc">View</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nc">Bitmap</span><span class="p">)</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">header</span> <span class="p">=</span> <span class="nf">createHeader</span><span class="p">()</span> <span class="kd">val</span> <span class="py">content</span> <span class="p">=</span> <span class="nf">viewToBitmap</span><span class="p">(</span><span class="n">view</span><span class="p">)</span> <span class="kd">val</span> <span class="py">footer</span> <span class="p">=</span> <span class="nf">createFooter</span><span class="p">()</span> <span class="nf">saveBitmap</span><span class="p">(</span><span class="n">header</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="n">footer</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>其中 <code class="language-plaintext highlighter-rouge">(View) -&gt; Bitmap</code> 就是参数 <code class="language-plaintext highlighter-rouge">viewToBitmap</code> 的函数类型,它接受一个 <code class="language-plaintext highlighter-rouge">View</code> 返回一个 <code class="language-plaintext highlighter-rouge">Bitmap</code>,相当于:</p> <p>什么是 lambda?简单的说就是匿名函数,它是一个“函数字面值”,即一个未声明的函数,但立即做为表达式传递。比如:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">sum</span> <span class="p">=</span> <span class="p">{</span> <span class="n">x</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">-&gt;</span> <span class="n">x</span> <span class="p">+</span> <span class="n">y</span> <span class="p">}</span> <span class="c1">// 使用时:</span> <span class="kd">val</span> <span class="py">result</span> <span class="p">=</span> <span class="nf">sum</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1">// 或者:</span> <span class="kd">val</span> <span class="py">result</span> <span class="p">=</span> <span class="n">sum</span><span class="p">.</span><span class="nf">invoke</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> </code></pre></div></div> <p>它相当于:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">sum</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nc">Int</span><span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span> <span class="k">return</span> <span class="n">x</span> <span class="p">+</span> <span class="n">y</span> <span class="p">}</span> <span class="c1">// 使用时:</span> <span class="kd">val</span> <span class="py">result</span> <span class="p">=</span> <span class="nf">sum</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1">// 或者:</span> <span class="kd">val</span> <span class="py">result</span> <span class="p">=</span> <span class="o">::</span><span class="n">sum</span><span class="p">.</span><span class="nf">invoke</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> </code></pre></div></div> <p>标签</p> <p>Lambda 表达式的完整语法形式,即函数类型的字面值如下:</p>button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { System.out.println("clicked"); } });Kotlin 操作符重载2018-01-31T00:00:00+00:002018-01-31T00:00:00+00:00https://ebnbin.github.io/2018/01/31/kotlin-operator-overloading<p>在 Kotlin 中,我们可以用 <strong>约定的操作符</strong>,代替 <strong>调用代码中以特定的命名定义的函数</strong>,来实现 <strong>与之对应的操作</strong>。例如在类中定义了一个名为 <code class="language-plaintext highlighter-rouge">plus</code> 的特殊方法,就可以使用加法运算符 <code class="language-plaintext highlighter-rouge">+</code> 代替 <code class="language-plaintext highlighter-rouge">plus()</code> 的方法调用。由于你无法修改已有的接口定义,因此一般可以通过 <strong>扩展函数</strong> 来为现有的类增添新的 <strong>约定方法</strong>,从而使得 <strong>操作符重载</strong> 这一语法糖适应任何现有的 Java 类。</p> <h2 id="算术运算符">算术运算符</h2> <p>我们就从最简单直接的例子 <code class="language-plaintext highlighter-rouge">+</code> 这一类算术运算符开始。</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">Point</span><span class="p">(</span><span class="kd">val</span> <span class="py">x</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="kd">val</span> <span class="py">y</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">plus</span><span class="p">(</span><span class="n">other</span><span class="p">:</span> <span class="nc">Point</span><span class="p">)</span> <span class="p">=</span> <span class="nc">Point</span><span class="p">(</span><span class="n">x</span> <span class="p">+</span> <span class="n">other</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="p">+</span> <span class="n">other</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">plus</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">=</span> <span class="s">"toString: ${Point(x + value, y + value)}"</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;)</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">p1</span> <span class="p">=</span> <span class="nc">Point</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="kd">val</span> <span class="py">p2</span> <span class="p">=</span> <span class="nc">Point</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span> <span class="nf">println</span><span class="p">(</span><span class="n">p1</span> <span class="p">+</span> <span class="n">p2</span><span class="p">)</span> <span class="nf">println</span><span class="p">(</span><span class="n">p1</span> <span class="p">+</span> <span class="mi">3</span><span class="p">)</span> <span class="p">}</span> <span class="cm">/* Point(x=4, y=6) toString: Point(x=4, y=5) */</span> </code></pre></div></div> <ul> <li> <p><strong><code class="language-plaintext highlighter-rouge">operator</code> 修饰符是必须的</strong>,否则 <code class="language-plaintext highlighter-rouge">plus</code> 只是一个普通方法,不能通过 <code class="language-plaintext highlighter-rouge">+</code> 调用。</p> </li> <li> <p>操作符是有优先级的,比较 <code class="language-plaintext highlighter-rouge">*</code> 优先级高于 <code class="language-plaintext highlighter-rouge">+</code>,不论这个操作符应用于什么对象,这种优先级都是固定存在的。</p> </li> <li> <p><code class="language-plaintext highlighter-rouge">plus</code> 方法的参数类型是任意的,因此可以方法重载,但是 <strong>参数数量只能是 1</strong> ,因为 <code class="language-plaintext highlighter-rouge">+</code> 是一个二元操作符。<code class="language-plaintext highlighter-rouge">plus</code> 方法的返回值类型也是任意的。</p> </li> <li> <p>如果出现多个方法签名相同的 operator 扩展方法,根据 import 决定使用哪个一,例如:</p> </li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 第一个文件:</span> <span class="k">package</span> <span class="nn">package0</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nc">Point</span><span class="p">.</span><span class="nf">times</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">=</span> <span class="nc">Point</span><span class="p">(</span><span class="n">x</span> <span class="p">*</span> <span class="n">value</span><span class="p">,</span> <span class="n">y</span> <span class="p">*</span> <span class="n">value</span><span class="p">)</span> <span class="c1">// 第二个文件:</span> <span class="k">package</span> <span class="nn">package1</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nc">Point</span><span class="p">.</span><span class="nf">times</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">=</span> <span class="nc">Unit</span> <span class="c1">// Do nothing.</span> <span class="c1">// 使用第一个扩展操作符:</span> <span class="k">import</span> <span class="nn">package0.times</span> <span class="kd">val</span> <span class="py">newPoint</span> <span class="p">=</span> <span class="nc">Point</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="p">*</span> <span class="mi">3</span> </code></pre></div></div> <p>Kotlin 为一些基本类型预定义了一些操作符方法,我们平时常写的基本数据计算也可以翻译成调用这些操作符方法,比如 <code class="language-plaintext highlighter-rouge">(2 + 3) * 4</code> 可以翻译成 <code class="language-plaintext highlighter-rouge">2.plus(3).times(4)</code>,<code class="language-plaintext highlighter-rouge">2 + 3 * 4</code> 可以翻译成 <code class="language-plaintext highlighter-rouge">2.plus(3.times(4))</code>。根据扩展函数的语法,扩展函数无法覆盖与类已有的方法签名相同的方法,因此,不必担心随随便便给 <code class="language-plaintext highlighter-rouge">Int</code> 自定义一个 <code class="language-plaintext highlighter-rouge">plus</code> 扩展方法就能让 <code class="language-plaintext highlighter-rouge">1 + 1</code> 变得不等于 <code class="language-plaintext highlighter-rouge">2</code>。</p> <p>同时,所有操作符都针对基本类型做了优化,比如 <code class="language-plaintext highlighter-rouge">1 + 2 * 3</code>、<code class="language-plaintext highlighter-rouge">4 &lt; 5</code>,不会为它们引入函数调用的开销。</p> <p>所有可重载的算术运算符有:</p> <table> <thead> <tr> <th style="text-align: left">表达式 </th> <th style="text-align: left">翻译为</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">a + b</td> <td style="text-align: left">a.plus(b)</td> </tr> <tr> <td style="text-align: left">a - b</td> <td style="text-align: left">a.minus(b)</td> </tr> <tr> <td style="text-align: left">a * b</td> <td style="text-align: left">a.times(b)</td> </tr> <tr> <td style="text-align: left">a / b</td> <td style="text-align: left">a.div(b)</td> </tr> <tr> <td style="text-align: left">a % b</td> <td style="text-align: left">a.rem(b)、 <del>a.mod(b)</del> (在 Kotlin 1.1 中被弃用)</td> </tr> <tr> <td style="text-align: left">a..b</td> <td style="text-align: left">a.rangeTo(b)</td> </tr> </tbody> </table> <p>它们的优先级与普通的数字类型运算符优先级相同。其中 <code class="language-plaintext highlighter-rouge">rangeTo</code> 会在下面说明。</p> <h2 id="广义赋值操作符">广义赋值操作符</h2> <table> <thead> <tr> <th style="text-align: left">表达式 </th> <th style="text-align: left">翻译为</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">a += b</td> <td style="text-align: left">a.plusAssign(b)</td> </tr> <tr> <td style="text-align: left">a -= b</td> <td style="text-align: left">a.minusAssign(b)</td> </tr> <tr> <td style="text-align: left">a *= b</td> <td style="text-align: left">a.timesAssign(b)</td> </tr> <tr> <td style="text-align: left">a /= b</td> <td style="text-align: left">a.divAssign(b)</td> </tr> <tr> <td style="text-align: left">a %= b</td> <td style="text-align: left">a.remAssign(b)、 <del>a.modAssign(b)</del> (在 Kotlin 1.1 中被弃用)</td> </tr> </tbody> </table> <p>对于以上广义赋值操作符:</p> <ul> <li> <p>如果对应的二元算术运算符函数也 <strong>可用</strong> ,则报错。<code class="language-plaintext highlighter-rouge">plus</code> 对应 <code class="language-plaintext highlighter-rouge">plusAssign</code>。<code class="language-plaintext highlighter-rouge">minus</code>、<code class="language-plaintext highlighter-rouge">times</code> 等也类似。</p> </li> <li> <p>返回值类型必须为 <code class="language-plaintext highlighter-rouge">Unit</code>。</p> </li> <li> <p>如果执行 <code class="language-plaintext highlighter-rouge">a += b</code> 时 <code class="language-plaintext highlighter-rouge">plusAssign</code> 不存在,会尝试生成 <code class="language-plaintext highlighter-rouge">a = a + b</code>,其中的 <code class="language-plaintext highlighter-rouge">a + b</code> 使用的就是 <code class="language-plaintext highlighter-rouge">plus</code> 操作符方法,相当于调用 <code class="language-plaintext highlighter-rouge">a = a.plus(b)</code>。并且此时会 <strong>要求 <code class="language-plaintext highlighter-rouge">a + b</code> 的 <code class="language-plaintext highlighter-rouge">plus</code> 方法的返回值类型必须与 <code class="language-plaintext highlighter-rouge">a</code> 类型一致</strong>(如果单独使用 <code class="language-plaintext highlighter-rouge">a + b</code> 不做此要求)。</p> </li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">Size</span><span class="p">(</span><span class="kd">var</span> <span class="py">width</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span><span class="p">,</span> <span class="kd">var</span> <span class="py">height</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">plus</span><span class="p">(</span><span class="n">other</span><span class="p">:</span> <span class="nc">Size</span><span class="p">):</span> <span class="nc">Size</span> <span class="p">{</span> <span class="k">return</span> <span class="nc">Size</span><span class="p">(</span><span class="n">width</span> <span class="p">+</span> <span class="n">other</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span> <span class="p">+</span> <span class="n">other</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> <span class="p">}</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">plusAssign</span><span class="p">(</span><span class="n">other</span><span class="p">:</span> <span class="nc">Size</span><span class="p">)</span> <span class="p">{</span> <span class="n">width</span> <span class="p">+=</span> <span class="n">other</span><span class="p">.</span><span class="n">width</span> <span class="n">height</span> <span class="p">+=</span> <span class="n">other</span><span class="p">.</span><span class="n">height</span> <span class="p">}</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;)</span> <span class="p">{</span> <span class="c1">//   var s1 = Size(1, 2) // 如果这么写,执行 += 时会报错.</span> <span class="err"> </span> <span class="kd">val</span> <span class="py">s1</span> <span class="p">=</span> <span class="nc">Size</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="kd">val</span> <span class="py">s2</span> <span class="p">=</span> <span class="nc">Size</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span> <span class="n">s1</span> <span class="p">+=</span> <span class="n">s2</span> <span class="p">}</span> </code></pre></div></div> <p>我们使用这个例子来理解:为什么使用 <code class="language-plaintext highlighter-rouge">var</code> 定义的 <code class="language-plaintext highlighter-rouge">s1</code> 会导致 <code class="language-plaintext highlighter-rouge">+=</code> 报错呢?因为理论上,执行 <code class="language-plaintext highlighter-rouge">+=</code> 时,既可以调用 <code class="language-plaintext highlighter-rouge">s1 = s1 + s2</code>,也就是 <code class="language-plaintext highlighter-rouge">s1 = s1.plus(s2)</code>,又可以调用 <code class="language-plaintext highlighter-rouge">s1.plusAssign(s2)</code>,都符合操作符重载约定,这样就会产生歧义,而如果使用 <code class="language-plaintext highlighter-rouge">val</code> 定义 <code class="language-plaintext highlighter-rouge">s1</code>,则只可能执行 <code class="language-plaintext highlighter-rouge">s1.plusAssign(s2)</code>,因为 <code class="language-plaintext highlighter-rouge">s1</code> 不可被重新赋值,因此 <code class="language-plaintext highlighter-rouge">s1 = s1 + s2</code> 这样的语法是出错的,永远不能能调用,那么调用 <code class="language-plaintext highlighter-rouge">s1 += s2</code> 就不会产生歧义了。</p> <p>既然编译器会帮我把 <code class="language-plaintext highlighter-rouge">a += b</code> 解释成 <code class="language-plaintext highlighter-rouge">a = a + b</code>,那是不是意味着我只需要 <code class="language-plaintext highlighter-rouge">plus</code> 永远不需要 <code class="language-plaintext highlighter-rouge">plusAssign</code> 了呢?比较好的实践方式是:</p> <ul> <li> <p><code class="language-plaintext highlighter-rouge">+</code> (plus) 始终返回一个新的对象</p> </li> <li> <p><code class="language-plaintext highlighter-rouge">+=</code> (plusAssign) 用于内容可变的类型,修改自身的内容。</p> </li> </ul> <p>Kotlin 标准库中就是这么实现的:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;)</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">list</span> <span class="p">=</span> <span class="nf">arrayListOf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="err"> </span> <span class="n">list</span> <span class="p">+=</span> <span class="mi">3</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="c1">// 添加元素到自身集合, 没有新的对象被创建, 调用的是 add 方法.</span> <span class="err"> </span> <span class="kd">val</span> <span class="py">newList</span> <span class="p">=</span> <span class="n">list</span> <span class="p">+</span> <span class="mi">4</span> <span class="c1">// 创建一个新的 ArrayList, 添加自身元素和新元素并返回新的 ArrayList.</span> <span class="p">}</span> </code></pre></div></div> <h2 id="in">in</h2> <table> <thead> <tr> <th style="text-align: left">表达式 </th> <th style="text-align: left">翻译为</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">a in b</td> <td style="text-align: left">b.contains(a)</td> </tr> <tr> <td style="text-align: left">a !in b</td> <td style="text-align: left">!b.contains(a)</td> </tr> </tbody> </table> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">println</span><span class="p">(</span><span class="s">"hello"</span> <span class="k">in</span> <span class="nf">arrayListOf</span><span class="p">(</span><span class="s">"hello"</span><span class="p">,</span> <span class="s">", "</span><span class="p">,</span> <span class="s">"world"</span><span class="p">))</span> <span class="cm">/* true */</span> </code></pre></div></div> <p><strong>在 <code class="language-plaintext highlighter-rouge">for</code> 循环中使用 <code class="language-plaintext highlighter-rouge">in</code> 操作符会执行迭代操作,<code class="language-plaintext highlighter-rouge">for(x in list) { /* 遍历 */ }</code> 将被转换成 <code class="language-plaintext highlighter-rouge">list.iterator()</code> 的调用,然后在上面重复调用<code class="language-plaintext highlighter-rouge">hasNext</code> 和 <code class="language-plaintext highlighter-rouge">next</code> 方法。</strong></p> <h2 id="rangeto">rangeTo</h2> <p><code class="language-plaintext highlighter-rouge">rangeTo</code> 用于创建一个区间。例如 <code class="language-plaintext highlighter-rouge">1..10</code> 也就是 <code class="language-plaintext highlighter-rouge">1.rangeTo(10)</code> 代表了从 <code class="language-plaintext highlighter-rouge">1</code> 到 <code class="language-plaintext highlighter-rouge">10</code> 这 10 个数字,<code class="language-plaintext highlighter-rouge">Int.rangeTo</code> 方法返回一个 <code class="language-plaintext highlighter-rouge">IntRange</code> 对象,<code class="language-plaintext highlighter-rouge">IntRange</code> 类定义如下:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** * A range of values of type `Int`. */</span> <span class="k">public</span> <span class="kd">class</span> <span class="nc">IntRange</span><span class="p">(</span><span class="n">start</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">endInclusive</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">:</span> <span class="nc">IntProgression</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">endInclusive</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="nc">ClosedRange</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">&gt;</span> <span class="p">{</span> <span class="k">override</span> <span class="kd">val</span> <span class="py">start</span><span class="p">:</span> <span class="nc">Int</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="n">first</span> <span class="k">override</span> <span class="kd">val</span> <span class="py">endInclusive</span><span class="p">:</span> <span class="nc">Int</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="n">last</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">contains</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">Int</span><span class="p">):</span> <span class="nc">Boolean</span> <span class="p">=</span> <span class="n">first</span> <span class="p">&lt;=</span> <span class="n">value</span> <span class="p">&amp;&amp;</span> <span class="n">value</span> <span class="p">&lt;=</span> <span class="n">last</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">isEmpty</span><span class="p">():</span> <span class="nc">Boolean</span> <span class="p">=</span> <span class="n">first</span> <span class="p">&gt;</span> <span class="n">last</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">equals</span><span class="p">(</span><span class="n">other</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?):</span> <span class="nc">Boolean</span> <span class="p">=</span> <span class="n">other</span> <span class="k">is</span> <span class="nc">IntRange</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="nf">isEmpty</span><span class="p">()</span> <span class="p">&amp;&amp;</span> <span class="n">other</span><span class="p">.</span><span class="nf">isEmpty</span><span class="p">()</span> <span class="p">||</span> <span class="n">first</span> <span class="p">==</span> <span class="n">other</span><span class="p">.</span><span class="n">first</span> <span class="p">&amp;&amp;</span> <span class="n">last</span> <span class="p">==</span> <span class="n">other</span><span class="p">.</span><span class="n">last</span><span class="p">)</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">hashCode</span><span class="p">():</span> <span class="nc">Int</span> <span class="p">=</span> <span class="k">if</span> <span class="p">(</span><span class="nf">isEmpty</span><span class="p">())</span> <span class="p">-</span><span class="mi">1</span> <span class="k">else</span> <span class="p">(</span><span class="mi">31</span> <span class="p">*</span> <span class="n">first</span> <span class="p">+</span> <span class="n">last</span><span class="p">)</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">toString</span><span class="p">():</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">"$first..$last"</span> <span class="k">companion</span> <span class="k">object</span> <span class="p">{</span> <span class="cm">/** An empty range of values of type Int. */</span> <span class="k">public</span> <span class="kd">val</span> <span class="py">EMPTY</span><span class="p">:</span> <span class="nc">IntRange</span> <span class="p">=</span> <span class="nc">IntRange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>它的基类 <code class="language-plaintext highlighter-rouge">IntProgression</code> 实现了 <code class="language-plaintext highlighter-rouge">Iterable</code> 接口,因此 <code class="language-plaintext highlighter-rouge">1..10</code> 可以用来迭代:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="n">index</span> <span class="k">in</span> <span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 遍历 1 到 10, 包括 1 和 10.</span> <span class="p">}</span> </code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">IntRange</code> 还实现了接口 <code class="language-plaintext highlighter-rouge">ClosedRange</code> ,可以用来判断某元素是否属于该区间。</p> <p>Kotlin 为 <code class="language-plaintext highlighter-rouge">Comparable</code> 定义了扩展函数 <code class="language-plaintext highlighter-rouge">rangeTo</code>:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** * Creates a range from this [Comparable] value to the specified [that] value. * * This value needs to be smaller than [that] value, otherwise the returned range will be empty. * @sample samples.ranges.Ranges.rangeFromComparable */</span> <span class="k">public</span> <span class="k">operator</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">:</span> <span class="nc">Comparable</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;&gt;</span> <span class="nc">T</span><span class="p">.</span><span class="nf">rangeTo</span><span class="p">(</span><span class="n">that</span><span class="p">:</span> <span class="nc">T</span><span class="p">):</span> <span class="nc">ClosedRange</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">=</span> <span class="nc">ComparableRange</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">that</span><span class="p">)</span> </code></pre></div></div> <p>因此所有的 <code class="language-plaintext highlighter-rouge">Comparable</code> 对象都可以使用 <code class="language-plaintext highlighter-rouge">..</code> 区间操作符,例如:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;)</span> <span class="p">{</span> <span class="err"> </span> <span class="kd">val</span> <span class="py">c1</span> <span class="p">=</span> <span class="nc">Calendar</span><span class="p">.</span><span class="nf">getInstance</span><span class="p">()</span> <span class="c1">// 代表今天.</span> <span class="err"> </span> <span class="kd">val</span> <span class="py">c2</span> <span class="p">=</span> <span class="nc">Calendar</span><span class="p">.</span><span class="nf">getInstance</span><span class="p">()</span> <span class="err"> </span> <span class="n">c2</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Calendar</span><span class="p">.</span><span class="nc">DATE</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="c1">// 代表 10 天后.</span> <span class="err"> </span> <span class="kd">val</span> <span class="py">c3</span> <span class="p">=</span> <span class="nc">Calendar</span><span class="p">.</span><span class="nf">getInstance</span><span class="p">()</span> <span class="n">c3</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Calendar</span><span class="p">.</span><span class="nc">DATE</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span> <span class="c1">// 代表 3 天后.</span> <span class="kd">val</span> <span class="py">c4</span> <span class="p">=</span> <span class="nc">Calendar</span><span class="p">.</span><span class="nf">getInstance</span><span class="p">()</span> <span class="n">c4</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Calendar</span><span class="p">.</span><span class="nc">DATE</span><span class="p">,</span> <span class="mi">13</span><span class="p">)</span> <span class="c1">// 代表 13 天后.</span> <span class="err"> </span> <span class="c1">// 判断某日期是否在某两个日期范围内.</span> <span class="err"> </span> <span class="nf">println</span><span class="p">(</span><span class="n">c3</span> <span class="k">in</span> <span class="n">c1</span><span class="o">..</span><span class="n">c2</span><span class="p">)</span> <span class="nf">println</span><span class="p">(</span><span class="n">c4</span> <span class="k">in</span> <span class="n">c1</span><span class="o">..</span><span class="n">c2</span><span class="p">)</span> <span class="p">}</span> <span class="cm">/* true false */</span> </code></pre></div></div> <h2 id="一元前缀操作符">一元前缀操作符</h2> <table> <thead> <tr> <th style="text-align: left">表达式 </th> <th style="text-align: left">翻译为</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">+a</td> <td style="text-align: left">a.unaryPlus()</td> </tr> <tr> <td style="text-align: left">-a</td> <td style="text-align: left">a.unaryMinus()</td> </tr> <tr> <td style="text-align: left">!a</td> <td style="text-align: left">a.not()</td> </tr> </tbody> </table> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">Point</span><span class="p">(</span><span class="kd">val</span> <span class="py">x</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="kd">val</span> <span class="py">y</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nc">Point</span><span class="p">.</span><span class="nf">unaryMinus</span><span class="p">()</span> <span class="p">=</span> <span class="nc">Point</span><span class="p">(-</span><span class="n">x</span><span class="p">,</span> <span class="p">-</span><span class="n">y</span><span class="p">)</span> <span class="kd">val</span> <span class="py">point</span> <span class="p">=</span> <span class="nc">Point</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> <span class="nf">println</span><span class="p">(-</span><span class="n">point</span><span class="p">)</span> <span class="cm">/* Point(x=-10, y=-20) */</span> </code></pre></div></div> <h2 id="递增与递减">递增与递减</h2> <table> <thead> <tr> <th style="text-align: left">表达式 </th> <th style="text-align: left">翻译为</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">a++</td> <td style="text-align: left">a.inc()</td> </tr> <tr> <td style="text-align: left">a–</td> <td style="text-align: left">a.dec()</td> </tr> </tbody> </table> <p>编译器自动支持与普通数字类型的前缀、后缀自增运算符相同的语义。例如后缀运算会先返回变量的值,然后才执行 <code class="language-plaintext highlighter-rouge">++</code> 操作。</p> <h2 id="索引访问操作符">索引访问操作符</h2> <table> <thead> <tr> <th style="text-align: left">表达式 </th> <th style="text-align: left">翻译为</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">a[i]</td> <td style="text-align: left">a.get(i)</td> </tr> <tr> <td style="text-align: left">a[i, j]</td> <td style="text-align: left">a.get(i, j)</td> </tr> <tr> <td style="text-align: left">a[i_1, ……, i_n]</td> <td style="text-align: left">a.get(i_1, ……, i_n)</td> </tr> <tr> <td style="text-align: left">a[i] = b</td> <td style="text-align: left">a.set(i, b)</td> </tr> <tr> <td style="text-align: left">a[i, j] = b</td> <td style="text-align: left">a.set(i, j, b)</td> </tr> <tr> <td style="text-align: left">a[i_1, ……, i_n] = b</td> <td style="text-align: left">a.set(i_1, ……, i_n, b)</td> </tr> </tbody> </table> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Suppress</span><span class="p">(</span><span class="s">"IMPLICIT_CAST_TO_ANY"</span><span class="p">,</span> <span class="s">"UNCHECKED_CAST"</span><span class="p">)</span> <span class="k">operator</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nc">SharedPreferences</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">defValue</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">=</span> <span class="k">when</span> <span class="p">(</span><span class="n">defValue</span><span class="p">)</span> <span class="p">{</span> <span class="k">is</span> <span class="nc">String</span> <span class="p">-&gt;</span> <span class="nf">getString</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Int</span> <span class="p">-&gt;</span> <span class="nf">getInt</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Long</span> <span class="p">-&gt;</span> <span class="nf">getLong</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Float</span> <span class="p">-&gt;</span> <span class="nf">getFloat</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Boolean</span> <span class="p">-&gt;</span> <span class="nf">getBoolean</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">else</span> <span class="p">-&gt;</span> <span class="k">throw</span> <span class="nc">RuntimeException</span><span class="p">()</span> <span class="p">}</span> <span class="k">as</span> <span class="nc">T</span> <span class="nd">@SuppressLint</span><span class="p">(</span><span class="s">"CommitPrefEdits"</span><span class="p">)</span> <span class="k">operator</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nc">SharedPreferences</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">=</span> <span class="nf">with</span><span class="p">(</span><span class="nf">edit</span><span class="p">())</span> <span class="p">{</span> <span class="k">when</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="k">is</span> <span class="nc">String</span> <span class="p">-&gt;</span> <span class="nf">putString</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Int</span> <span class="p">-&gt;</span> <span class="nf">putInt</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Long</span> <span class="p">-&gt;</span> <span class="nf">putLong</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Float</span> <span class="p">-&gt;</span> <span class="nf">putFloat</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Boolean</span> <span class="p">-&gt;</span> <span class="nf">putBoolean</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">else</span> <span class="p">-&gt;</span> <span class="k">throw</span> <span class="nc">RuntimeException</span><span class="p">()</span> <span class="p">}.</span><span class="nf">apply</span><span class="p">()</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;)</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">version</span> <span class="p">=</span> <span class="n">sp</span><span class="p">[</span><span class="s">"key_version"</span><span class="p">,</span> <span class="mi">47</span><span class="p">]</span> <span class="c1">// 读 sp.</span> <span class="n">sp</span><span class="p">[</span><span class="s">"key_version"</span><span class="p">]</span> <span class="p">=</span> <span class="mi">48</span> <span class="c1">// 写 sp.</span> <span class="p">}</span> </code></pre></div></div> <h2 id="调用操作符">调用操作符</h2> <table> <thead> <tr> <th style="text-align: left">表达式 </th> <th style="text-align: left">翻译为</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">a()</td> <td style="text-align: left">a.invoke()</td> </tr> <tr> <td style="text-align: left">a(i)</td> <td style="text-align: left">a.invoke(i)</td> </tr> <tr> <td style="text-align: left">a(i, j)</td> <td style="text-align: left">a.invoke(i, j)</td> </tr> <tr> <td style="text-align: left">a(i_1, ……, i_n)</td> <td style="text-align: left">a.invoke(i_1, ……, i_n)</td> </tr> </tbody> </table> <h2 id="相等与不等操作符">相等与不等操作符</h2> <table> <thead> <tr> <th style="text-align: left">表达式 </th> <th style="text-align: left">翻译为</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">a == b</td> <td style="text-align: left">a?.equals(b) ?: (b === null)</td> </tr> <tr> <td style="text-align: left">a != b</td> <td style="text-align: left">!(a?.equals(b) ?: (b === null))</td> </tr> </tbody> </table> <p>这在 <code class="language-plaintext highlighter-rouge">Any</code> 中被定义。<strong>Java 的 <code class="language-plaintext highlighter-rouge">a.equals(b)</code> 相当于 Koltin 的 <code class="language-plaintext highlighter-rouge">a == b</code>,Java 的 <code class="language-plaintext highlighter-rouge">a == b</code> 相当于 Kotlin 的 <code class="language-plaintext highlighter-rouge">a === b</code>(同一性检查)</strong>。要自定义 <code class="language-plaintext highlighter-rouge">==</code> 操作符其实就是覆写 <code class="language-plaintext highlighter-rouge">equals</code> 方法。Kotlin 中 <code class="language-plaintext highlighter-rouge">===</code> 不可被重载。</p> <h2 id="比较操作符">比较操作符</h2> <table> <thead> <tr> <th style="text-align: left">表达式 </th> <th style="text-align: left">翻译为</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">a &gt; b</td> <td style="text-align: left">a.compareTo(b) &gt; 0</td> </tr> <tr> <td style="text-align: left">a &lt; b</td> <td style="text-align: left">a.compareTo(b) &lt; 0</td> </tr> <tr> <td style="text-align: left">a &gt;= b</td> <td style="text-align: left">a.compareTo(b) &gt;= 0</td> </tr> <tr> <td style="text-align: left">a &lt;= b</td> <td style="text-align: left">a.compareTo(b) &lt;= 0</td> </tr> </tbody> </table> <p>要求 <code class="language-plaintext highlighter-rouge">compareTo</code> <strong>返回值类型必须为 <code class="language-plaintext highlighter-rouge">Int</code></strong> ,这与 <code class="language-plaintext highlighter-rouge">Comparable</code> 接口保持一致。</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">Movie</span><span class="p">(</span><span class="kd">val</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">val</span> <span class="py">score</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="kd">val</span> <span class="py">date</span><span class="p">:</span> <span class="nc">Date</span><span class="p">,</span> <span class="kd">val</span> <span class="py">other</span><span class="p">:</span> <span class="nc">Any</span> <span class="p">=</span> <span class="nc">Any</span><span class="p">())</span> <span class="p">:</span> <span class="nc">Comparable</span><span class="p">&lt;</span><span class="nc">Movie</span><span class="p">&gt;</span> <span class="p">{</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">compareTo</span><span class="p">(</span><span class="n">other</span><span class="p">:</span> <span class="nc">Movie</span><span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span><span class="k">return</span> <span class="nf">compareValuesBy</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">other</span><span class="p">,</span> <span class="nc">Movie</span><span class="o">::</span><span class="n">score</span><span class="p">,</span> <span class="nc">Movie</span><span class="o">::</span><span class="n">date</span><span class="p">,</span> <span class="nc">Movie</span><span class="o">::</span><span class="n">name</span><span class="p">)</span> <span class="c1">// 如果将 Movie::other 也用作比较会报错, 因为 other 不是 Comparable 类型的。</span> <span class="err"> </span> <span class="err"> </span><span class="p">}</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;)</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">df</span> <span class="p">=</span> <span class="nc">SimpleDateFormat</span><span class="p">(</span><span class="s">"yyyy-MM-dd"</span><span class="p">,</span> <span class="nc">Locale</span><span class="p">.</span><span class="nf">getDefault</span><span class="p">())</span> <span class="kd">val</span> <span class="py">movie0</span> <span class="p">=</span> <span class="nc">Movie</span><span class="p">(</span><span class="s">"马戏之王"</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="n">df</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s">"2018-01-31"</span><span class="p">))</span> <span class="kd">val</span> <span class="py">movie1</span> <span class="p">=</span> <span class="nc">Movie</span><span class="p">(</span><span class="s">"神秘巨星"</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="n">df</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s">"2018-01-01"</span><span class="p">))</span> <span class="kd">val</span> <span class="py">movie2</span> <span class="p">=</span> <span class="nc">Movie</span><span class="p">(</span><span class="s">"移动迷宫"</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="n">df</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s">"2018-01-02"</span><span class="p">))</span> <span class="nf">println</span><span class="p">(</span><span class="n">movie0</span> <span class="p">&lt;</span> <span class="n">movie1</span><span class="p">)</span> <span class="nf">println</span><span class="p">(</span><span class="n">movie1</span> <span class="p">&lt;</span> <span class="n">movie2</span><span class="p">)</span> <span class="p">}</span> <span class="cm">/* false true */</span> </code></pre></div></div> <p>其中的 <code class="language-plaintext highlighter-rouge">compareValuesBy</code> 方法如下:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** * Compares two values using the specified functions [selectors] to calculate the result of the comparison. * The functions are called sequentially, receive the given values [a] and [b] and return [Comparable] * objects. As soon as the [Comparable] instances returned by a function for [a] and [b] values do not * compare as equal, the result of that comparison is returned. * * @sample samples.comparisons.Comparisons.compareValuesByWithSelectors */</span> <span class="k">public</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">compareValuesBy</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nc">T</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nc">T</span><span class="p">,</span> <span class="k">vararg</span> <span class="n">selectors</span><span class="p">:</span> <span class="p">(</span><span class="nc">T</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nc">Comparable</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;?):</span> <span class="nc">Int</span> <span class="p">{</span> <span class="nf">require</span><span class="p">(</span><span class="n">selectors</span><span class="p">.</span><span class="n">size</span> <span class="p">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="nf">compareValuesByImpl</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">selectors</span><span class="p">)</span> <span class="p">}</span> <span class="k">private</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">compareValuesByImpl</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nc">T</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nc">T</span><span class="p">,</span> <span class="n">selectors</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="k">out</span> <span class="err">(</span><span class="nc">T</span><span class="err">)-</span><span class="p">&gt;</span><span class="nc">Comparable</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;?&gt;):</span> <span class="nc">Int</span> <span class="p">{</span> <span class="k">for</span> <span class="p">(</span><span class="n">fn</span> <span class="k">in</span> <span class="n">selectors</span><span class="p">)</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">v1</span> <span class="p">=</span> <span class="nf">fn</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="kd">val</span> <span class="py">v2</span> <span class="p">=</span> <span class="nf">fn</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="kd">val</span> <span class="py">diff</span> <span class="p">=</span> <span class="nf">compareValues</span><span class="p">(</span><span class="n">v1</span><span class="p">,</span> <span class="n">v2</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="n">diff</span> <span class="p">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">diff</span> <span class="p">}</span> <span class="k">return</span> <span class="mi">0</span> <span class="p">}</span> </code></pre></div></div> <p>我们定义一个 <code class="language-plaintext highlighter-rouge">Movie</code> 类,它实现了 <code class="language-plaintext highlighter-rouge">Comparable</code> 接口,在比较时,希望按照 <code class="language-plaintext highlighter-rouge">评分</code> 、 <code class="language-plaintext highlighter-rouge">上映日期</code> 、 <code class="language-plaintext highlighter-rouge">电影名称</code> 的优先级顺序排序。可以简单的使用比较操作符对 <code class="language-plaintext highlighter-rouge">Movie</code> 对象进行“大小比较”。</p> <h2 id="操作符函数与-java">操作符函数与 Java</h2> <ul> <li> <p>Java 中调用 Kotlin 中的操作符方法,就跟调用普通方法一样,你不能期望在 Java 中写 <code class="language-plaintext highlighter-rouge">new Point(1, 2) + new Point(3, 4)</code> 这样的语法,只能乖乖调用 <code class="language-plaintext highlighter-rouge">new Point(1, 2).plus(new Point(3, 4))</code>。</p> </li> <li> <p>反之,Kotlin 中调用 Java 代码却可以同 Kotlin 中自定义操作符方法一样方便。只要一个类提供了满足操作符方法签名的方法,哪怕它只是一个普通方法,不需要加 <code class="language-plaintext highlighter-rouge">operator</code> 修饰符(Java 中也没有这个修饰符),就可以在 Kotlin 中以操作符的方式调用。例如:<code class="language-plaintext highlighter-rouge">arrayList[0]</code> 相当于 Java 中 <code class="language-plaintext highlighter-rouge">arrayList.get(0)</code>,尽管这个 <code class="language-plaintext highlighter-rouge">get</code> 方法是 Java 中定义的。又比如所有实现了 <code class="language-plaintext highlighter-rouge">Comparable</code> 的类实例都可以使用比较操作符 <code class="language-plaintext highlighter-rouge">&gt;</code>、<code class="language-plaintext highlighter-rouge">&lt;</code> 等进行比较。</p> </li> <li> <p><strong>Java 中的位运算符在 Kotlin 中是没有的</strong> ,它们只能使用普通方法加中缀表达式使用,只能用于 <code class="language-plaintext highlighter-rouge">Int</code> 和 <code class="language-plaintext highlighter-rouge">Long</code>,对应关系如下:</p> </li> </ul> <table> <thead> <tr> <th style="text-align: left">Java 中  </th> <th style="text-align: left">Kotlin 中</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">« 有符号左移</td> <td style="text-align: left">shl(bits)</td> </tr> <tr> <td style="text-align: left">» 有符号右移</td> <td style="text-align: left">shr(bits)</td> </tr> <tr> <td style="text-align: left">»&gt; 无符号右移</td> <td style="text-align: left">ushr(bits)</td> </tr> <tr> <td style="text-align: left">&amp; 与</td> <td style="text-align: left">and(bits)</td> </tr> <tr> <td style="text-align: left">| 或</td> <td style="text-align: left">or(bits)</td> </tr> <tr> <td style="text-align: left">^ 异或</td> <td style="text-align: left">xor(bits)</td> </tr> <tr> <td style="text-align: left">! 非</td> <td style="text-align: left">inv()</td> </tr> </tbody> </table> <h2 id="操作符重载与属性委托中缀调用">操作符重载与属性委托、中缀调用</h2> <p>我们在使用委托属性时也用过 <code class="language-plaintext highlighter-rouge">operator</code> 修饰符:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Delegate</span> <span class="p">{</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">getValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;):</span> <span class="nc">String</span> <span class="p">{</span> <span class="c1">//...</span> <span class="p">}</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">setValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span> <span class="c1">//...</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>符合这样方法签名的 <code class="language-plaintext highlighter-rouge">getValue</code> 、 <code class="language-plaintext highlighter-rouge">setValue</code> 也是操作符函数,用于委托属性的 getter 和 setter。</p> <p>可以看出,操作符重载并不是一定要用如 <code class="language-plaintext highlighter-rouge">*</code> 、 <code class="language-plaintext highlighter-rouge">+</code> 、 <code class="language-plaintext highlighter-rouge">&lt;</code> 这样的符号来表示的,比如之前的 <code class="language-plaintext highlighter-rouge">in</code> 操作符,这里的 <code class="language-plaintext highlighter-rouge">getter</code> 、 <code class="language-plaintext highlighter-rouge">setter</code>。</p> <p>除了以上这些标准的可被重载的操作符外,我们也可以通过中缀函数的调用来模拟自定义中缀操作符,实现形如 <code class="language-plaintext highlighter-rouge">a in list</code> 这样的语法。</p>在 Kotlin 中,我们可以用 约定的操作符,代替 调用代码中以特定的命名定义的函数,来实现 与之对应的操作。例如在类中定义了一个名为 plus 的特殊方法,就可以使用加法运算符 + 代替 plus() 的方法调用。由于你无法修改已有的接口定义,因此一般可以通过 扩展函数 来为现有的类增添新的 约定方法,从而使得 操作符重载 这一语法糖适应任何现有的 Java 类。Kotlin 函数2017-12-13T00:00:00+00:002017-12-13T00:00:00+00:00https://ebnbin.github.io/2017/12/13/kotlin-functions<h1 id="函数默认参数和命名参数">函数默认参数和命名参数</h1> <ul> <li>默认参数扩展函数调用方式。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">setPadding</span><span class="p">(</span> <span class="n">left</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">left</span><span class="p">,</span> <span class="n">top</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">top</span><span class="p">,</span> <span class="n">right</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">right</span><span class="p">,</span> <span class="n">bottom</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">bottom</span><span class="p">)</span> <span class="p">{}</span> <span class="nf">setPadding</span><span class="p">()</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="c1">// 全部使用默认值.</span> <span class="nf">setPadding</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="c1">// 设置 left.</span> <span class="nf">setPadding</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="c1">// 设置全部.</span> <span class="nf">setPadding</span><span class="p">(</span><span class="n">top</span> <span class="p">=</span> <span class="mi">2</span><span class="p">)</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="c1">// 设置 top.</span> <span class="c1">//setPadding(left = 1, 2, 3, 4)         // 编译错误.</span> <span class="nf">setPadding</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">bottom</span> <span class="p">=</span> <span class="mi">4</span><span class="p">,</span> <span class="n">right</span> <span class="p">=</span> <span class="mi">3</span><span class="p">)</span> <span class="c1">// 默认参数顺序可以改变.</span> <span class="nf">setPadding</span><span class="p">(</span><span class="n">left</span> <span class="p">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">top</span> <span class="p">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">right</span> <span class="p">=</span> <span class="mi">3</span><span class="p">,</span> <span class="n">bottom</span> <span class="p">=</span> <span class="mi">4</span><span class="p">)</span> <span class="c1">// 命名参数, 增加可读性.</span> </code></pre></div></div> <ul> <li>默认参数可以使用之前的参数作为默认值。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">setMargin</span><span class="p">(</span> <span class="n">left</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">top</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">right</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="n">left</span><span class="p">,</span> <span class="n">bottom</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="n">top</span><span class="p">)</span> <span class="p">{}</span> <span class="nf">setMargin</span><span class="p">()</span> <span class="c1">// 0, 0, 0, 0</span> <span class="nf">setMargin</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1">// 1, 0, 1, 0</span> <span class="nf">setMargin</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="c1">// 1, 2, 1, 2</span> <span class="nf">setMargin</span><span class="p">(</span><span class="n">right</span> <span class="p">=</span> <span class="mi">3</span><span class="p">)</span> <span class="c1">// 0, 0, 3, 0</span> <span class="nf">setMargin</span><span class="p">(</span><span class="n">top</span> <span class="p">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">bottom</span> <span class="p">=</span> <span class="mi">4</span><span class="p">)</span> <span class="c1">// 0, 2, 0, 4</span> </code></pre></div></div> <ul> <li>默认参数减少方法重载。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">CustomView</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span> <span class="n">attrs</span><span class="p">:</span> <span class="nc">AttributeSet</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span><span class="p">,</span> <span class="n">defStyleAttr</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">defStyleRes</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span><span class="p">)</span> </code></pre></div></div> <ul> <li>默认参数在无默认参数之前。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">circle</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nc">Float</span> <span class="p">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nc">Float</span> <span class="p">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">radius</span><span class="p">:</span> <span class="nc">Float</span><span class="p">)</span> <span class="p">{}</span> <span class="nf">circle</span><span class="p">(</span><span class="n">radius</span> <span class="p">=</span> <span class="mf">1f</span><span class="p">)</span> <span class="c1">// 如果要 x, y 使用默认参数, 必须通过命名参数指定 radius.</span> </code></pre></div></div> <ul> <li>Lambda 表达式作为最后一个参数。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">post</span><span class="p">(</span><span class="n">delayMillis</span><span class="p">:</span> <span class="nc">Long</span> <span class="p">=</span> <span class="mi">0L</span><span class="p">,</span> <span class="n">runnable</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">Unit</span><span class="p">)</span> <span class="p">{}</span> <span class="nf">post</span> <span class="p">{</span> <span class="c1">// Do something.</span> <span class="p">}</span> <span class="nf">post</span><span class="p">(</span><span class="mi">1000L</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 1 秒后 do something.</span> <span class="p">}</span> <span class="nf">post</span><span class="p">(</span><span class="n">runnable</span> <span class="p">=</span> <span class="p">{</span> <span class="c1">// 如果在括号内传递 lambda 表达式参数, 想要使用 delayMillis 默认参数, 仍然必须使用命名参数.</span> <span class="p">})</span> </code></pre></div></div> <h1 id="可变数量参数">可变数量参数</h1> <ul> <li>与 Java 的 … 可变参数类似。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">sum</span><span class="p">(</span><span class="k">vararg</span> <span class="n">numbers</span><span class="p">:</span> <span class="nc">Float</span><span class="p">):</span> <span class="nc">Float</span> <span class="p">{</span> <span class="kd">var</span> <span class="py">sum</span> <span class="p">=</span> <span class="mf">0f</span> <span class="n">numbers</span><span class="p">.</span><span class="nf">forEach</span> <span class="p">{</span> <span class="n">sum</span> <span class="p">+=</span> <span class="n">it</span> <span class="p">}</span> <span class="k">return</span> <span class="n">sum</span> <span class="p">}</span> <span class="nf">sum</span><span class="p">(</span><span class="mf">1f</span><span class="p">,</span> <span class="mf">2f</span><span class="p">,</span> <span class="mf">3f</span><span class="p">,</span> <span class="mf">4f</span><span class="p">)</span> </code></pre></div></div> <ul> <li>可变数量参数作为参数传递。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">average</span><span class="p">(</span><span class="k">vararg</span> <span class="n">numbers</span><span class="p">:</span> <span class="nc">Float</span><span class="p">):</span> <span class="nc">Float</span> <span class="p">{</span> <span class="c1">//    return sum(numbers) / numbers.size // 编译错误:Type mismatch. Required: Float. Found: FloatArray.</span> <span class="k">return</span> <span class="nf">sum</span><span class="p">(*</span><span class="n">numbers</span><span class="p">)</span> <span class="p">/</span> <span class="n">numbers</span><span class="p">.</span><span class="n">size</span> <span class="p">}</span> <span class="kd">val</span> <span class="py">floatArray</span> <span class="p">=</span> <span class="nf">floatArrayOf</span><span class="p">(</span><span class="mf">3f</span><span class="p">,</span> <span class="mf">4f</span><span class="p">,</span> <span class="mf">5f</span><span class="p">)</span> <span class="nf">average</span><span class="p">(</span><span class="mf">1f</span><span class="p">,</span> <span class="mf">2f</span><span class="p">,</span> <span class="p">*</span><span class="n">floatArray</span><span class="p">,</span> <span class="mf">6f</span><span class="p">)</span> </code></pre></div></div> <h1 id="扩展函数">扩展函数</h1> <blockquote> <p>能够扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。</p> </blockquote> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nc">Date</span><span class="p">.</span><span class="nf">toCalendar</span><span class="p">():</span> <span class="nc">Calendar</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">calendar</span> <span class="p">=</span> <span class="nc">Calendar</span><span class="p">.</span><span class="nf">getInstance</span><span class="p">()</span> <span class="n">calendar</span><span class="p">.</span><span class="n">time</span> <span class="p">=</span> <span class="k">this</span> <span class="k">return</span> <span class="n">calendar</span> <span class="p">}</span> <span class="kd">val</span> <span class="py">calendar</span> <span class="p">=</span> <span class="n">date</span><span class="p">.</span><span class="nf">toCalendar</span><span class="p">()</span> </code></pre></div></div> <ul> <li>扩展函数不会覆盖成员函数。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Warning: Extension is shadowed by a member: public open fun toString(): String</span> <span class="k">fun</span> <span class="nc">Date</span><span class="p">.</span><span class="nf">toString</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span> <span class="k">return</span> <span class="s">"我就不告诉你时间"</span> <span class="p">}</span> <span class="n">date</span><span class="p">.</span><span class="nf">toString</span><span class="p">()</span> <span class="c1">// 还是会输出时间的.</span> </code></pre></div></div> <ul> <li>但是方法重载是可以的。</li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nc">Date</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="n">pattern</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">String</span> <span class="p">{</span> <span class="kd">val</span> <span class="py">dateFormat</span> <span class="p">=</span> <span class="nc">SimpleDateFormat</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="nc">Locale</span><span class="p">.</span><span class="nf">getDefault</span><span class="p">())</span> <span class="k">return</span> <span class="n">dateFormat</span><span class="p">.</span><span class="nf">format</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">}</span> <span class="n">date</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="s">"yyyy-MM-dd"</span><span class="p">)</span> <span class="c1">// 2017-12-13</span> </code></pre></div></div> <ul> <li>可空接受者。 ``` kotlin fun String?.isEmpty(): Boolean { //    return this == null || this.isEmpty() // 这么写会出现一个小递归。。。    return this == null || this.length == 0 }</li> </ul> <p>fun foo(str: String?) { str.isEmpty() }</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> * 中缀表示法进一步简化. ``` kotlin infix fun Int.pow(e: Int): Int { return Math.pow(this.toDouble(), e.toDouble()).toInt() } 2.pow(5) // 不加 infix 调用. 2 pow 5 // 加 infix 简化调用. 32 </code></pre></div></div> <h1 id="局部函数">局部函数</h1> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">processor</span><span class="p">(</span><span class="n">type</span><span class="p">:</span> <span class="nc">Char</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 需要被复用且仅被 processor 方法复用.</span> <span class="k">fun</span> <span class="nf">coreOperation</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"Core operation."</span><span class="p">)</span> <span class="p">}</span> <span class="k">when</span> <span class="p">(</span><span class="n">type</span><span class="p">)</span> <span class="p">{</span> <span class="sc">'A'</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"Pre A"</span><span class="p">)</span> <span class="nf">coreOperation</span><span class="p">()</span> <span class="nf">println</span><span class="p">(</span><span class="s">"Post A"</span><span class="p">)</span> <span class="p">}</span> <span class="sc">'B'</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"Pre B"</span><span class="p">)</span> <span class="nf">coreOperation</span><span class="p">()</span> <span class="nf">println</span><span class="p">(</span><span class="s">"Post B"</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h1 id="方法赋予属性">方法赋予属性</h1> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">action</span> <span class="p">=</span> <span class="k">fun</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"property"</span><span class="p">)</span> <span class="p">}</span> <span class="kd">val</span> <span class="py">action2</span> <span class="p">=</span> <span class="k">fun</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"property 2"</span><span class="p">)</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">action2</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"function"</span><span class="p">)</span> <span class="p">}</span> <span class="nf">action</span><span class="p">()</span> <span class="c1">// property</span> <span class="n">action2</span><span class="p">.</span><span class="nf">invoke</span><span class="p">()</span> <span class="c1">// property 2</span> <span class="nf">action2</span><span class="p">()</span> <span class="c1">// function</span> </code></pre></div></div> <h1 id="传递成员-function-参数">传递成员 function 参数</h1> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">printAsterisk</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"****************************************************************"</span><span class="p">)</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">printPound</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"################################################################"</span><span class="p">)</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">printHyphe</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"----------------------------------------------------------------"</span><span class="p">)</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">log</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">printSeparatorAction</span><span class="p">:</span> <span class="p">()</span> <span class="p">-&gt;</span> <span class="nc">Unit</span><span class="p">)</span> <span class="p">{</span> <span class="nf">printSeparatorAction</span><span class="p">()</span> <span class="nf">println</span><span class="p">(</span><span class="n">message</span><span class="p">)</span> <span class="nf">printSeparatorAction</span><span class="p">()</span> <span class="p">}</span> <span class="nf">log</span><span class="p">(</span><span class="s">"hello"</span><span class="p">,</span> <span class="o">::</span><span class="n">printAsterisk</span><span class="p">)</span> </code></pre></div></div>函数默认参数和命名参数Kotlin 日常踩坑2017-12-07T00:00:00+00:002017-12-07T00:00:00+00:00https://ebnbin.github.io/2017/12/07/kotlin-trap<p>踩到一个 Koltin 加 Android api 27 的坑。</p> <p>Support library 升级到 27.x.x 之后,发现项目一堆报错:</p> <p><img src="/assets/img/2017-12-07-kotlin-trap/error.png" alt="error" /></p> <p>它说我的 <code class="language-plaintext highlighter-rouge">onCreateView</code> 方法覆写了 nothing。<strong>Are You Kidding me?</strong></p> <p>对比了一下两个版本的 support library,发现了问题。</p> <p>27.x.x 版本:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Nullable</span> <span class="kd">public</span> <span class="nc">View</span> <span class="nf">onCreateView</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="nc">LayoutInflater</span> <span class="n">inflater</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">ViewGroup</span> <span class="n">container</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <p>26.x.x 版本</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Nullable</span> <span class="kd">public</span> <span class="nc">View</span> <span class="nf">onCreateView</span><span class="o">(</span><span class="nc">LayoutInflater</span> <span class="n">inflater</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">ViewGroup</span> <span class="n">container</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <p>就是这个 <code class="language-plaintext highlighter-rouge">LayoutInflater</code> 前的 <code class="language-plaintext highlighter-rouge">@NonNull</code> 注解导致的,在 kotlin 中,任何类型都分 nullable 和 non-null 两种,并且不能隐式转换,在 kotlin 调用 java 代码时如果 java 代码没有使用注解声明一个变量是否可为空,例如 <code class="language-plaintext highlighter-rouge">String str</code>,kotlin 会将这个变量标记为 <code class="language-plaintext highlighter-rouge">String!</code> 类型,表示我也不知道这个变量是否可为空,使用时需要自行处理,当 java 代码中使用了 <code class="language-plaintext highlighter-rouge">@NonNull</code> 和 <code class="language-plaintext highlighter-rouge">@Nullable</code> 注解标注了一个变量的类型,那么 kotlin 就明确地知道了它要么是 <code class="language-plaintext highlighter-rouge">String</code> 不可为空,要么是 <code class="language-plaintext highlighter-rouge">String?</code> 可为空。<code class="language-plaintext highlighter-rouge">String</code> 和 <code class="language-plaintext highlighter-rouge">String?</code> 可以理解为两个不相同的类型,因此上面的方法覆写就会报错,认为方法签名不相同,不能使用 <code class="language-plaintext highlighter-rouge">override</code> 关键字。</p> <p>同理,在 fragment 中:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Nullable</span> <span class="kd">public</span> <span class="nc">Context</span> <span class="nf">getContext</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mHost</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="n">mHost</span><span class="o">.</span><span class="na">getContext</span><span class="o">();</span> <span class="o">}</span> </code></pre></div></div> <p>因此在调用 context 的方法时必须 <code class="language-plaintext highlighter-rouge">context!!.getString()</code> 而不能 <code class="language-plaintext highlighter-rouge">context.getString()</code>。</p> <p><strong><em>WTTTTTTTF!</em></strong></p> <p>即便在我明确认定 context 不为空的情况下,仍然需要加 <code class="language-plaintext highlighter-rouge">!!</code> 使用。于是我想了一个办法:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">fun</span> <span class="nf">getContext</span><span class="p">()</span> <span class="p">=</span> <span class="k">super</span><span class="p">.</span><span class="nf">getContext</span><span class="p">()</span><span class="o">!!</span> <span class="k">fun</span> <span class="nf">getNullableContext</span><span class="p">()</span> <span class="p">=</span> <span class="k">super</span><span class="p">.</span><span class="nf">getContext</span><span class="p">()</span> </code></pre></div></div> <p>这样我就可以在明确知道 context 不为空的地方直接调用 <code class="language-plaintext highlighter-rouge">context.getString()</code> 啦,需要判断是否为空的地方调用 <code class="language-plaintext highlighter-rouge">getNullableContext()</code>。</p> <p>然而问题并没有结束,因为有些地方我需要使用 <code class="language-plaintext highlighter-rouge">getActivity()</code> 而不只是 <code class="language-plaintext highlighter-rouge">getContext()</code>,可是:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Nullable</span> <span class="kd">final</span> <span class="kd">public</span> <span class="nc">FragmentActivity</span> <span class="nf">getActivity</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mHost</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="o">(</span><span class="nc">FragmentActivity</span><span class="o">)</span> <span class="n">mHost</span><span class="o">.</span><span class="na">getActivity</span><span class="o">();</span> <span class="o">}</span> </code></pre></div></div> <p>注意到那个“万恶”的 <code class="language-plaintext highlighter-rouge">final</code> 了吗?</p> <p><strong><em>WTTTTTTTTTTTTTTF!</em></strong></p> <p>只能在每个使用 <code class="language-plaintext highlighter-rouge">getActivity()</code> 并确定它不为空的地方手动加 <code class="language-plaintext highlighter-rouge">!!</code> 了。。。暂时没有想到更好的解决办法。</p> <p>最后说两句:</p> <ul> <li> <p>第一,新的变动并不是为了给写代码添加麻烦而加上的,要明确 fragment 中的 <code class="language-plaintext highlighter-rouge">getContext()</code>、<code class="language-plaintext highlighter-rouge">getActivity()</code> 在一些情况下<strong>就是有可能为空的</strong>,不要简单地在所有地方加上 <code class="language-plaintext highlighter-rouge">!!</code> 就算完事,该判断的地方还是得判断的。</p> </li> <li> <p>第二,虽然 27 版本的升级带来了这样的兼容问题,但整体而言,对于是否可为空类型在 java 和 kotlin 之间的调用语法更加完善了。兼容适配的过程很痛苦,但是 —— <strong>忍着!</strong> - -#</p> </li> </ul>踩到一个 Koltin 加 Android api 27 的坑。Android Instance State2017-11-28T00:00:00+00:002017-11-28T00:00:00+00:00https://ebnbin.github.io/2017/11/28/android-instance-state<p>今天我们来聊聊 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 这两个非生命周期却为大家所熟知的方法。</p> <p>我们从 <em>简书</em> app 的一个功能说起。</p> <p><img src="/assets/img/2017-11-28-android-instance-state/jianshu.gif" alt="简书 app" /></p> <p>浏览过一个文章页面后,如果应用在后台被杀死,再次打开 app 时,会从首页自动跳转到上次浏览的页面以及浏览的位置。</p> <p><img src="/assets/img/2017-11-28-android-instance-state/jianshu2.gif" alt="简书 app" /></p> <p>而如果是正常的返回退出应用,再次打开 app 时不会进行自动跳转。</p> <h1 id="简书-自动恢复页面功能简单实现"><em>简书</em> 自动恢复页面功能简单实现</h1> <p>话不多说先上代码:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ItemActivity.</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">KEY_ITEM</span> <span class="o">=</span> <span class="s">"item"</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">KEY_SCROLL_Y</span> <span class="o">=</span> <span class="s">"scroll_y"</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="nc">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="n">setTitle</span><span class="o">(</span><span class="n">getIntent</span><span class="o">().</span><span class="na">getStringExtra</span><span class="o">(</span><span class="no">KEY_ITEM</span><span class="o">));</span> <span class="k">if</span> <span class="o">(</span><span class="n">savedInstanceState</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">mSp</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="no">KEY_SCROLL_Y</span><span class="o">))</span> <span class="o">{</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">scrollY</span> <span class="o">=</span> <span class="n">mSp</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="no">KEY_SCROLL_Y</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span> <span class="n">mScrollView</span><span class="o">.</span><span class="na">postDelayed</span><span class="o">(</span><span class="k">new</span> <span class="nc">Runnable</span><span class="o">()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> <span class="n">mScrollView</span><span class="o">.</span><span class="na">smoothScrollTo</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">scrollY</span><span class="o">);</span> <span class="o">}</span> <span class="o">},</span> <span class="mi">500L</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onResume</span><span class="o">()</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">onResume</span><span class="o">();</span> <span class="n">mSp</span><span class="o">.</span><span class="na">edit</span><span class="o">().</span><span class="na">remove</span><span class="o">(</span><span class="no">KEY_ITEM</span><span class="o">).</span><span class="na">apply</span><span class="o">();</span> <span class="n">mSp</span><span class="o">.</span><span class="na">edit</span><span class="o">().</span><span class="na">remove</span><span class="o">(</span><span class="no">KEY_SCROLL_Y</span><span class="o">).</span><span class="na">apply</span><span class="o">();</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onSaveInstanceState</span><span class="o">(</span><span class="nc">Bundle</span> <span class="n">outState</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">onSaveInstanceState</span><span class="o">(</span><span class="n">outState</span><span class="o">);</span> <span class="n">mSp</span><span class="o">.</span><span class="na">edit</span><span class="o">().</span><span class="na">putString</span><span class="o">(</span><span class="no">KEY_ITEM</span><span class="o">,</span> <span class="n">getIntent</span><span class="o">().</span><span class="na">getStringExtra</span><span class="o">(</span><span class="no">KEY_ITEM</span><span class="o">)).</span><span class="na">apply</span><span class="o">();</span> <span class="n">mSp</span><span class="o">.</span><span class="na">edit</span><span class="o">().</span><span class="na">putInt</span><span class="o">(</span><span class="no">KEY_SCROLL_Y</span><span class="o">,</span> <span class="n">mScrollView</span><span class="o">.</span><span class="na">getScrollY</span><span class="o">()).</span><span class="na">apply</span><span class="o">();</span> <span class="o">}</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// MainActivity.</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="nc">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="n">mListView</span><span class="o">.</span><span class="na">setAdapter</span><span class="o">(</span><span class="k">new</span> <span class="nc">ArrayAdapter</span><span class="o">&lt;&gt;(</span><span class="k">this</span><span class="o">,</span> <span class="n">android</span><span class="o">.</span><span class="na">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">simple_list_item_1</span><span class="o">,</span> <span class="no">LIST_VIEW_ITEMS</span><span class="o">));</span> <span class="n">mListView</span><span class="o">.</span><span class="na">setOnItemClickListener</span><span class="o">(</span><span class="k">this</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">savedInstanceState</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">mSp</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="nc">ItemActivity</span><span class="o">.</span><span class="na">KEY_ITEM</span><span class="o">))</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">item</span> <span class="o">=</span> <span class="n">mSp</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="nc">ItemActivity</span><span class="o">.</span><span class="na">KEY_ITEM</span><span class="o">,</span> <span class="s">"0"</span><span class="o">);</span> <span class="n">mListView</span><span class="o">.</span><span class="na">postDelayed</span><span class="o">(</span><span class="k">new</span> <span class="nc">Runnable</span><span class="o">()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> <span class="kt">int</span> <span class="n">position</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">item</span><span class="o">);</span> <span class="n">onItemClick</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">position</span><span class="o">,</span> <span class="mi">0L</span><span class="o">);</span> <span class="o">}</span> <span class="o">},</span> <span class="mi">500L</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onItemClick</span><span class="o">(</span><span class="nc">AdapterView</span><span class="o">&lt;?&gt;</span> <span class="n">parent</span><span class="o">,</span> <span class="nc">View</span> <span class="n">view</span><span class="o">,</span> <span class="kt">int</span> <span class="n">position</span><span class="o">,</span> <span class="kt">long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span> <span class="n">startActivity</span><span class="o">(</span><span class="k">new</span> <span class="nc">Intent</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="nc">ItemActivity</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="o">.</span><span class="na">putExtra</span><span class="o">(</span><span class="nc">ItemActivity</span><span class="o">.</span><span class="na">KEY_ITEM</span><span class="o">,</span> <span class="no">LIST_VIEW_ITEMS</span><span class="o">[</span><span class="n">position</span><span class="o">]));</span> <span class="o">}</span> </code></pre></div></div> <p>效果如下:</p> <p><img src="/assets/img/2017-11-28-android-instance-state/demo.gif" alt="demo" /></p> <p>当然这只是一种可以达到效果的实现方式,在 <code class="language-plaintext highlighter-rouge">ItemActivity</code> 的 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 中使用 <code class="language-plaintext highlighter-rouge">SharedPreferences</code> 保存当前 Item 页面和 <code class="language-plaintext highlighter-rouge">ScrollView</code> 滚动的位置,<strong>在 <code class="language-plaintext highlighter-rouge">onResume</code> 中清除这两个值</strong>。因为正常按返回键退出 app,下次启动时不会自动打开上次浏览的页面。这就涉及到 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 的触发时机了。</p> <h1 id="onrestoreinstancestate-和-onsaveinstancestate-的触发时机"><code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 的触发时机</h1> <p>对于 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code>,在以下场景会触发回调:</p> <ul> <li>Home 键</li> <li>熄屏</li> <li>打开另一个 Activity</li> <li>屏幕旋转(不止是屏幕旋转,任何未在 Manifest 中配置 configChanges 的 config 在改变时都会触发,只是屏幕旋转最常见也最容易观测)</li> </ul> <p>总而言之,<code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 会在 <strong>非用户主动关闭 Activity 而又有可能导致 Activity 被回收而销毁</strong> 的时机调用,屏幕旋转是会立即销毁并立即重建 activity,如果是用户主动按 back 退出 Activity,这个方法不会被调用。</p> <p>对于 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code>,<strong>它不是和 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 配对调用的</strong>。<code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 被调用并不一定意味着 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 在之后也会被调用,它只在 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 被调用且 Activity 的确被销毁的情况下,下次恢复 activity 时调用。例如:按 home 键触发 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code>,然后立即回到 activity,activity 还没有被销毁,是不会调用 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 的。</p> <p>也就是说:</p> <ul> <li><code class="language-plaintext highlighter-rouge">onSaveInstanceState</code>:如果你不是主动要退出,但目前的某个操作导致你<strong>处于可能被系统回收的状态</strong>,你可以在这里保存你当前的状态</li> <li><code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code>:如果你之前保存过状态,且你<strong>真的被系统回收了</strong>,你可以在这里恢复你保存的状态</li> </ul> <p><code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 在 application 进程被杀死的情况下,例如,在应用信息中强制停止 app,或在 recent 界面中清除 app,则不会在下次启动时被调用。</p> <p>另外,<code class="language-plaintext highlighter-rouge">onCreate</code> 生命周期方法中也提供参数 <code class="language-plaintext highlighter-rouge">savedInstanceState</code>。与 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 不同的是:<code class="language-plaintext highlighter-rouge">onCreate</code> 中的 <code class="language-plaintext highlighter-rouge">savedInstanceState</code> 是可能为 <code class="language-plaintext highlighter-rouge">null</code> 的,<code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 中的一定不为 <code class="language-plaintext highlighter-rouge">null</code>,只在有状态需要被恢复时,才会调用 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code>。</p> <p>回到上面的问题,在 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 中保存 SharedPreferences 是因为这符合“我的数据可能丢失”的情况,只是这里使用了持久化存储以便在即使应用被杀死的情况下也能恢复数据。在 <code class="language-plaintext highlighter-rouge">onResume</code> 中删除数据是因为我又回到了正常状态,如果这时候按 back 退出,这是用户的主动操作,我不需要在下一次启动是恢复数据了。之后在 <code class="language-plaintext highlighter-rouge">onCreate</code> 中判断是否存在已保存的数据,如果存在就恢复。判断的时候多加了一个 <code class="language-plaintext highlighter-rouge">savedInstanceState == null</code> 是因为这是针对应用被杀死后还能恢复的需求的,如果仍然存在可以恢复的数据,应该使用 <code class="language-plaintext highlighter-rouge">savedInstanceState</code> 进行数据恢复而不应该读取 SharedPreferences 进行数据恢复。</p> <h1 id="view-的-instance-state">View 的 instance state.</h1> <p>等等,这个场景好像和 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 没什么关系啊,我们上面写的 demo,都没有用到 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code>,而且,我们目前测试的主要是持久化的部分,并没有测试真正的当 activity 被系统回收时的情况。</p> <p>那么我们该怎么测试系统回收 activity 的情况呢。。。毕竟现在手机内存都大,哪那么容易测得到这种情况。</p> <p>两种办法:</p> <ul> <li>自己写的 demo 不固定屏幕方向,测试屏幕旋转时候的情况,它一定会调用 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code></li> <li>如果我不在 demo 中测试,想自己在线上项目中测试,而这个 app 又不允许屏幕旋转,那么:</li> </ul> <p>在 <em>开发者选项</em> 中开启“不保留活动”,这个“活动”就是 <code class="language-plaintext highlighter-rouge">Activity</code> 的翻译 = =。。。还不如不翻。</p> <p><img src="/assets/img/2017-11-28-android-instance-state/developer.png" alt="开发者选项" /></p> <p>它会在 activity 被放置到后台时就销毁(回收)。那么我们只要按下 home 键就立刻模拟了系统内存不够而回收 activity 的场景。再次回到 activity 时,<code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 就会被调用。</p> <p>开启后我们测试下我们的 demo。</p> <p><img src="/assets/img/2017-11-28-android-instance-state/demo2.gif" alt="demo" /></p> <p>发现之前滚动的位置被恢复了,并没有回到 ScrollView 顶部。然而,我们并没有覆写 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 在里面恢复 ScrollView 的位置,甚至没有在 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 中保存什么值啊?</p> <p>View 也有自己的 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code>,我们可以看一下 <code class="language-plaintext highlighter-rouge">ScrollView</code> 源码:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onRestoreInstanceState</span><span class="o">(</span><span class="nc">Parcelable</span> <span class="n">state</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mContext</span><span class="o">.</span><span class="na">getApplicationInfo</span><span class="o">().</span><span class="na">targetSdkVersion</span> <span class="o">&lt;=</span> <span class="nc">Build</span><span class="o">.</span><span class="na">VERSION_CODES</span><span class="o">.</span><span class="na">JELLY_BEAN_MR2</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Some old apps reused IDs in ways they shouldn't have.</span> <span class="c1">// Don't break them, but they don't get scroll state restoration.</span> <span class="kd">super</span><span class="o">.</span><span class="na">onRestoreInstanceState</span><span class="o">(</span><span class="n">state</span><span class="o">);</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="nc">SavedState</span> <span class="n">ss</span> <span class="o">=</span> <span class="o">(</span><span class="nc">SavedState</span><span class="o">)</span> <span class="n">state</span><span class="o">;</span> <span class="kd">super</span><span class="o">.</span><span class="na">onRestoreInstanceState</span><span class="o">(</span><span class="n">ss</span><span class="o">.</span><span class="na">getSuperState</span><span class="o">());</span> <span class="n">mSavedState</span> <span class="o">=</span> <span class="n">ss</span><span class="o">;</span> <span class="n">requestLayout</span><span class="o">();</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="nc">Parcelable</span> <span class="nf">onSaveInstanceState</span><span class="o">()</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mContext</span><span class="o">.</span><span class="na">getApplicationInfo</span><span class="o">().</span><span class="na">targetSdkVersion</span> <span class="o">&lt;=</span> <span class="nc">Build</span><span class="o">.</span><span class="na">VERSION_CODES</span><span class="o">.</span><span class="na">JELLY_BEAN_MR2</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Some old apps reused IDs in ways they shouldn't have.</span> <span class="c1">// Don't break them, but they don't get scroll state restoration.</span> <span class="k">return</span> <span class="kd">super</span><span class="o">.</span><span class="na">onSaveInstanceState</span><span class="o">();</span> <span class="o">}</span> <span class="nc">Parcelable</span> <span class="n">superState</span> <span class="o">=</span> <span class="kd">super</span><span class="o">.</span><span class="na">onSaveInstanceState</span><span class="o">();</span> <span class="nc">SavedState</span> <span class="n">ss</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SavedState</span><span class="o">(</span><span class="n">superState</span><span class="o">);</span> <span class="err"> </span> <span class="err"> </span><span class="n">ss</span><span class="o">.</span><span class="na">scrollPosition</span> <span class="o">=</span> <span class="n">mScrollY</span><span class="o">;</span> <span class="c1">// 保存滚动位置</span> <span class="err"> </span> <span class="err"> </span><span class="k">return</span> <span class="n">ss</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <p>ScrollView 自己处理的滚动位置的保存与恢复。</p> <p>然后 Activity 是怎么保存和恢复 View 的状态的呢?我们可以追溯到 Activity 的 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code>(<code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 与它是基本类似的逻辑,一个是保存,一个是恢复,所以我们接下来就只看保存的部分):</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onSaveInstanceState</span><span class="o">(</span><span class="nc">Bundle</span> <span class="n">outState</span><span class="o">)</span> <span class="o">{</span> <span class="err"> </span> <span class="err"> </span><span class="n">outState</span><span class="o">.</span><span class="na">putBundle</span><span class="o">(</span><span class="no">WINDOW_HIERARCHY_TAG</span><span class="o">,</span> <span class="n">mWindow</span><span class="o">.</span><span class="na">saveHierarchyState</span><span class="o">());</span> <span class="c1">// 这里</span> <span class="n">outState</span><span class="o">.</span><span class="na">putInt</span><span class="o">(</span><span class="no">LAST_AUTOFILL_ID</span><span class="o">,</span> <span class="n">mLastAutofillId</span><span class="o">);</span> <span class="nc">Parcelable</span> <span class="n">p</span> <span class="o">=</span> <span class="n">mFragments</span><span class="o">.</span><span class="na">saveAllState</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">p</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">outState</span><span class="o">.</span><span class="na">putParcelable</span><span class="o">(</span><span class="no">FRAGMENTS_TAG</span><span class="o">,</span> <span class="n">p</span><span class="o">);</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">mAutoFillResetNeeded</span><span class="o">)</span> <span class="o">{</span> <span class="n">outState</span><span class="o">.</span><span class="na">putBoolean</span><span class="o">(</span><span class="no">AUTOFILL_RESET_NEEDED</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> <span class="n">getAutofillManager</span><span class="o">().</span><span class="na">onSaveInstanceState</span><span class="o">(</span><span class="n">outState</span><span class="o">);</span> <span class="o">}</span> <span class="n">getApplication</span><span class="o">().</span><span class="na">dispatchActivitySaveInstanceState</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">outState</span><span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <p>它往 Bundle 中存入了 <code class="language-plaintext highlighter-rouge">mWindow.saveHierarchyState()</code>,这个 <code class="language-plaintext highlighter-rouge">mWindow</code> 就是 <code class="language-plaintext highlighter-rouge">PhoneWindow</code>,所以我们接着找 PhoneWindow:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">Bundle</span> <span class="nf">saveHierarchyState</span><span class="o">()</span> <span class="o">{</span> <span class="nc">Bundle</span> <span class="n">outState</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Bundle</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">mContentParent</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">outState</span><span class="o">;</span> <span class="o">}</span> <span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">states</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;();</span> <span class="err"> </span> <span class="err"> </span><span class="n">mContentParent</span><span class="o">.</span><span class="na">saveHierarchyState</span><span class="o">(</span><span class="n">states</span><span class="o">);</span> <span class="c1">// 这里</span> <span class="err"> </span> <span class="err"> </span><span class="n">outState</span><span class="o">.</span><span class="na">putSparseParcelableArray</span><span class="o">(</span><span class="no">VIEWS_TAG</span><span class="o">,</span> <span class="n">states</span><span class="o">);</span> <span class="c1">// Save the focused view ID.</span> <span class="kd">final</span> <span class="nc">View</span> <span class="n">focusedView</span> <span class="o">=</span> <span class="n">mContentParent</span><span class="o">.</span><span class="na">findFocus</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">focusedView</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">focusedView</span><span class="o">.</span><span class="na">getId</span><span class="o">()</span> <span class="o">!=</span> <span class="nc">View</span><span class="o">.</span><span class="na">NO_ID</span><span class="o">)</span> <span class="o">{</span> <span class="n">outState</span><span class="o">.</span><span class="na">putInt</span><span class="o">(</span><span class="no">FOCUSED_ID_TAG</span><span class="o">,</span> <span class="n">focusedView</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span> <span class="o">}</span> <span class="c1">// save the panels</span> <span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">panelStates</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;();</span> <span class="n">savePanelState</span><span class="o">(</span><span class="n">panelStates</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">panelStates</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="n">outState</span><span class="o">.</span><span class="na">putSparseParcelableArray</span><span class="o">(</span><span class="no">PANELS_TAG</span><span class="o">,</span> <span class="n">panelStates</span><span class="o">);</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">mDecorContentParent</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">actionBarStates</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;();</span> <span class="n">mDecorContentParent</span><span class="o">.</span><span class="na">saveToolbarHierarchyState</span><span class="o">(</span><span class="n">actionBarStates</span><span class="o">);</span> <span class="n">outState</span><span class="o">.</span><span class="na">putSparseParcelableArray</span><span class="o">(</span><span class="no">ACTION_BAR_TAG</span><span class="o">,</span> <span class="n">actionBarStates</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="n">outState</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <p>这个 <code class="language-plaintext highlighter-rouge">mContentParent</code> 是 Window 的 <code class="language-plaintext highlighter-rouge">DecorView</code> 上 id 为 <code class="language-plaintext highlighter-rouge">com.android.internal.R.id.content</code> 的一个 ViewGroup,在设置了某些 flag 或主题的情况下,它有可能是 DecorView 自身。简而言之,它是我们 <code class="language-plaintext highlighter-rouge">setContentView</code> 时内容添加到的 ViewGroup,平时我们往 Activity 填充一个 fragment 时可以这么写:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fragmentManager</span><span class="o">.</span><span class="na">beginTransaction</span><span class="o">().</span><span class="na">add</span><span class="o">(</span><span class="n">android</span><span class="o">.</span><span class="na">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">content</span><span class="o">,</span> <span class="n">fragment</span><span class="o">).</span><span class="na">commit</span><span class="o">();</span> </code></pre></div></div> <p>就往 Activity 的 contentView 上添加一个 fragment,省去了为 Activity <code class="language-plaintext highlighter-rouge">setContentView</code> 添加只有一个 FrameLayout 的布局的麻烦。</p> <p>对的这个 <code class="language-plaintext highlighter-rouge">mContentParent</code> 就是这个 <code class="language-plaintext highlighter-rouge">android.R.id.content</code> 的 View,Activity 的 content 区域的所有 View 都以它为父 ViewGroup。</p> <p>这个 <code class="language-plaintext highlighter-rouge">mContentParent</code> 调用了 <code class="language-plaintext highlighter-rouge">saveHierarchyState(states)</code>:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">saveHierarchyState</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="n">dispatchSaveInstanceState</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="o">}</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">dispatchSaveInstanceState</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mID</span> <span class="o">!=</span> <span class="no">NO_ID</span> <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="n">mViewFlags</span> <span class="o">&amp;</span> <span class="no">SAVE_DISABLED_MASK</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="n">mPrivateFlags</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="no">PFLAG_SAVE_STATE_CALLED</span><span class="o">;</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span><span class="nc">Parcelable</span> <span class="n">state</span> <span class="o">=</span> <span class="n">onSaveInstanceState</span><span class="o">();</span> <span class="c1">// 这里</span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span> <span class="err"> </span><span class="k">if</span> <span class="o">((</span><span class="n">mPrivateFlags</span> <span class="o">&amp;</span> <span class="no">PFLAG_SAVE_STATE_CALLED</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span> <span class="s">"Derived class did not call super.onSaveInstanceState()"</span><span class="o">);</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">state</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Log.i("View", "Freezing #" + Integer.toHexString(mID)</span> <span class="c1">// + ": " + state);</span> <span class="n">container</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">mID</span><span class="o">,</span> <span class="n">state</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>绕了一大圈子,总算回到 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 了,这还没完,这是 View 的 <code class="language-plaintext highlighter-rouge">dispatchSaveInstanceState</code>,对于 ViewGroup:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">dispatchSaveInstanceState</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">dispatchSaveInstanceState</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">mChildrenCount</span><span class="o">;</span> <span class="kd">final</span> <span class="nc">View</span><span class="o">[]</span> <span class="n">children</span> <span class="o">=</span> <span class="n">mChildren</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="nc">View</span> <span class="n">c</span> <span class="o">=</span> <span class="n">children</span><span class="o">[</span><span class="n">i</span><span class="o">];</span> <span class="k">if</span> <span class="o">((</span><span class="n">c</span><span class="o">.</span><span class="na">mViewFlags</span> <span class="o">&amp;</span> <span class="no">PARENT_SAVE_DISABLED_MASK</span><span class="o">)</span> <span class="o">!=</span> <span class="no">PARENT_SAVE_DISABLED</span><span class="o">)</span> <span class="o">{</span> <span class="n">c</span><span class="o">.</span><span class="na">dispatchSaveInstanceState</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>如果还是不太清楚,我们从头屡一遍:</p> <ul> <li>因可能发生 Activity 回收而调用 Activity 的 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code></li> <li>Activity 的 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 中调用 PhoneWindow 的 <code class="language-plaintext highlighter-rouge">saveHierarchyState()</code></li> <li>PhoneWindow 的 <code class="language-plaintext highlighter-rouge">saveHierarchyState()</code> 中调用 <code class="language-plaintext highlighter-rouge">mContentParent</code>,也就是 Window 的 contentView 的父 ViewGroup,的 <code class="language-plaintext highlighter-rouge">saveHierarchyState(states)</code></li> <li><code class="language-plaintext highlighter-rouge">mContentParent</code> 的 <code class="language-plaintext highlighter-rouge">saveHierarchyState(states)</code> 会调用 <code class="language-plaintext highlighter-rouge">dispatchSaveInstanceState(container)</code></li> <li><code class="language-plaintext highlighter-rouge">mContentParent</code> 是一个 ViewGroup,它的 <code class="language-plaintext highlighter-rouge">dispatchSaveInstanceState(container)</code> 会先保存自己的状态,然后调用每一个子 View 的 <code class="language-plaintext highlighter-rouge">dispatchSaveInstanceState(container)</code></li> <li>对于一个 View 的 <code class="language-plaintext highlighter-rouge">dispatchSaveInstanceState(container)</code> 中会调用 <code class="language-plaintext highlighter-rouge">onSaveInstanceState()</code></li> <li>ScrollView 的 <code class="language-plaintext highlighter-rouge">onSaveInstanceState()</code> 中保存了滚动位置信息</li> </ul> <p>这个图展示了 View 部分的 instance state 调用顺序:</p> <p><img src="/assets/img/2017-11-28-android-instance-state/instance-state.png" alt="instanceState" /></p> <p>因此,我们啥也不写,ScrollView 也能正确地保存和恢复自己的状态。</p> <p>注意一点:想要一个 View 的状态被自动保存和恢复,必须设置 <code class="language-plaintext highlighter-rouge">setSaveEnabled(true)</code>(默认情况下这个 flag 就是 <code class="language-plaintext highlighter-rouge">true</code> 的,所以这个用来阻止一个 view 自动保存状态时使用),还<strong>必须为这个 View 设置 id</strong>,通常是在 xml 中 <code class="language-plaintext highlighter-rouge">android:id="@+id/..."</code> 的形式。如果没有设置 id,View 将不会自动处理状态的保存与恢复。原因可以查看上面的 View 的 <code class="language-plaintext highlighter-rouge">dispatchSaveInstanceState</code> 代码,一开始就进行了判断:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="kt">void</span> <span class="nf">dispatchSaveInstanceState</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mID</span> <span class="o">!=</span> <span class="no">NO_ID</span> <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="n">mViewFlags</span> <span class="o">&amp;</span> <span class="no">SAVE_DISABLED_MASK</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="nc">Parcelable</span> <span class="n">state</span> <span class="o">=</span> <span class="n">onSaveInstanceState</span><span class="o">();</span> <span class="c1">// ...</span> <span class="k">if</span> <span class="o">(</span><span class="n">state</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Log.i("View", "Freezing #" + Integer.toHexString(mID)</span> <span class="c1">// + ": " + state);</span> <span class="n">container</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">mID</span><span class="o">,</span> <span class="n">state</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>这里首先就判断了 id 不为空且不允许添加 <code class="language-plaintext highlighter-rouge">SAVE_DISABLED_MASK</code> flag.</p> <p>View 的 id 是作为这个用于存储状态的 <code class="language-plaintext highlighter-rouge">SparseArray&lt;Parcelable&gt;</code> 的 key 使用的,如果没有设置 id,所有未设置 id 的 View 的 key 都使用 <code class="language-plaintext highlighter-rouge">-1</code> 显然是不可能的。</p> <h1 id="自定义-view-的状态的保存与恢复">自定义 View 的状态的保存与恢复.</h1> <p>其实对于 Activity 的 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 理解和使用起来还是比较简单的,无非是往传入的 <code class="language-plaintext highlighter-rouge">savedInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">outState</code> 这两个 <code class="language-plaintext highlighter-rouge">Bundle</code> 对象中读写可序列化的对象或基本数据类型变量。</p> <p>但我们为什么需要自定义 View 处理 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 呢?</p> <p>在用途上,Activity 和 Fragment 应该保存的是与业务相关的状态。如果所有 View 的状态都交由 Activity 和 Fragment 处理,显然是不可行的,因此就需要 View 自身管理自身的状态,正确的保存和恢复。</p> <p>它和 Activity 的区别还在于方法签名的不同导致的用法上的区别:</p> <p>Activity 的:<code class="language-plaintext highlighter-rouge">void onRestoreInstanceState(Bundle savedInstanceState)</code> 和 <code class="language-plaintext highlighter-rouge">void onSaveInstanceState(Bundle outState)</code> View 的:<code class="language-plaintext highlighter-rouge">void onRestoreInstanceState(Parcelable state)</code> 和 <code class="language-plaintext highlighter-rouge">Parcelable onSaveInstanceState()</code> <em>以及 Fragment 的:<code class="language-plaintext highlighter-rouge">void onViewStateRestored(Bundle savedInstanceState)</code> 和 <code class="language-plaintext highlighter-rouge">onSaveInstanceState(Bundle outState)</code>,fragment 与 activity 的差别只在方法名上,使用起来和 activity 是一样的。</em></p> <p>Activity 的参数 <code class="language-plaintext highlighter-rouge">Bundle</code> 是一个非抽象类,且都是通过参数传入,使用时只需要调用 <code class="language-plaintext highlighter-rouge">Bundle</code> 已有的一堆方法。 View 的参数 <code class="language-plaintext highlighter-rouge">Parcelable</code> 是一个接口,<strong>没有多少可调用的方法</strong>,且 <code class="language-plaintext highlighter-rouge">onSaveInstanceState()</code> 时需要返回一个 <code class="language-plaintext highlighter-rouge">Parcelable</code>。</p> <p>所以它使用起来是这样的:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 需要被保存的自定义状态.</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">mCustomState</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onRestoreInstanceState</span><span class="o">(</span><span class="nc">Parcelable</span> <span class="n">state</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 强转 state 为 SavedState 类型,这是安全的,因为保存状态时提供的也是这个类型。</span> <span class="nc">SavedState</span> <span class="n">ss</span> <span class="o">=</span> <span class="o">(</span><span class="nc">SavedState</span><span class="o">)</span> <span class="n">state</span><span class="o">;</span> <span class="c1">// 恢复 super 状态。</span> <span class="kd">super</span><span class="o">.</span><span class="na">onRestoreInstanceState</span><span class="o">(</span><span class="n">ss</span><span class="o">.</span><span class="na">getSuperState</span><span class="o">());</span> <span class="c1">// 恢复当前自定义的状态。</span> <span class="n">mCustomState</span> <span class="o">=</span> <span class="n">ss</span><span class="o">.</span><span class="na">customState</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="nc">Parcelable</span> <span class="nf">onSaveInstanceState</span><span class="o">()</span> <span class="o">{</span> <span class="c1">// 保存 super 状态。</span> <span class="nc">Parcelable</span> <span class="n">superState</span> <span class="o">=</span> <span class="kd">super</span><span class="o">.</span><span class="na">onSaveInstanceState</span><span class="o">();</span> <span class="c1">// 创建自定义的 SavedState 并传入 super 的状态和当前自定义的状态。</span> <span class="nc">SavedState</span> <span class="n">ss</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SavedState</span><span class="o">(</span><span class="n">superState</span><span class="o">,</span> <span class="n">mCustomState</span><span class="o">);</span> <span class="c1">// 返回 SavedState。</span> <span class="k">return</span> <span class="n">ss</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 自定义状态保存类。 */</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">SavedState</span> <span class="kd">extends</span> <span class="nc">BaseSavedState</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">customState</span><span class="o">;</span> <span class="cm">/** * 由 Parcelable 使用。 */</span> <span class="kd">public</span> <span class="nf">SavedState</span><span class="o">(</span><span class="nc">Parcel</span> <span class="n">source</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">(</span><span class="n">source</span><span class="o">);</span> <span class="n">customState</span> <span class="o">=</span> <span class="n">source</span><span class="o">.</span><span class="na">readString</span><span class="o">();</span> <span class="o">}</span> <span class="cm">/** * @param superState Super 的状态。 * @param customState 自定义的状态。 */</span> <span class="kd">public</span> <span class="nf">SavedState</span><span class="o">(</span><span class="nc">Parcelable</span> <span class="n">superState</span><span class="o">,</span> <span class="nc">String</span> <span class="n">customState</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">(</span><span class="n">superState</span><span class="o">);</span> <span class="k">this</span><span class="o">.</span><span class="na">customState</span> <span class="o">=</span> <span class="n">customState</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">writeToParcel</span><span class="o">(</span><span class="nc">Parcel</span> <span class="n">out</span><span class="o">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">writeToParcel</span><span class="o">(</span><span class="n">out</span><span class="o">,</span> <span class="n">flags</span><span class="o">);</span> <span class="n">out</span><span class="o">.</span><span class="na">writeString</span><span class="o">(</span><span class="n">customState</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Parcelable</span><span class="o">.</span><span class="na">Creator</span><span class="o">&lt;</span><span class="nc">SavedState</span><span class="o">&gt;</span> <span class="no">CREATOR</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Parcelable</span><span class="o">.</span><span class="na">Creator</span><span class="o">&lt;</span><span class="nc">SavedState</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="kd">public</span> <span class="nc">SavedState</span> <span class="nf">createFromParcel</span><span class="o">(</span><span class="nc">Parcel</span> <span class="n">in</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">SavedState</span><span class="o">(</span><span class="n">in</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="nc">SavedState</span><span class="o">[]</span> <span class="nf">newArray</span><span class="o">(</span><span class="kt">int</span> <span class="n">size</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nc">SavedState</span><span class="o">[</span><span class="n">size</span><span class="o">];</span> <span class="o">}</span> <span class="o">};</span> <span class="o">}</span> </code></pre></div></div> <p>上面我们说,一个 View 必须设置 id 才能被保存和恢复状态,如果同一个 ViewGroup 下的两个 View id 相同呢?</p> <p>这怎么可能呢?在 layout 文件中,两个 View id 相同会直接报错的。但是,如果使用了 <code class="language-plaintext highlighter-rouge">&lt;include&gt;</code> 标签,同时 include 了两个 layout 文件,它们中存在相同的 id,或者干脆我 include 同一个 layout 文件两次,那么这个问题就出现了。虽然这样的情况很少见,甚至 android 自带的 ViewGroup 都没有处理这种情况,但是我们还是要说一下。</p> <p>如果引用了两个不同的 layout 文件,它们中存在相同的 id,比较简单的方式是,直接修改 id 保证同一个 Activity 内的所有子 View id 都不相同。这需要项目自己做规范。</p> <p>如果引用了同一个 layout 文件多次,那么这种时候,修改 id 的方式就不可行了,当然,你还是可以复制出来一份 layout,然后修改,啊这不是我想说的。。</p> <p>有一种解决办法是自定义你所需要的 ViewGroup,修改其中保存子 View 状态的相关方法,然后 layout 文件中使用自定义 ViewGroup。</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">dispatchRestoreInstanceState</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 阻止 ViewGroup 恢复子 View 的状态,只让 ViewGroup 恢复自己的状态。</span> <span class="n">dispatchThawSelfOnly</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">dispatchSaveInstanceState</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 阻止 ViewGroup 保存子 View 的状态,只让 ViewGroup 保存自己的状态。</span> <span class="n">dispatchFreezeSelfOnly</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onRestoreInstanceState</span><span class="o">(</span><span class="nc">Parcelable</span> <span class="n">state</span><span class="o">)</span> <span class="o">{</span> <span class="nc">SavedState</span> <span class="n">ss</span> <span class="o">=</span> <span class="o">(</span><span class="nc">SavedState</span><span class="o">)</span> <span class="n">state</span><span class="o">;</span> <span class="kd">super</span><span class="o">.</span><span class="na">onRestoreInstanceState</span><span class="o">(</span><span class="n">ss</span><span class="o">.</span><span class="na">getSuperState</span><span class="o">());</span> <span class="c1">// 从自己创建的 SparseArray 恢复子 View 的状态,保证了子 View 的状态也在单独的 SparseArray 中。</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">getChildCount</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">getChildAt</span><span class="o">(</span><span class="n">i</span><span class="o">).</span><span class="na">restoreHierarchyState</span><span class="o">(</span><span class="n">ss</span><span class="o">.</span><span class="na">childrenStates</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="nc">Parcelable</span> <span class="nf">onSaveInstanceState</span><span class="o">()</span> <span class="o">{</span> <span class="nc">Parcelable</span> <span class="n">superState</span> <span class="o">=</span> <span class="kd">super</span><span class="o">.</span><span class="na">onSaveInstanceState</span><span class="o">();</span> <span class="nc">SavedState</span> <span class="n">ss</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SavedState</span><span class="o">(</span><span class="n">superState</span><span class="o">);</span> <span class="c1">// 保存到自己创建的 SparseArray 中。</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">getChildCount</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">getChildAt</span><span class="o">(</span><span class="n">i</span><span class="o">).</span><span class="na">saveHierarchyState</span><span class="o">(</span><span class="n">ss</span><span class="o">.</span><span class="na">childrenStates</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="n">ss</span><span class="o">;</span> <span class="o">}</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">SavedState</span> <span class="kd">extends</span> <span class="nc">BaseSavedState</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">childrenStates</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">SavedState</span><span class="o">(</span><span class="nc">Parcel</span> <span class="n">source</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">(</span><span class="n">source</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="nf">SavedState</span><span class="o">(</span><span class="nc">Parcel</span> <span class="n">source</span><span class="o">,</span> <span class="nc">ClassLoader</span> <span class="n">loader</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">(</span><span class="n">source</span><span class="o">);</span> <span class="n">childrenStates</span> <span class="o">=</span> <span class="n">source</span><span class="o">.</span><span class="na">readSparseArray</span><span class="o">(</span><span class="n">loader</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="nf">SavedState</span><span class="o">(</span><span class="nc">Parcelable</span> <span class="n">superState</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">(</span><span class="n">superState</span><span class="o">);</span> <span class="n">childrenStates</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SparseArray</span><span class="o">&lt;&gt;();</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ClassLoaderCreator</span><span class="o">&lt;</span><span class="nc">SavedState</span><span class="o">&gt;</span> <span class="no">CREATOR</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ClassLoaderCreator</span><span class="o">&lt;</span><span class="nc">SavedState</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">SavedState</span> <span class="nf">createFromParcel</span><span class="o">(</span><span class="nc">Parcel</span> <span class="n">source</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="nf">createFromParcel</span><span class="o">(</span><span class="n">source</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">SavedState</span> <span class="nf">createFromParcel</span><span class="o">(</span><span class="nc">Parcel</span> <span class="n">source</span><span class="o">,</span> <span class="nc">ClassLoader</span> <span class="n">loader</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">SavedState</span><span class="o">(</span><span class="n">source</span><span class="o">,</span> <span class="n">loader</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="nc">SavedState</span><span class="o">[]</span> <span class="nf">newArray</span><span class="o">(</span><span class="kt">int</span> <span class="n">size</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nc">SavedState</span><span class="o">[</span><span class="n">size</span><span class="o">];</span> <span class="o">}</span> <span class="o">};</span> <span class="o">}</span> </code></pre></div></div> <p>其中 <code class="language-plaintext highlighter-rouge">dispatchThawSelfOnly(container)</code> 和 <code class="language-plaintext highlighter-rouge">dispatchFreezeSelfOnly(container)</code> 的实现如下:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="kt">void</span> <span class="nf">dispatchThawSelfOnly</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">dispatchRestoreInstanceState</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="o">}</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">dispatchFreezeSelfOnly</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">dispatchSaveInstanceState</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">dispatchRestoreInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">dispatchSaveInstanceState</code> 的默认实现如下:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">dispatchRestoreInstanceState</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">dispatchRestoreInstanceState</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">mChildrenCount</span><span class="o">;</span> <span class="kd">final</span> <span class="nc">View</span><span class="o">[]</span> <span class="n">children</span> <span class="o">=</span> <span class="n">mChildren</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="nc">View</span> <span class="n">c</span> <span class="o">=</span> <span class="n">children</span><span class="o">[</span><span class="n">i</span><span class="o">];</span> <span class="k">if</span> <span class="o">((</span><span class="n">c</span><span class="o">.</span><span class="na">mViewFlags</span> <span class="o">&amp;</span> <span class="no">PARENT_SAVE_DISABLED_MASK</span><span class="o">)</span> <span class="o">!=</span> <span class="no">PARENT_SAVE_DISABLED</span><span class="o">)</span> <span class="o">{</span> <span class="n">c</span><span class="o">.</span><span class="na">dispatchRestoreInstanceState</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">dispatchSaveInstanceState</span><span class="o">(</span><span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">container</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">dispatchSaveInstanceState</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">mChildrenCount</span><span class="o">;</span> <span class="kd">final</span> <span class="nc">View</span><span class="o">[]</span> <span class="n">children</span> <span class="o">=</span> <span class="n">mChildren</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="nc">View</span> <span class="n">c</span> <span class="o">=</span> <span class="n">children</span><span class="o">[</span><span class="n">i</span><span class="o">];</span> <span class="k">if</span> <span class="o">((</span><span class="n">c</span><span class="o">.</span><span class="na">mViewFlags</span> <span class="o">&amp;</span> <span class="no">PARENT_SAVE_DISABLED_MASK</span><span class="o">)</span> <span class="o">!=</span> <span class="no">PARENT_SAVE_DISABLED</span><span class="o">)</span> <span class="o">{</span> <span class="n">c</span><span class="o">.</span><span class="na">dispatchSaveInstanceState</span><span class="o">(</span><span class="n">container</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>这两个方法就是<strong>放弃 ViewGroup 本来那些保存和恢复子 View 的操作,只简单地调用 ViewGroup 作为 View 自身应该保存和恢复的操作</strong>。</p> <p>还记得 PhoneWindow 中的代码吗:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">Bundle</span> <span class="nf">saveHierarchyState</span><span class="o">()</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;</span> <span class="n">states</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SparseArray</span><span class="o">&lt;</span><span class="nc">Parcelable</span><span class="o">&gt;();</span> <span class="n">mContentParent</span><span class="o">.</span><span class="na">saveHierarchyState</span><span class="o">(</span><span class="n">states</span><span class="o">);</span> <span class="c1">// ...</span> <span class="o">}</span> </code></pre></div></div> <p>从根 ViewGroup 开始遍历,所有的 View 的状态都被添加到这个名为 <code class="language-plaintext highlighter-rouge">states</code> 的 <code class="language-plaintext highlighter-rouge">SparseArray</code>,key 为 view id,value 为状态。这就是整个 layout 下的需要保存状态的 view 需要有 id 且 id 不能重复的原因。而在自定义的 ViewGroup 中,<strong>原先应该 put 进公共 <code class="language-plaintext highlighter-rouge">states</code> 中的子 View 的状态,被 put 进了自己创建的 <code class="language-plaintext highlighter-rouge">childrenStates</code> 这个 <code class="language-plaintext highlighter-rouge">SparseArray</code> 中,然后整个 ViewGroup 包括其子 View 的状态被 put 进公共的 <code class="language-plaintext highlighter-rouge">states</code> 中</strong>,只要 ViewGroup 的 id 不重复,其子 View 的 id 是可以与其他 ViewGroup 中子 View 的 id 相同的。而一个布局 include 两次同一个 layout 文件,它们也需要不同的 id,这就没有问题了。</p> <p>这是默认情况下 ViewGroup 的实现:</p> <p><img src="/assets/img/2017-11-28-android-instance-state/viewgroup.png" alt="ViewGroup" /></p> <p>这是自己管理子 View 状态的 ViewGroup 的实现:</p> <p><img src="/assets/img/2017-11-28-android-instance-state/viewgroup2.png" alt="ViewGroup" /></p> <p>系统提供的各个 ViewGroup 默认不会有这样的实现,是为了提高性能,这样的需求,只在必要时使用就好了。</p> <h1 id="persistentstate">persistentState</h1> <p>从 API 21 开始,Activity 中添加了几个方法:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** * @param persistentState if the activity is being re-initialized after * previously being shut down or powered off then this Bundle contains the data it most * recently supplied to outPersistentState in {@link #onSaveInstanceState}. * &lt;b&gt;&lt;i&gt;Note: Otherwise it is null.&lt;/i&gt;&lt;/b&gt; */</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="nc">Bundle</span> <span class="n">savedInstanceState</span><span class="o">,</span> <span class="nc">PersistableBundle</span> <span class="n">persistentState</span><span class="o">)</span> <span class="o">{}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onRestoreInstanceState</span><span class="o">(</span><span class="nc">Bundle</span> <span class="n">savedInstanceState</span><span class="o">,</span> <span class="nc">PersistableBundle</span> <span class="n">persistentState</span><span class="o">)</span> <span class="o">{}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSaveInstanceState</span><span class="o">(</span><span class="nc">Bundle</span> <span class="n">outState</span><span class="o">,</span> <span class="nc">PersistableBundle</span> <span class="n">outPersistentState</span><span class="o">)</span> <span class="o">{}</span> </code></pre></div></div> <p>从 api 文档上看,在 Manifest 中配置 <code class="language-plaintext highlighter-rouge">android:persistableMode="persistAcrossReboots"</code> 后,这几个方法的 <code class="language-plaintext highlighter-rouge">PersistableBundle</code> 是用来在手机重启后仍能恢复状态使用的。</p> <p>然而在我不成熟的小测试中,它们并没有什么卵用,且不同版本的设备上出现的问题也不相同。</p> <p>不过即便是它们的确起到了卵用,对于实际的开发而言也仍然没太大实际作用,如果真的希望更长久的保存 app 中的状态数据,应该考虑持久化存储,<code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 也只是用来暂存的,它<strong>不能保证数据不丢失</strong>(比如强制停止 app),真正重要的数据是不应该使用 instance state 的。</p> <h1 id="应用场景">应用场景</h1> <p>大家有没有发现,平时开发,似乎都不怎么注意到 instance state 的处理,不论是自定义 View,还是在 Activity 或 Fragment 中,都较少关注这几个方法参数。</p> <p>首先,Android 提供的原生控件,已经很好地帮我们实现的大部分情况下需要的 instance state 的保存与恢复。使用的大部分都是原生控件,不需要额外的处理。</p> <p>其次,Android 设备内存越来越大,大部分情况下不需要考虑 app 因系统内存紧张而被回收的情况,即使被回收,重新初始化所有数据就好,没有多少恢复状态的必要。并且,受各种因素影响,多数 app 基本 ui 模型都是底部 4 到 5 个 tab,这样的应用 ui 结构在横屏的显示效果是不太好的,也没有太大必要的,因此,大部分应用的屏幕方向都固定写死了。没有回收和屏幕旋转,平时我们很难注意到对状态的处理,因为它不是能很直观地看的出来的东西。</p> <p>因此就现有的应用结构,完全不处理 <code class="language-plaintext highlighter-rouge">onRestoreInstanceState</code> 和 <code class="language-plaintext highlighter-rouge">onSaveInstanceState</code> 也能应付大部分情况。</p> <p>当开发者选项中的“不保留活动”开启后,模拟系统回收 Activity 的场景,其实大部分 app 都有多多少少的适配问题。</p> <p><img src="/assets/img/2017-11-28-android-instance-state/jianshu-bug.gif" alt="简书 bug" /></p> <p><img src="/assets/img/2017-11-28-android-instance-state/maoyan.gif" alt="猫眼" /></p> <p>因此为了更好的用户体验,以及可能的部分页面横竖屏切换的需要(其实一个应用能支持横竖屏切换也是用户体验的一部分),理解掌握 instance state 还是有必要的。</p>今天我们来聊聊 onRestoreInstanceState 和 onSaveInstanceState 这两个非生命周期却为大家所熟知的方法。Kotlin 对 Android APK 包大小的影响2017-11-07T00:00:00+00:002017-11-07T00:00:00+00:00https://ebnbin.github.io/2017/11/07/kotlin-apk-size<p>使用两个空项目测试以 Java 和 Kotlin 作为开发语言生成的 APK 包的大小。</p> <p>两个项目都没有任何 <code class="language-plaintext highlighter-rouge">res</code> 资源文件( <code class="language-plaintext highlighter-rouge">AndroidManifest</code> 文件中全部使用默认值),都只有一个空的 <code class="language-plaintext highlighter-rouge">MainActivity</code> 文件,没有任何逻辑代码。Kotlin 项目中添加了 <code class="language-plaintext highlighter-rouge">kotlin-android</code> 和 <code class="language-plaintext highlighter-rouge">kotlin-android-extensions</code> 插件,引用了 <code class="language-plaintext highlighter-rouge">org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version</code> 库。Java 项目中不引用任何库。<code class="language-plaintext highlighter-rouge">build.gradle</code> 文件中使用 <code class="language-plaintext highlighter-rouge">minifyEnabled true</code> 控制代码压缩,使用 <code class="language-plaintext highlighter-rouge">useProguard false</code> 控制代码混淆,分别打 Release 包,测试结果如下(大小分别为 <code class="language-plaintext highlighter-rouge">.apk</code> 文件大小和 <code class="language-plaintext highlighter-rouge">.dex</code> 文件大小):</p> <table> <thead> <tr> <th style="text-align: left"> </th> <th style="text-align: center">Java</th> <th style="text-align: center">Kotlin</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">不压缩不混淆</td> <td style="text-align: center">5K (1K)</td> <td style="text-align: center">360K (1.5M)</td> </tr> <tr> <td style="text-align: left">压缩不混淆</td> <td style="text-align: center">5K (484B)</td> <td style="text-align: center">20K (5K)</td> </tr> <tr> <td style="text-align: left">压缩混淆</td> <td style="text-align: center">5K (432B)</td> <td style="text-align: center">18K (436B)</td> </tr> </tbody> </table> <p>应该可以理解为,Kotlin 语言的引入对 APK 包大小的影响,最大为 360K 左右,即 Kotlin 依赖库中的所有文件在项目中都用到了。一般情况下,使用 Kotlin 开发只会增加 APK 包 100~200K 的大小。其他的可能的影响就是 Kotlin 的语法糖对应的 Java 代码的复杂程度了。</p> <p>比如下面这个例子 <em>(以下测试代码都是在不压缩不混淆的配置下反编译的)</em>:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">kotlinx.android.synthetic.main.activity_main.helloButton</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">Activity</span><span class="p">()</span> <span class="p">{</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span> <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span> <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span> <span class="n">helloButton</span><span class="p">.</span><span class="n">text</span> <span class="p">=</span> <span class="s">"Hello"</span> <span class="n">helloButton</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span> <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"World"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>这个扩展插件 <code class="language-plaintext highlighter-rouge">Kotlin Android Extensions</code> 简化了 <code class="language-plaintext highlighter-rouge">findViewById</code> 的使用。</p> <p>反编译后得到的 Java 代码:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="kd">extends</span> <span class="nc">Activity</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">HashMap</span> <span class="n">_$_findViewCache</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">MainActivity</span><span class="o">()</span> <span class="o">{}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="n">_$_clearFindViewByIdCache</span><span class="o">()</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">_$_findViewCache</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">_$_findViewCache</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="nc">View</span> <span class="n">_$_findCachedViewById</span><span class="o">(</span><span class="kt">int</span> <span class="n">paramInt</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">_$_findViewCache</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">_$_findViewCache</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">();</span> <span class="o">}</span> <span class="nc">View</span> <span class="n">localView</span> <span class="o">=</span> <span class="o">(</span><span class="nc">View</span><span class="o">)</span><span class="n">_$_findViewCache</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="nc">Integer</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">paramInt</span><span class="o">));</span> <span class="k">if</span> <span class="o">(</span><span class="n">localView</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">localView</span> <span class="o">=</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">paramInt</span><span class="o">);</span> <span class="n">_$_findViewCache</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Integer</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">paramInt</span><span class="o">),</span> <span class="n">localView</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="n">localView</span><span class="o">;</span> <span class="o">}</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Bundle</span> <span class="n">paramBundle</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">paramBundle</span><span class="o">);</span> <span class="n">setContentView</span><span class="o">(</span><span class="mi">2130837504</span><span class="o">);</span> <span class="o">((</span><span class="nc">Button</span><span class="o">)</span><span class="n">_$_findCachedViewById</span><span class="o">(</span><span class="no">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">helloButton</span><span class="o">)).</span><span class="na">setText</span><span class="o">((</span><span class="nc">CharSequence</span><span class="o">)</span><span class="s">"Hello"</span><span class="o">);</span> <span class="o">((</span><span class="nc">Button</span><span class="o">)</span><span class="n">_$_findCachedViewById</span><span class="o">(</span><span class="no">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">helloButton</span><span class="o">)).</span><span class="na">setOnClickListener</span><span class="o">((</span><span class="nc">View</span><span class="o">.</span><span class="na">OnClickListener</span><span class="o">)</span><span class="k">new</span> <span class="nc">View</span><span class="o">.</span><span class="na">OnClickListener</span><span class="o">()</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">onClick</span><span class="o">(</span><span class="nc">View</span> <span class="n">paramAnonymousView</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Toast</span><span class="o">.</span><span class="na">makeText</span><span class="o">((</span><span class="nc">Context</span><span class="o">)</span><span class="k">this</span><span class="err">$</span><span class="mi">0</span><span class="o">,</span> <span class="o">(</span><span class="nc">CharSequence</span><span class="o">)</span><span class="s">"World"</span><span class="o">,</span> <span class="mi">0</span><span class="o">).</span><span class="na">show</span><span class="o">();</span> <span class="o">}</span> <span class="o">});</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>可以看出 <code class="language-plaintext highlighter-rouge">Kotlin Android Extensions</code> 为每一个 <code class="language-plaintext highlighter-rouge">Activity</code> 生成了一个名为 <code class="language-plaintext highlighter-rouge">_$_findViewCache</code> 的 <code class="language-plaintext highlighter-rouge">HashMap</code> 用于缓存 View id,每次通过 <code class="language-plaintext highlighter-rouge">kotlinx.android.synthetic.*.*.*</code> 获取 View 其实都是从 Cache 中获取。</p> <ul> <li>优点是书写方便。</li> <li>缺点是其实真正执行的代码反而变多了,并且灵活性下降。</li> <li>性能上,由于使用了缓存,所以基本上无差。</li> </ul>使用两个空项目测试以 Java 和 Kotlin 作为开发语言生成的 APK 包的大小。Kotlin 标识符2017-11-07T00:00:00+00:002017-11-07T00:00:00+00:00https://ebnbin.github.io/2017/11/07/kotlin-identifiers<p>简单地聊一聊 Kotlin 标识符。</p> <h2 id="java-允许而-kotlin-不允许的">Java 允许而 Kotlin 不允许的</h2> <ul> <li><code class="language-plaintext highlighter-rouge">_</code>、<code class="language-plaintext highlighter-rouge">__</code>、<code class="language-plaintext highlighter-rouge">___</code> 等只由下划线组成的标识符在 Kotlin 中是不允许的。但是复合在其他字符中使用是可以的。下划线在 Kotlin 中的使用有几个场景:</li> </ul> <blockquote> <ol> <li>Lambda 表达式中简写参数。</li> </ol> </blockquote> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 第一个参数是 View, 第二个参数是 MotionEvent. 但他们在函数中都不需要 (没引用), 所以可以使用 _.</span> <span class="n">view</span><span class="p">.</span><span class="nf">setOnTouchListener</span> <span class="p">{</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="p">-&gt;</span> <span class="c1">// Do nothing.</span> <span class="k">true</span> <span class="c1">// 这个是返回值, 在 Lambda 表达式中不需要也不能够写 return.</span> <span class="p">}</span> </code></pre></div></div> <blockquote> <ol> <li>提高数字常量可读性。</li> </ol> </blockquote> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">num</span> <span class="p">=</span> <span class="mi">1_000_000</span> <span class="kd">val</span> <span class="py">hex</span> <span class="p">=</span> <span class="mh">0xFF_EC_DE_5E</span> <span class="kd">val</span> <span class="py">bytes</span> <span class="p">=</span> <span class="mb">0b11010010_01101001_10010100_10010010</span> <span class="c1">// PS: Kotlin 不支持八进制数字, Java 中 0 开头的数字是八进制数字.</span> </code></pre></div></div> <blockquote> <ol> <li>数据类和解构声明中省略不需要的属性声明。</li> </ol> </blockquote> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">Person</span><span class="p">(</span><span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">age</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="kd">val</span> <span class="py">person</span> <span class="p">=</span> <span class="nc">Person</span><span class="p">(</span><span class="s">"Pichai"</span><span class="p">,</span> <span class="mi">45</span><span class="p">)</span> <span class="kd">val</span> <span class="p">(</span><span class="py">name</span><span class="p">,</span> <span class="py">_</span><span class="p">)</span> <span class="p">=</span> <span class="n">person</span> <span class="nf">println</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="c1">// Pichai</span> </code></pre></div></div> <ul> <li> <p><code class="language-plaintext highlighter-rouge">$</code> 这个字符在 Kotlin 中不能用在标识符中,它在 Kotlin 中在字符串模版中使用。例如 <code class="language-plaintext highlighter-rouge">"My name is $name!"</code>。</p> </li> <li> <p>Kotlin 中的关键字或修饰符(而 Java 中不是关键字的),例如 <code class="language-plaintext highlighter-rouge">in</code>、<code class="language-plaintext highlighter-rouge">when</code>,在 Kotlin 中使用 <strong>`in`</strong>, <strong>`when`</strong>。其实 Kotlin 中所有标识符都可以在前后添加 <strong>`</strong> 来使用,就是为了在任何 Koltin 本身不允许作为标识符的情况下做兼容,比如 <strong>`_`</strong> 相当于 Java 中的 <code class="language-plaintext highlighter-rouge">_</code>。当使用的标识符在当前语境下不产生歧义时,关键字或修饰符也可以作为标识符,比如 <code class="language-plaintext highlighter-rouge">data</code> 在 Kotlin 中是可以用来定义数据类(<code class="language-plaintext highlighter-rouge">data class</code>)的修饰符,但是 <code class="language-plaintext highlighter-rouge">var data = 0</code> 这么写是允许的。</p> </li> </ul> <h2 id="kotlin-允许而-java-不允许的">Kotlin 允许而 Java 不允许的</h2> <p>毕竟 Java 是老大,Kotlin 要向 Java 兼容而不是反过来,所以当 Kotlin 中可用而 Java 中不允许的标识符出现时(例如 Java 中的关键字在 Kotlin 中不是关键字的),那么 Kotlin 就要自己通过添加前缀修饰符的方式在字节码层面做兼容了。</p> <h2 id="中文标识符">中文标识符</h2> <p>这在 Kotlin 和 Java 中都允许但 = =。。。 <strong>永远别用!!</strong></p>简单地聊一聊 Kotlin 标识符。Kotlin 的 Getter 和 Setter2017-11-06T00:00:00+00:002017-11-06T00:00:00+00:00https://ebnbin.github.io/2017/11/06/kotlin-getter-setter<p>关于 Kotlin 的 getter 和 setter 的基础语法就不详细介绍了。但是就是这么一个简单的概念,却有很多容易让人忽略细节。如果不理解 Kotlin 的语法在编译到字节码后是什么样的,你可能无法真正用好 getter 和 setter,先来个例子:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="kd">val</span> <span class="py">foo</span> <span class="p">=</span> <span class="nf">calcValue</span><span class="p">(</span><span class="s">"foo"</span><span class="p">)</span> <span class="k">private</span> <span class="kd">val</span> <span class="py">bar</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="nf">calcValue</span><span class="p">(</span><span class="s">"bar"</span><span class="p">)</span> <span class="k">private</span> <span class="k">fun</span> <span class="nf">calcValue</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"Calculating $name"</span><span class="p">)</span> <span class="k">return</span> <span class="mi">3</span> <span class="p">}</span> </code></pre></div></div> <p>是的唯一的不同是添加了 <code class="language-plaintext highlighter-rouge">get()</code>,这两个属性在访问时的行为却不一致了。</p> <p>属性 <code class="language-plaintext highlighter-rouge">foo</code> 只有第一次使用时会调用 <code class="language-plaintext highlighter-rouge">calcValue()</code>,属性 <code class="language-plaintext highlighter-rouge">bar</code> 每一次使用是都会调用 <code class="language-plaintext highlighter-rouge">calcValue()</code>。可以通过反编译后的 Java 代码明确看出区别:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">foo</span> <span class="o">=</span> <span class="n">calcValue</span><span class="o">(</span><span class="s">"foo"</span><span class="o">);</span> <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">calcValue</span><span class="o">(</span><span class="nc">String</span> <span class="n">paramString</span><span class="o">)</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">str</span> <span class="o">=</span> <span class="s">"Calculating "</span> <span class="o">+</span> <span class="n">paramString</span><span class="o">;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str</span><span class="o">);</span> <span class="k">return</span> <span class="mi">3</span><span class="o">;</span> <span class="o">}</span> <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getBar</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="nf">calcValue</span><span class="o">(</span><span class="s">"bar"</span><span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <hr /> <p>一个简单的访问权限修饰符也能导致其对应的 Java 代码的较大的差别:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="kd">val</span> <span class="py">foo</span> <span class="p">=</span> <span class="mi">3</span> <span class="kd">val</span> <span class="py">bar</span> <span class="p">=</span> <span class="mi">3</span> </code></pre></div></div> <p>反编译后:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">bar</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span> <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">foo</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getBar</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">bar</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <hr /> <p>在 Kotlin 中,类中没有字段这一概念,所有形如 Java 中 <code class="language-plaintext highlighter-rouge">field</code> 的变量在 Kotlin 中都叫 <strong>属性</strong>,它和 <strong>字段</strong> 的区别是所有的属性都有对应的 getter 和 setter,<code class="language-plaintext highlighter-rouge">val</code> 属性没有 setter,<code class="language-plaintext highlighter-rouge">private</code> 属性在编译成可能省略 getter 和 setter。这体现在,Kotlin 代码中的所谓 “字段” 在 Java 文件中使用时是无法直接调用的,必须通过 getXxx() 和 setXxx(value) 调用 Kotlin 中名为 xxx 的属性。还体现在,Kotlin 代码中如果已经存在名为 xxx 的属性,就不允许再创建名为 getXxx() 或 setXxx(value) 的方法。</p> <p>因此,在 Kotlin 中的一个 <strong>有初始化值</strong> 的属性对应了 Java 中的一个 <code class="language-plaintext highlighter-rouge">private</code> 字段加上其相应的 getter 和 setter。这个字段一定是 <code class="language-plaintext highlighter-rouge">private</code> 的,属性的修饰符体现在对应的 getter 和 setter 上。</p> <p><code class="language-plaintext highlighter-rouge">var</code> 和 <code class="language-plaintext highlighter-rouge">val</code> 的唯一区别在于 <code class="language-plaintext highlighter-rouge">var</code> 既有 getter 又有 setter,<code class="language-plaintext highlighter-rouge">val</code> 只有 getter 没有 setter。</p> <p><code class="language-plaintext highlighter-rouge">val</code> 与 Java 中的 <code class="language-plaintext highlighter-rouge">final</code> 不完全相同。只有一种情况它们完全相同,就是像 <code class="language-plaintext highlighter-rouge">private val foo = 3</code> 这样,<strong>以 <code class="language-plaintext highlighter-rouge">private</code> 修饰</strong>,<strong>赋予初始值</strong>,<strong>并且不提供自定义 getter</strong>,<strong>不被委托</strong>的属性,这个属性完全等同于 Java 中的 <code class="language-plaintext highlighter-rouge">private final int foo = 3;</code>。</p> <hr /> <p>再回顾上面第一个例子,我们把代码简化一下,关注这个 <code class="language-plaintext highlighter-rouge">get()</code>:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">foo</span> <span class="p">=</span> <span class="mi">3</span> <span class="kd">val</span> <span class="py">bar</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="mi">3</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">foo</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getBar</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="mi">3</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getFoo</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">foo</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <p>注意到,被 <code class="language-plaintext highlighter-rouge">private</code> 修饰的 Kotlin 属性在编译成字节码后省略了 getter 和 setter,因为 <code class="language-plaintext highlighter-rouge">private</code> 属性的非自定义 getter 和 setter 的存在是没有意义的,省略 getter 和 setter 是为了节省调用方法的开销。就如在 Java 中,如果你的 getter 和 setter 只是简单的赋值和返回,那么调用 getXxx() 和 setXxx(value) 和直接使用字段有什么差别呢?我个人认为 Java 中无意义的 getter 和 setter 是非常糟糕的实践,我们每个人在初学 Java 时都被告知,类里的字段不应该是 <code class="language-plaintext highlighter-rouge">public</code> 的,最好要被 <code class="language-plaintext highlighter-rouge">private</code> 修饰,然后提供对应的 getXxx() 和 setXxx(value),所以每一个 Java 初学者都习惯了这种写法,却不知道为什么!!??为了封装?封装什么?不还是通过 getXxx() 原样暴露出去了吗?</p> <p>渐渐地我们发现,Java 中的 getter 和 setter <strong>不是没有意义的</strong>,<strong>只是经常被滥用了</strong>。只有当一个字段的 getter 和 setter <strong>在意义上</strong> 需要被自定义,它才有存在的必要,对于一个纯 Model,赋值取值时不做校验且需要对外暴露的字段,或者在意义上就是共享的字段,强行封装一层 getter 和 setter 只会导致冗余没有任何意义。</p> <hr /> <p>Java 中与字段分离的 getter 和 setter 有两个缺点,第一书写相对麻烦,这个不多说了,第二:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">int</span> <span class="n">x</span><span class="o">;</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">y</span><span class="o">;</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getX</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">x</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getY</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">y</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setX</span><span class="o">(</span><span class="kt">int</span> <span class="n">x</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">x</span> <span class="o">==</span> <span class="n">x</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">this</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">x</span><span class="o">;</span> <span class="n">onPositionChanged</span><span class="o">();</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setY</span><span class="o">(</span><span class="kt">int</span> <span class="n">y</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">y</span> <span class="o">==</span> <span class="n">y</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">this</span><span class="o">.</span><span class="na">y</span> <span class="o">=</span> <span class="n">y</span><span class="o">;</span> <span class="n">onPositionChanged</span><span class="o">();</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setPosition</span><span class="o">(</span><span class="kt">int</span> <span class="n">x</span><span class="o">,</span> <span class="kt">int</span> <span class="n">y</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">x</span> <span class="o">==</span> <span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="o">.</span><span class="na">y</span> <span class="o">==</span> <span class="n">y</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">this</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">x</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">y</span> <span class="o">=</span> <span class="n">y</span><span class="o">;</span> <span class="n">onPositionChanged</span><span class="o">();</span> <span class="o">}</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onPositionChanged</span><span class="o">()</span> <span class="o">{</span> <span class="o">}</span> </code></pre></div></div> <p>OK 我很有可能在类内部忘记调用 <code class="language-plaintext highlighter-rouge">setX(x)</code> 而直接使用 <code class="language-plaintext highlighter-rouge">x = 100</code>,那么 <code class="language-plaintext highlighter-rouge">onPositionChanged</code> 回调不就被我遗漏了吗?这时候有两种说辞:</p> <ul> <li>- 我可能就是不需要 <code class="language-plaintext highlighter-rouge">onPositionChanged</code> 回调啊!- 那在业务复杂的时候你自己不会混乱什么时候调用 x 什么时候调用 getX() 吗?它们俩为什么意义不同呢?如果真的有这个必要,不应该以更明确的方式写出来吗?</li> <li>- 那我每次都调用 setX(x) 呗!- 既然如此,为啥非得以这样的麻烦语法去写它?IDE 提供的 <code class="language-plaintext highlighter-rouge">Getter and Setter Generator</code> 就已经说明了——“这是个语言设计缺陷,这么麻烦的操作让我用外部工具帮你批量完成它吧!”</li> </ul> <hr /> <p>Kotlin 的 getter 和 setter 是与属性绑定的:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="py">foo</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="o">..</span><span class="p">.</span> <span class="k">protected</span> <span class="k">set</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">=</span> <span class="o">..</span><span class="p">.</span> <span class="kd">val</span> <span class="py">bar</span> <span class="k">get</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span> </code></pre></div></div> <ul> <li> <p>如果没有权限修饰符修饰 getter 和 setter,那么默认为属性的修饰符,getter 和 setter 的修饰符不能 <strong>大于</strong> 属性的修饰符,比如被 <code class="language-plaintext highlighter-rouge">protected</code> 修饰的属性的 getter 和 setter 不能被 <code class="language-plaintext highlighter-rouge">public</code> 修饰。</p> </li> <li> <p>不能同时存在两个形如 <code class="language-plaintext highlighter-rouge">foo</code> 和 <code class="language-plaintext highlighter-rouge">Foo</code> 的字段,因为他们在 Java 中的 getter 和 setter 调用方法签名冲突。</p> </li> <li> <p>如果 Kotlin 中的属性使用了 Java 的关键字(但非 Kotlin 的关键字,比如 default、new),那么 Kotlin 会为对应的 Java 字段添加一个前缀,比如 <code class="language-plaintext highlighter-rouge">jdField_</code>,在 Kotlin 中的 <code class="language-plaintext highlighter-rouge">default</code> 字段反编译后得到 Java 中的 <code class="language-plaintext highlighter-rouge">jdField_default</code>,但是其 getter 还是 <code class="language-plaintext highlighter-rouge">getDefault()</code>,如果在 Kotlin 中再添加一个名为 <code class="language-plaintext highlighter-rouge">jdField_default</code> 的字段,编译运行都不会出问题,但是反编译出来的 Java 文件中出现了两个重名的字段,不确定是不是我使用的反编译工具的问题。。。不过这个问题要想做兼容并不困难,具体就不再深究。代码如下:</p> </li> </ul> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="py">default</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span> <span class="kd">var</span> <span class="py">jdField_default</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span> <span class="kd">var</span> <span class="py">new</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">int</span> <span class="n">jdField_default</span><span class="o">;</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">jdField_default</span><span class="o">;</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">jdField_new</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getDefault</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">jdField_default</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getJdField_default</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">jdField_default</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getNew</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">jdField_new</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">setDefault</span><span class="o">(</span><span class="kt">int</span> <span class="n">paramInt</span><span class="o">)</span> <span class="o">{</span> <span class="n">jdField_default</span> <span class="o">=</span> <span class="n">paramInt</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">setJdField_default</span><span class="o">(</span><span class="kt">int</span> <span class="n">paramInt</span><span class="o">)</span> <span class="o">{</span> <span class="n">jdField_default</span> <span class="o">=</span> <span class="n">paramInt</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">setNew</span><span class="o">(</span><span class="kt">int</span> <span class="n">paramInt</span><span class="o">)</span> <span class="o">{</span> <span class="n">jdField_new</span> <span class="o">=</span> <span class="n">paramInt</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <hr /> <p>如果你确定你已经理解了 Kotlin 属性,来看最后这个例子:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="py">a</span> <span class="p">=</span> <span class="mi">3</span> <span class="c1">// var a: Int = 3</span> <span class="c1">// 省略可推断类型.</span> <span class="kd">var</span> <span class="py">b</span> <span class="p">=</span> <span class="mi">3</span> <span class="c1">// Warning: Redundant setter.</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="n">field</span> <span class="c1">// Warning: Redundant setter.</span> <span class="k">set</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="n">field</span> <span class="p">=</span> <span class="n">value</span> <span class="p">}</span> <span class="c1">// 编译错误: Property must be initialized or be abstract.</span> <span class="c1">// var c: Int</span> <span class="c1">// 编译错误: Property must be initialized.</span> <span class="c1">// var d</span> <span class="c1">// get() = 3</span> <span class="kd">val</span> <span class="py">e</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="mi">3</span> <span class="kd">var</span> <span class="py">f</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="mi">3</span> <span class="c1">// Warning: Redundant setter.</span> <span class="k">set</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{}</span> </code></pre></div></div> <ul> <li>属性 <code class="language-plaintext highlighter-rouge">a</code> 和属性 <code class="language-plaintext highlighter-rouge">b</code> 在字节码层面 <strong>完全一致</strong>,属性 <code class="language-plaintext highlighter-rouge">b</code> 中的 getter 和 setter 写与不写完全没差。</li> <li>对比属性 <code class="language-plaintext highlighter-rouge">d</code> 和属性 <code class="language-plaintext highlighter-rouge">e</code>,为什么 <code class="language-plaintext highlighter-rouge">d</code> 编译错误呢,因为我们上面说过属性 <code class="language-plaintext highlighter-rouge">b</code> 的 setter 完全可以省略,意味着属性 <code class="language-plaintext highlighter-rouge">a</code> 的 setter 其实就长属性 <code class="language-plaintext highlighter-rouge">b</code> 的 setter 那样,只是被省略了,那对于这样一个默认的 setter,如果属性 <code class="language-plaintext highlighter-rouge">e</code> 不初始化,setter 中的 <code class="language-plaintext highlighter-rouge">field</code> 字段从何而来?<code class="language-plaintext highlighter-rouge">field</code> 是一个代表了当前属性的 <strong>幕后字段</strong> ,它的作用域只限于当前 setter(getter 中也有一个 <code class="language-plaintext highlighter-rouge">field</code>),由于 Kotlin 中的类属性不像 Java 中的类字段,Kotlin 中的类属性是不提供默认初始化的,必须手动指定其默认值,因此属性 <code class="language-plaintext highlighter-rouge">e</code> 必须被初始化,setter 中的 <code class="language-plaintext highlighter-rouge">field</code> 被访问时才有值。而对于 <code class="language-plaintext highlighter-rouge">val</code> 属性 <code class="language-plaintext highlighter-rouge">e</code>,没有 setter,虽然 getter 中也可以存在 <code class="language-plaintext highlighter-rouge">field</code> 幕后字段,但是这里直接返回 <code class="language-plaintext highlighter-rouge">3</code> 没有用到 <code class="language-plaintext highlighter-rouge">field</code>,所以属性 <code class="language-plaintext highlighter-rouge">e</code> 这么写是没有问题的。</li> <li>对比属性 <code class="language-plaintext highlighter-rouge">d</code> 和属性 <code class="language-plaintext highlighter-rouge">f</code>,多了个 setter,覆盖了默认的 <code class="language-plaintext highlighter-rouge">field = value</code> 的 setter,那么 <code class="language-plaintext highlighter-rouge">field</code> 也就没有引用了,<code class="language-plaintext highlighter-rouge">f</code> 的初始化也就可以省略了。</li> <li>属性 <code class="language-plaintext highlighter-rouge">f</code> 的写法与直接写两个名为 <code class="language-plaintext highlighter-rouge">getF()</code> 和 <code class="language-plaintext highlighter-rouge">setF(value)</code> 的方法在字节码完全没区别。</li> <li>像属性 <code class="language-plaintext highlighter-rouge">a</code>、属性 <code class="language-plaintext highlighter-rouge">b</code> 这种带初始化值的属性,在 Java 字节码会创建对应的字段,而像属性 <code class="language-plaintext highlighter-rouge">e</code>、属性 <code class="language-plaintext highlighter-rouge">f</code> 这样不带初始化值的属性,就不会有对应的字段被生成,原因很简单,它们不需要幕后字段 <code class="language-plaintext highlighter-rouge">field</code>,这个幕后字段其实就是对应到 Java 中的类字段,在 Kotlin 中,这样的幕后字段是用来存储属性真正的值的,除了 getter 和 setter 外部访问不到。如果 getter 和 setter 都不需要,那它就会被省略。</li> <li>最后需要注意的一点:属性 <code class="language-plaintext highlighter-rouge">b</code> 的 setter 所带的 warning,和属性 <code class="language-plaintext highlighter-rouge">f</code> 的 setter 所带的 warning,虽然都提示 <code class="language-plaintext highlighter-rouge">Redundant setter.</code>,但它们意义不同。前者是说,“你不写这个 setter 我也会默认帮你做一样的事情 <code class="language-plaintext highlighter-rouge">field = value</code>”,后者是说,“你写了一个啥事情都没做一条语句都没有的空方法”,后者的 setter 也会被 IDE 提示“可删除”,<strong>但是如果删除了,逻辑是不同的</strong>。</li> </ul> <p>以上这些分析,可以很容易从下面反编译出的 Java 代码得到验证:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span> <span class="kd">private</span> <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getA</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">a</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getB</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">b</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getE</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="mi">3</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">getF</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="mi">3</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">setA</span><span class="o">(</span><span class="kt">int</span> <span class="n">paramInt</span><span class="o">)</span> <span class="o">{</span> <span class="n">a</span> <span class="o">=</span> <span class="n">paramInt</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">setB</span><span class="o">(</span><span class="kt">int</span> <span class="n">paramInt</span><span class="o">)</span> <span class="o">{</span> <span class="n">b</span> <span class="o">=</span> <span class="n">paramInt</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">setF</span><span class="o">(</span><span class="kt">int</span> <span class="n">paramInt</span><span class="o">)</span> <span class="o">{}</span> </code></pre></div></div> <h2 id="总结">总结</h2> <ul> <li>Kotlin 中的类 <strong>属性</strong> 对映 Java 中的类 <strong>字段</strong> 加上其对应的 getter 和 setter。</li> <li>属性的访问权限修饰符作用于其 getter 和 setter,getter 和 setter 的权限不能大于属性的权限,对应的 Java 字段始终为 <code class="language-plaintext highlighter-rouge">private</code> 权限。</li> <li><code class="language-plaintext highlighter-rouge">val</code> 属性没有 setter,形如 <code class="language-plaintext highlighter-rouge">private val a = 0</code> 的属性与 Java 中的 <code class="language-plaintext highlighter-rouge">private final</code> 字段完全相同。</li> <li>幕后字段 <code class="language-plaintext highlighter-rouge">field</code>,用来在 getter 和 setter 中访问当前属性,相当于 Java 中的 <strong>private 字段</strong>。</li> <li>一定条件下属性可以不赋予初始化值,对应在 Java 中不会生成字段,只会生成 getter 和 setter。</li> </ul> <p>好了,现在随便写一个 Kotlin 属性,脑中马上就能反映出来对应的 Java 代码了吧!</p>关于 Kotlin 的 getter 和 setter 的基础语法就不详细介绍了。但是就是这么一个简单的概念,却有很多容易让人忽略细节。如果不理解 Kotlin 的语法在编译到字节码后是什么样的,你可能无法真正用好 getter 和 setter,先来个例子:Kotlin 委托属性2017-10-23T00:00:00+00:002017-10-23T00:00:00+00:00https://ebnbin.github.io/2017/10/23/kotlin-delegated-properties<p>简单的说,委托属性就是将一个属性的操作委托给一个委托类的实例处理,多个属性可以委托给同一个委托类。</p> <p>跟没说一样。。</p> <h2 id="委托类">委托类</h2> <p>先看一个简单的例子。</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Delegate</span> <span class="p">{</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">getValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;):</span> <span class="nc">String</span> <span class="p">{</span> <span class="k">return</span> <span class="s">"${property.name}: $thisRef"</span> <span class="p">}</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">setValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"value=$value"</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">class</span> <span class="nc">Test</span> <span class="p">{</span> <span class="kd">var</span> <span class="py">s</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegate</span><span class="p">()</span> <span class="p">}</span> <span class="kd">val</span> <span class="py">test</span> <span class="p">=</span> <span class="nc">Test</span><span class="p">()</span> <span class="nf">println</span><span class="p">(</span><span class="n">test</span><span class="p">.</span><span class="n">s</span><span class="p">)</span> <span class="c1">// s: Test@4eec7777</span> <span class="n">test</span><span class="p">.</span><span class="n">s</span> <span class="p">=</span> <span class="s">"hello"</span> <span class="c1">// value=hello</span> </code></pre></div></div> <ul> <li> <p>委托类类名任意。</p> </li> <li> <p>如果被 <code class="language-plaintext highlighter-rouge">val</code> 属性委托,必须提供 <code class="language-plaintext highlighter-rouge">getValue</code> 方法,如果被 <code class="language-plaintext highlighter-rouge">var</code> 属性委托,必须提供 <code class="language-plaintext highlighter-rouge">getValue</code> 和 <code class="language-plaintext highlighter-rouge">setValue</code> 方法。委托类中的其他属性和方法任意。</p> </li> <li> <p><code class="language-plaintext highlighter-rouge">getValue</code>,<code class="language-plaintext highlighter-rouge">setValue</code> 的方法签名,参考对应接口:<code class="language-plaintext highlighter-rouge">kotlin.properties.ReadOnlyProperty</code> 和 <code class="language-plaintext highlighter-rouge">kotlin.properties.ReadWriteProperty</code>。</p> </li> </ul> <h2 id="thisref-参数"><code class="language-plaintext highlighter-rouge">thisRef</code> 参数</h2> <p>官方对 <code class="language-plaintext highlighter-rouge">thisRef</code> 参数的要求:</p> <blockquote> <p>thisRef —— 必须与 <strong>属性所有者</strong> 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型</p> </blockquote> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">DelegateTest</span> <span class="p">{</span> <span class="k">private</span> <span class="kd">var</span> <span class="py">s1</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegate</span><span class="p">()</span> <span class="k">fun</span> <span class="nf">test1</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="n">s1</span><span class="p">)</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">test2</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="py">s2</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegate</span><span class="p">()</span> <span class="nf">println</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span> <span class="p">}</span> <span class="k">companion</span> <span class="k">object</span> <span class="p">{</span> <span class="k">private</span> <span class="kd">var</span> <span class="py">s3</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegate</span><span class="p">()</span> <span class="k">fun</span> <span class="nf">test3</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="n">s3</span><span class="p">)</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">test4</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="py">s4</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegate</span><span class="p">()</span> <span class="nf">println</span><span class="p">(</span><span class="n">s4</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="k">private</span> <span class="kd">var</span> <span class="py">s5</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegate</span><span class="p">()</span> <span class="k">fun</span> <span class="nf">test5</span><span class="p">()</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="n">s5</span><span class="p">)</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">test6</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="py">s6</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegate</span><span class="p">()</span> <span class="nf">println</span><span class="p">(</span><span class="n">s6</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>测试:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">test</span> <span class="p">=</span> <span class="nc">DelegateTest</span><span class="p">()</span> <span class="n">test</span><span class="p">.</span><span class="nf">test1</span><span class="p">()</span> <span class="n">test</span><span class="p">.</span><span class="nf">test2</span><span class="p">()</span> <span class="nc">DelegateTest</span><span class="p">.</span><span class="nf">test3</span><span class="p">()</span> <span class="nc">DelegateTest</span><span class="p">.</span><span class="nf">test4</span><span class="p">()</span> <span class="nf">test5</span><span class="p">()</span> <span class="nf">test6</span><span class="p">()</span> </code></pre></div></div> <p>Java 调用方式:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">DelegateTest</span> <span class="n">test</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DelegateTest</span><span class="o">();</span> <span class="n">test</span><span class="o">.</span><span class="na">test1</span><span class="o">();</span> <span class="n">test</span><span class="o">.</span><span class="na">test2</span><span class="o">();</span> <span class="nc">DelegateTest</span><span class="o">.</span><span class="na">Companion</span><span class="o">.</span><span class="na">test3</span><span class="o">();</span> <span class="nc">DelegateTest</span><span class="o">.</span><span class="na">Companion</span><span class="o">.</span><span class="na">test4</span><span class="o">();</span> <span class="nc">DelegateTestKt</span><span class="o">.</span><span class="na">test5</span><span class="o">();</span> <span class="nc">DelegateTestKt</span><span class="o">.</span><span class="na">test6</span><span class="o">();</span> </code></pre></div></div> <p>输出:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>s1: DelegateTest@7229724f s2: null s3: DelegateTest$Companion@4c873330 s4: null s5: null s6: null </code></pre></div></div> <p>可以看出,对于局部属性(在方法中声明的属性)或静态属性,<code class="language-plaintext highlighter-rouge">thisRef</code> 为 <code class="language-plaintext highlighter-rouge">null</code>,否则为属性所在对象。</p> <h2 id="委托类中的-setvalue">委托类中的 <code class="language-plaintext highlighter-rouge">setValue</code></h2> <p>如果委托一个 <code class="language-plaintext highlighter-rouge">var</code> 属性,希望保存属性上一次 <code class="language-plaintext highlighter-rouge">setValue</code> 的值,需要手动添加一个变量用于记录。</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">WeirdDelegate</span><span class="p">(</span><span class="n">initValue</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">""</span><span class="p">)</span> <span class="p">{</span> <span class="k">private</span> <span class="kd">var</span> <span class="py">localValue</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="n">initValue</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">getValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;):</span> <span class="nc">String</span> <span class="p">{</span> <span class="k">return</span> <span class="s">"This is NOT $localValue"</span> <span class="p">}</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">setValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span> <span class="n">localValue</span> <span class="p">=</span> <span class="n">value</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">var</span> <span class="py">fruit</span> <span class="k">by</span> <span class="nc">WeirdDelegate</span><span class="p">(</span><span class="s">"banana"</span><span class="p">)</span> <span class="n">fruit</span> <span class="p">=</span> <span class="s">"apple"</span> <span class="nf">println</span><span class="p">(</span><span class="n">fruit</span><span class="p">)</span> <span class="c1">// This is NOT apple</span> </code></pre></div></div> <h2 id="kotlin-标准委托">Kotlin 标准委托</h2> <p>Kotlin 自带一些默认的委托实现。</p> <h4 id="延迟属性-lazy">延迟属性 Lazy</h4> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">lazyValue</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nf">lazy</span> <span class="p">{</span> <span class="nf">print</span><span class="p">(</span><span class="s">"Calculating..."</span><span class="p">)</span> <span class="s">"world"</span> <span class="p">}</span> <span class="nf">println</span><span class="p">(</span><span class="n">lazyValue</span><span class="p">)</span> <span class="c1">// Calculating...world</span> <span class="nf">println</span><span class="p">(</span><span class="n">lazyValue</span><span class="p">)</span> <span class="c1">// world</span> </code></pre></div></div> <h4 id="可观察属性-observable">可观察属性 Observable</h4> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">User</span> <span class="p">{</span> <span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nc">Delegates</span><span class="p">.</span><span class="nf">observable</span><span class="p">(</span><span class="s">"&lt;no name&gt;"</span><span class="p">)</span> <span class="p">{</span> <span class="n">_</span><span class="p">,</span> <span class="n">old</span><span class="p">,</span> <span class="n">new</span> <span class="p">-&gt;</span> <span class="nf">println</span><span class="p">(</span><span class="s">"$old -&gt; $new"</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">val</span> <span class="py">user</span> <span class="p">=</span> <span class="nc">User</span><span class="p">()</span> <span class="n">user</span><span class="p">.</span><span class="n">name</span> <span class="p">=</span> <span class="s">"Steve"</span> <span class="c1">// &lt;no name&gt; -&gt; Steve</span> <span class="n">user</span><span class="p">.</span><span class="n">name</span> <span class="p">=</span> <span class="s">"Tim"</span> <span class="c1">// Steve -&gt; Tim</span> </code></pre></div></div> <h2 id="小结">小结</h2> <p>我们可以这么理解:因为 Kotlin 中类不能有字段,只有属性,<code class="language-plaintext highlighter-rouge">val</code> 声明的是只一个有 getter 没有 setter 的属性,<code class="language-plaintext highlighter-rouge">var</code> 声明的是一个既有 getter 又有 setter 的属性,可以通过:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="py">foo</span><span class="p">:</span> <span class="nc">Data</span> <span class="k">get</span><span class="p">()</span> <span class="p">{</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span> <span class="k">set</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span> </code></pre></div></div> <p>的方式自定义一个类属性的 getter 和 setter。如果有一批属性,他们都需要相同而复杂的 getter 和 setter,就可以通过委托属性实现,一个委托类可以帮助被委托的属性处理复杂的自定义 getter 和 setter 操作。</p> <h2 id="委托属性在-android-sharedpreferences-中的应用">委托属性在 Android SharedPreferences 中的应用</h2> <h4 id="java-版本">Java 版本</h4> <p>通常,我们这么写一个 SharedPreferences 工具类:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">PreferencesUtil</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">static</span> <span class="nc">PreferencesUtil</span> <span class="n">sInstance</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">sInstance</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">sInstance</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PreferencesUtil</span><span class="o">(</span><span class="n">context</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="nc">PreferencesUtil</span> <span class="nf">getInstance</span><span class="o">()</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">sInstance</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">RuntimeException</span><span class="o">(</span><span class="s">"Uninitialized."</span><span class="o">);</span> <span class="k">return</span> <span class="n">sInstance</span><span class="o">;</span> <span class="o">}</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">SharedPreferences</span> <span class="n">mSp</span><span class="o">;</span> <span class="kd">private</span> <span class="nf">PreferencesUtil</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="n">mSp</span> <span class="o">=</span> <span class="nc">PreferenceManager</span><span class="o">.</span><span class="na">getDefaultSharedPreferences</span><span class="o">(</span><span class="n">context</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getString</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="nc">String</span> <span class="n">defValue</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mSp</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">defValue</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">putString</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="nc">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="n">mSp</span><span class="o">.</span><span class="na">edit</span><span class="o">().</span><span class="na">putString</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">).</span><span class="na">apply</span><span class="o">();</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getInt</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="kt">int</span> <span class="n">defValue</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mSp</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">defValue</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">putInt</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="kt">int</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="n">mSp</span><span class="o">.</span><span class="na">edit</span><span class="o">().</span><span class="na">putInt</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">).</span><span class="na">apply</span><span class="o">();</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">long</span> <span class="nf">getLong</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="kt">long</span> <span class="n">defValue</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mSp</span><span class="o">.</span><span class="na">getLong</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">defValue</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">putLong</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="kt">long</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="n">mSp</span><span class="o">.</span><span class="na">edit</span><span class="o">().</span><span class="na">putLong</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">).</span><span class="na">apply</span><span class="o">();</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">float</span> <span class="nf">getFloat</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="kt">float</span> <span class="n">defValue</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mSp</span><span class="o">.</span><span class="na">getFloat</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">defValue</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">putFloat</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="kt">float</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="n">mSp</span><span class="o">.</span><span class="na">edit</span><span class="o">().</span><span class="na">putFloat</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">).</span><span class="na">apply</span><span class="o">();</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">getBoolean</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">defValue</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mSp</span><span class="o">.</span><span class="na">getBoolean</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">defValue</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">putBoolean</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="n">mSp</span><span class="o">.</span><span class="na">edit</span><span class="o">().</span><span class="na">putBoolean</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">).</span><span class="na">apply</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>然后这么用:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="nc">PreferencesUtil</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">getBoolean</span><span class="o">(</span><span class="nc">Constant</span><span class="o">.</span><span class="na">KEY_IS_FIRST_LAUNCH</span><span class="o">,</span> <span class="nc">Constant</span><span class="o">.</span><span class="na">DEF_IS_FIRST_LAUNCH</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// Do something first launch, like showing Welcome.</span> <span class="o">...</span> <span class="nc">PreferencesUtil</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">putBoolean</span><span class="o">(</span><span class="nc">Constant</span><span class="o">.</span><span class="na">KEY_IS_FIRST_LAUNCH</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <ul> <li> <p>创建一个 <code class="language-plaintext highlighter-rouge">PreferencesUtil</code> 单例,在 <code class="language-plaintext highlighter-rouge">Application</code> 中调用 <code class="language-plaintext highlighter-rouge">PreferencesUtil.init(context)</code> 初始化。</p> </li> <li> <p>代理 <code class="language-plaintext highlighter-rouge">SharedPreferences</code> 中的所有 <code class="language-plaintext highlighter-rouge">getXxx()</code>,<code class="language-plaintext highlighter-rouge">putXxx()</code> 方法,方便使用时不需要写 <code class="language-plaintext highlighter-rouge">getSharedPreferences().edit().putXxx().apply()</code> 这么长的代码。</p> </li> <li> <p>使用时调用 <code class="language-plaintext highlighter-rouge">PreferencesUtil.getInstance().getXxx(key, defValue)</code> 读取,调用 <code class="language-plaintext highlighter-rouge">PreferencesUtil.getInstance().putXxx(key, value)</code> 写入。</p> </li> <li> <p>写起来仍然很麻烦。每次 <code class="language-plaintext highlighter-rouge">getInstance()</code>,传 <code class="language-plaintext highlighter-rouge">key</code>,如果写入不同的 <code class="language-plaintext highlighter-rouge">SharedPreferences</code> 文件,还需要每次传文件名。</p> </li> <li> <p><code class="language-plaintext highlighter-rouge">getXxx()</code> 和 <code class="language-plaintext highlighter-rouge">putXxx()</code> 要保证 <code class="language-plaintext highlighter-rouge">key</code> 相同,会去字符串常量类中找,可能出错。</p> </li> <li> <p>处理的是 <code class="language-plaintext highlighter-rouge">SharedPreferences</code> 文件中的同一个 <code class="language-plaintext highlighter-rouge">key</code> 对应的 <code class="language-plaintext highlighter-rouge">value</code>,用的却是两次没有关联的 util 操作。</p> </li> </ul> <h4 id="kotlin-委托属性版本">Kotlin 委托属性版本</h4> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">PreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="k">private</span> <span class="kd">val</span> <span class="py">defValue</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span> <span class="k">private</span> <span class="kd">val</span> <span class="py">sp</span> <span class="k">by</span> <span class="nf">lazy</span> <span class="p">{</span> <span class="nc">PreferenceManager</span><span class="p">.</span><span class="nf">getDefaultSharedPreferences</span><span class="p">(</span><span class="nc">AppApplication</span><span class="p">.</span><span class="n">instance</span><span class="p">)</span> <span class="p">}</span> <span class="nd">@Suppress</span><span class="p">(</span><span class="s">"IMPLICIT_CAST_TO_ANY"</span><span class="p">,</span> <span class="s">"UNCHECKED_CAST"</span><span class="p">)</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">getValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;)</span> <span class="p">=</span> <span class="nf">with</span><span class="p">(</span><span class="n">sp</span><span class="p">)</span> <span class="p">{</span> <span class="k">when</span> <span class="p">(</span><span class="n">defValue</span><span class="p">)</span> <span class="p">{</span> <span class="k">is</span> <span class="nc">String</span> <span class="p">-&gt;</span> <span class="nf">getString</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="c1">// is Set&lt;*&gt; -&gt; getStringSet(key, defValue as Set&lt;String&gt;) // Unsupported.</span> <span class="k">is</span> <span class="nc">Int</span> <span class="p">-&gt;</span> <span class="nf">getInt</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Long</span> <span class="p">-&gt;</span> <span class="nf">getLong</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Float</span> <span class="p">-&gt;</span> <span class="nf">getFloat</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Boolean</span> <span class="p">-&gt;</span> <span class="nf">getBoolean</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">else</span> <span class="p">-&gt;</span> <span class="k">throw</span> <span class="nc">RuntimeException</span><span class="p">(</span><span class="s">"Unsupported type."</span><span class="p">)</span> <span class="p">}</span> <span class="k">as</span> <span class="nc">T</span> <span class="p">}</span> <span class="nd">@SuppressLint</span><span class="p">(</span><span class="s">"CommitPrefEdits"</span><span class="p">)</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">setValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">=</span> <span class="nf">with</span><span class="p">(</span><span class="n">sp</span><span class="p">.</span><span class="nf">edit</span><span class="p">())</span> <span class="p">{</span> <span class="k">when</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="k">is</span> <span class="nc">String</span> <span class="p">-&gt;</span> <span class="nf">putString</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Int</span> <span class="p">-&gt;</span> <span class="nf">putInt</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Long</span> <span class="p">-&gt;</span> <span class="nf">putLong</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Float</span> <span class="p">-&gt;</span> <span class="nf">putFloat</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Boolean</span> <span class="p">-&gt;</span> <span class="nf">putBoolean</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">else</span> <span class="p">-&gt;</span> <span class="k">throw</span> <span class="nc">RuntimeException</span><span class="p">(</span><span class="s">"Unsupported type."</span><span class="p">)</span> <span class="p">}.</span><span class="nf">apply</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>使用:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="kd">var</span> <span class="py">isFirstLaunch</span> <span class="k">by</span> <span class="nc">PreferencesDelegate</span><span class="p">(</span><span class="nc">Constant</span><span class="p">.</span><span class="nc">KEY_IS_FIRST_LAUNCH</span><span class="p">,</span> <span class="nc">Constant</span><span class="p">.</span><span class="nc">DEF_IS_FIRST_LAUNCH</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="n">isFirstLaunch</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Do something first launch, like showing Welcome.</span> <span class="o">..</span><span class="p">.</span> <span class="n">isFirstLaunch</span> <span class="p">=</span> <span class="k">false</span> <span class="p">}</span> </code></pre></div></div> <ul> <li> <p>创建一个 <code class="language-plaintext highlighter-rouge">PreferencesDelegate</code> 代理类,处理各个类型的 Preferences 的存取。</p> </li> <li> <p>在使用时,创建一个代表要处理的 Preference 的对应类型的属性,使用 <code class="language-plaintext highlighter-rouge">by Delegate</code> 语法,用 <code class="language-plaintext highlighter-rouge">PreferencesDelegate</code> 类代理这个属性。</p> </li> <li> <p>直接对变量取值就是从 SharedPreferences 文件中读取,对变量赋值即写入 SharedPreferences。</p> </li> <li> <p><code class="language-plaintext highlighter-rouge">IMPLICIT_CAST_TO_ANY</code>:这是 Kotlin 中使用 <code class="language-plaintext highlighter-rouge">when</code> 表达式时,当多个分支返回不同的类型时出现的 warning,表示 <code class="language-plaintext highlighter-rouge">when</code> 表达式的返回值类型被隐式转化成了 <code class="language-plaintext highlighter-rouge">Any</code>。</p> </li> <li> <p><code class="language-plaintext highlighter-rouge">UNCHECKED_CAST</code>:范型强转 warning。</p> </li> <li> <p><code class="language-plaintext highlighter-rouge">CommitPrefEdits</code>:使用 <code class="language-plaintext highlighter-rouge">when</code> 表达式时,静态分析无法判断 <code class="language-plaintext highlighter-rouge">SharedPreferences.Editor</code> 是否执行了 <code class="language-plaintext highlighter-rouge">commit</code> 调用。</p> </li> <li> <p>如果委托给一个局部变量,可能出现 <code class="language-plaintext highlighter-rouge">UNUSED_VALUE</code> warning,即变量赋值后未被使用,但实际上委托类执行了写 SharedPreferences 操作,并不是无用赋值。</p> </li> </ul> <h4 id="多个-sharedpreferences-文件">多个 SharedPreferences 文件</h4> <p>如果需要存取多个 SharedPreferences 文件,可以创建多个对应的委托类,继承子一个默认的 <code class="language-plaintext highlighter-rouge">DefaultPreferencesDelegate</code>。</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">open</span> <span class="kd">class</span> <span class="nc">DefaultPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="k">private</span> <span class="kd">val</span> <span class="py">defValue</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/** * SharedPreferences file name. `&lt;packageName&gt;_preferences.xml`. */</span> <span class="k">protected</span> <span class="k">open</span> <span class="kd">val</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">"${AppApplication.instance.packageName}_preferences"</span> <span class="k">private</span> <span class="kd">val</span> <span class="py">sp</span> <span class="k">by</span> <span class="nf">lazy</span> <span class="p">{</span> <span class="nc">AppApplication</span><span class="p">.</span><span class="n">instance</span><span class="p">.</span><span class="nf">getSharedPreferences</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="nc">Context</span><span class="p">.</span><span class="nc">MODE_PRIVATE</span><span class="p">)</span> <span class="p">}</span> <span class="o">..</span><span class="p">.</span> <span class="p">}</span> <span class="kd">class</span> <span class="nc">SettingsPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">defValue</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">:</span> <span class="nc">DefaultPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="p">{</span> <span class="k">override</span> <span class="kd">val</span> <span class="py">name</span> <span class="p">=</span> <span class="s">"settings"</span> <span class="p">}</span> <span class="kd">class</span> <span class="nc">DataPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">defValue</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">:</span> <span class="nc">DefaultPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="p">{</span> <span class="k">override</span> <span class="kd">val</span> <span class="py">name</span> <span class="p">=</span> <span class="s">"data"</span> <span class="p">}</span> </code></pre></div></div> <h4 id="从-sharedpreferences-中移除-key">从 SharedPreferences 中移除 <code class="language-plaintext highlighter-rouge">key</code></h4> <p>通过 <code class="language-plaintext highlighter-rouge">sp.edit().remove(key).apply</code> 移除一个 <code class="language-plaintext highlighter-rouge">key</code>。一个简单的实现方式:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">operator</span> <span class="k">fun</span> <span class="nf">setValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">?)</span> <span class="p">=</span> <span class="nf">with</span><span class="p">(</span><span class="n">sp</span><span class="p">.</span><span class="nf">edit</span><span class="p">())</span> <span class="p">{</span> <span class="k">when</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="k">null</span> <span class="p">-&gt;</span> <span class="nf">remove</span><span class="p">(</span><span class="n">key</span><span class="p">)</span> <span class="k">is</span> <span class="nc">String</span> <span class="p">-&gt;</span> <span class="nf">putString</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="o">..</span><span class="p">.</span> <span class="p">}.</span><span class="nf">apply</span><span class="p">()</span> <span class="p">}</span> <span class="kd">var</span> <span class="py">content</span><span class="p">:</span> <span class="nc">String</span><span class="p">?</span> <span class="k">by</span> <span class="nc">DataPreferencesDelegate</span><span class="p">(</span><span class="nc">Constant</span><span class="p">.</span><span class="nc">KEY_TODO</span><span class="p">,</span> <span class="nc">Constant</span><span class="p">.</span><span class="nc">DEF_TODO</span><span class="p">)</span> <span class="n">content</span> <span class="p">=</span> <span class="k">null</span> </code></pre></div></div> <p>这么写的不方便之处在于,这个属性可能在意义上是不可为空类型的,或者是 <code class="language-plaintext highlighter-rouge">Int</code>、<code class="language-plaintext highlighter-rouge">Boolean</code> 等类型,那么将它指定为可为空类型就不合适。如果需要更好的扩展,另写工具类支持。</p> <h4 id="读取-sharedpreferences-时指定默认值为-null">读取 SharedPreferences 时指定默认值为 <code class="language-plaintext highlighter-rouge">null</code></h4> <p>因为使用范型实现,并且通过 <code class="language-plaintext highlighter-rouge">defValue</code> 判断要存取的 SharedPreferences 的数据类型,因此这种委托写法不支持 <code class="language-plaintext highlighter-rouge">sp.getXxx(key, defValue)</code> 时 <code class="language-plaintext highlighter-rouge">defValue</code> 为 <code class="language-plaintext highlighter-rouge">null</code>。</p> <h2 id="sample">Sample</h2> <p>最后上一段完整的 sample 代码:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// PreferencesDelegates.kt</span> <span class="k">open</span> <span class="kd">class</span> <span class="nc">DefaultPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="k">private</span> <span class="kd">val</span> <span class="py">defValue</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/** * SharedPreferences file name. */</span> <span class="k">protected</span> <span class="k">open</span> <span class="kd">val</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">"${AppApplication.instance.packageName}_preferences"</span> <span class="k">private</span> <span class="kd">val</span> <span class="py">sp</span> <span class="k">by</span> <span class="nf">lazy</span> <span class="p">{</span> <span class="nc">AppApplication</span><span class="p">.</span><span class="n">instance</span><span class="p">.</span><span class="nf">getSharedPreferences</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="nc">Context</span><span class="p">.</span><span class="nc">MODE_PRIVATE</span><span class="p">)</span> <span class="p">}</span> <span class="nd">@Suppress</span><span class="p">(</span><span class="s">"IMPLICIT_CAST_TO_ANY"</span><span class="p">,</span> <span class="s">"UNCHECKED_CAST"</span><span class="p">)</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">getValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;)</span> <span class="p">=</span> <span class="nf">with</span><span class="p">(</span><span class="n">sp</span><span class="p">)</span> <span class="p">{</span> <span class="k">when</span> <span class="p">(</span><span class="n">defValue</span><span class="p">)</span> <span class="p">{</span> <span class="k">is</span> <span class="nc">String</span> <span class="p">-&gt;</span> <span class="nf">getString</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Int</span> <span class="p">-&gt;</span> <span class="nf">getInt</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Long</span> <span class="p">-&gt;</span> <span class="nf">getLong</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Float</span> <span class="p">-&gt;</span> <span class="nf">getFloat</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Boolean</span> <span class="p">-&gt;</span> <span class="nf">getBoolean</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="k">else</span> <span class="p">-&gt;</span> <span class="k">throw</span> <span class="nc">RuntimeException</span><span class="p">(</span><span class="s">"Unsupported type."</span><span class="p">)</span> <span class="p">}</span> <span class="k">as</span> <span class="nc">T</span> <span class="p">}</span> <span class="nd">@SuppressLint</span><span class="p">(</span><span class="s">"CommitPrefEdits"</span><span class="p">)</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">setValue</span><span class="p">(</span><span class="n">thisRef</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">property</span><span class="p">:</span> <span class="nc">KProperty</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">?)</span> <span class="p">=</span> <span class="nf">with</span><span class="p">(</span><span class="n">sp</span><span class="p">.</span><span class="nf">edit</span><span class="p">())</span> <span class="p">{</span> <span class="k">when</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="k">null</span> <span class="p">-&gt;</span> <span class="nf">remove</span><span class="p">(</span><span class="n">key</span><span class="p">)</span> <span class="k">is</span> <span class="nc">String</span> <span class="p">-&gt;</span> <span class="nf">putString</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Int</span> <span class="p">-&gt;</span> <span class="nf">putInt</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Long</span> <span class="p">-&gt;</span> <span class="nf">putLong</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Float</span> <span class="p">-&gt;</span> <span class="nf">putFloat</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">is</span> <span class="nc">Boolean</span> <span class="p">-&gt;</span> <span class="nf">putBoolean</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">else</span> <span class="p">-&gt;</span> <span class="k">throw</span> <span class="nc">RuntimeException</span><span class="p">(</span><span class="s">"Unsupported type."</span><span class="p">)</span> <span class="p">}.</span><span class="nf">apply</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">class</span> <span class="nc">SettingsPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">defValue</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">:</span> <span class="nc">DefaultPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="p">{</span> <span class="k">override</span> <span class="kd">val</span> <span class="py">name</span> <span class="p">=</span> <span class="s">"settings"</span> <span class="p">}</span> <span class="kd">class</span> <span class="nc">DataPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">defValue</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">:</span> <span class="nc">DefaultPreferencesDelegate</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="n">key</span><span class="p">,</span> <span class="n">defValue</span><span class="p">)</span> <span class="p">{</span> <span class="k">override</span> <span class="kd">val</span> <span class="py">name</span> <span class="p">=</span> <span class="s">"data"</span> <span class="p">}</span> </code></pre></div></div> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">object</span> <span class="nc">PreferencesHelper</span> <span class="p">{</span> <span class="cm">/** * Keys. */</span> <span class="k">private</span> <span class="k">const</span> <span class="kd">val</span> <span class="py">KEY_IS_FIRST_LAUNCH</span> <span class="p">=</span> <span class="s">"is_first_launch"</span> <span class="k">private</span> <span class="k">const</span> <span class="kd">val</span> <span class="py">KEY_TODO</span> <span class="p">=</span> <span class="s">"todo"</span> <span class="cm">/** * Default values. */</span> <span class="k">private</span> <span class="k">const</span> <span class="kd">val</span> <span class="py">DEF_IS_FIRST_LAUNCH</span> <span class="p">=</span> <span class="k">true</span> <span class="k">private</span> <span class="k">const</span> <span class="kd">val</span> <span class="py">DEF_TODO</span> <span class="p">=</span> <span class="s">"Learn Kotlin."</span> <span class="kd">var</span> <span class="py">isFirstLaunch</span><span class="p">:</span> <span class="nc">Boolean</span> <span class="k">by</span> <span class="nc">SettingsPreferencesDelegate</span><span class="p">(</span><span class="nc">KEY_IS_FIRST_LAUNCH</span><span class="p">,</span> <span class="nc">DEF_IS_FIRST_LAUNCH</span><span class="p">)</span> <span class="kd">var</span> <span class="py">todo</span><span class="p">:</span> <span class="nc">String</span><span class="p">?</span> <span class="k">by</span> <span class="nc">DataPreferencesDelegate</span><span class="p">(</span><span class="nc">KEY_TODO</span><span class="p">,</span> <span class="nc">DEF_TODO</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">kotlinx.android.synthetic.main.activity_main.todoEditText</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">Activity</span><span class="p">()</span> <span class="p">{</span> <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span> <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span> <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="nc">PreferencesHelper</span><span class="p">.</span><span class="n">isFirstLaunch</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Do something first launch, like showing Welcome.</span> <span class="nf">showWelcome</span><span class="p">()</span> <span class="nc">PreferencesHelper</span><span class="p">.</span><span class="n">isFirstLaunch</span> <span class="p">=</span> <span class="k">false</span> <span class="p">}</span> <span class="c1">// Read from SharedPreferences.</span> <span class="n">todoEditText</span><span class="p">.</span><span class="nf">setText</span><span class="p">(</span><span class="nc">PreferencesHelper</span><span class="p">.</span><span class="n">todo</span><span class="p">)</span> <span class="p">}</span> <span class="k">private</span> <span class="k">fun</span> <span class="nf">showWelcome</span><span class="p">()</span> <span class="p">{</span> <span class="nc">AlertDialog</span><span class="p">.</span><span class="nc">Builder</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">.</span><span class="nf">setMessage</span><span class="p">(</span><span class="s">"Welcome!"</span><span class="p">)</span> <span class="p">.</span><span class="nf">setPositiveButton</span><span class="p">(</span><span class="s">"Fine"</span><span class="p">,</span> <span class="k">null</span><span class="p">)</span> <span class="p">.</span><span class="nf">show</span><span class="p">()</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">saveOnClick</span><span class="p">(</span><span class="n">view</span><span class="p">:</span> <span class="nc">View</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Write to SharedPreferences.</span> <span class="nc">PreferencesHelper</span><span class="p">.</span><span class="n">todo</span> <span class="p">=</span> <span class="n">todoEditText</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="nf">toString</span><span class="p">()</span> <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"Save success"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span> <span class="p">}</span> <span class="k">fun</span> <span class="nf">clearOnClick</span><span class="p">(</span><span class="n">view</span><span class="p">:</span> <span class="nc">View</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Remove from SharedPreferences.</span> <span class="nc">PreferencesHelper</span><span class="p">.</span><span class="n">todo</span> <span class="p">=</span> <span class="k">null</span> <span class="n">todoEditText</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="nf">clear</span><span class="p">()</span> <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"Clear success"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div>简单的说,委托属性就是将一个属性的操作委托给一个委托类的实例处理,多个属性可以委托给同一个委托类。