Rouse
2024-03-25T13:48:57.222Z
https://www.rousetime.com/
Rouse
Hexo
Recyclerview竟能够如此丝滑,这14个优化策略不容错过...
https://www.rousetime.com/2024/03/25/Recyclerview竟能够如此丝滑,这14个优化策略不容错过/
2024-03-25T13:48:02.000Z
2024-03-25T13:48:57.222Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Android开发中,RecyclerView是一种常用的列表控件,用于展示大量数据。然而,随着数据量的增加,RecyclerView的性能可能会受到影响,导致卡顿、内存泄漏等问题。本文将介绍一些优化技巧,帮助大家提升RecyclerView的性能,使其在各种情况下都能保持流畅。</p>
<h2 id="优化思路"><a href="#优化思路" class="headerlink" title="优化思路"></a>优化思路</h2><p>RecyclerView 性能优化的核心思路可以概括为以下几个方面:</p>
<ol>
<li>布局优化: 优化 RecyclerView 的布局结构,减少嵌套层级,提高布局效率。</li>
<li>减少绘制: 尽可能减少视图的绘制次数,避免过度绘制带来的性能消耗。</li>
<li>滑动优化: 在滑动过程中,尽可能的减少耗时操作,避免影响滑动效果。</li>
<li>预加载: 预加载即将显示的视图,提高展示性能。</li>
<li>内存优化: 减少内存的消耗,合理释放内存,避免内存泄漏。</li>
</ol>
<p>下面针对这些分别给出具体的优化策略。</p>
<h2 id="布局优化"><a href="#布局优化" class="headerlink" title="布局优化"></a>布局优化</h2><ol>
<li>减少布局嵌套</li>
</ol>
<p>避免在RecyclerView的Item布局中使用过多的嵌套布局和复杂的层次结构,这会增加渲染的时间和消耗。尽量使用简单的布局结构,并合理使用ConstraintLayout等高效布局。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- item_layout.xml --></span></span><br><span class="line"><span class="tag"><<span class="name">androidx.constraintlayout.widget.ConstraintLayout</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_height</span>=<span class="string">"wrap_content"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">TextView</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:id</span>=<span class="string">"@+id/textView"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_width</span>=<span class="string">"wrap_content"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_height</span>=<span class="string">"wrap_content"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">app:layout_constraintStart_toStartOf</span>=<span class="string">"parent"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">app:layout_constraintTop_toTopOf</span>=<span class="string">"parent"</span>/></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- 其他视图组件 --></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">androidx.constraintlayout.widget.ConstraintLayout</span>></span></span><br></pre></td></tr></table></figure>
<ol start="2">
<li>使用merge标签来合并布局</li>
</ol>
<p>使用merge标签可以将多个布局文件合并为一个,减少布局层级,提高绘制性能。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 使用merge标签合并布局 --></span></span><br><span class="line"><span class="tag"><<span class="name">merge</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ImageView</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:id</span>=<span class="string">"@+id/imageView"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_width</span>=<span class="string">"wrap_content"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_height</span>=<span class="string">"wrap_content"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:src</span>=<span class="string">"@drawable/image"</span> /></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">TextView</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:id</span>=<span class="string">"@+id/textView"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_width</span>=<span class="string">"wrap_content"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:layout_height</span>=<span class="string">"wrap_content"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:text</span>=<span class="string">"Text"</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">merge</span>></span></span><br></pre></td></tr></table></figure>
<ol start="3">
<li>启用setHasFixedSize</li>
</ol>
<p>设置 <code>setHasFixedSize(true)</code> 后,RecyclerView会假设所有的Item的高度是固定的,不会因为Item的变化而触发重新计算布局,避免<code>requestLayout</code>导致的资源浪费。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> recyclerView = findViewById<RecyclerView>(R.id.recyclerView)</span><br><span class="line">recyclerView.setHasFixedSize(<span class="literal">true</span>)</span><br></pre></td></tr></table></figure>
<p>需要注意的是,使用 <code>setHasFixedSize(true)</code>适用于所有Item高度固定且不会发生变化的情况。如果Item高度不固定或者会发生变化,应该避免使用该方法,否则可能导致布局显示异常。</p>
<h2 id="减少绘制"><a href="#减少绘制" class="headerlink" title="减少绘制"></a>减少绘制</h2><ol>
<li>使用DiffUtil进行数据更新</li>
</ol>
<p>在数据集变化时,使用DiffUtil进行差异计算可以减少不必要的UI更新,提高性能。DiffUtil可以在后台线程中高效地计算数据集的差异,并将结果应用到RecyclerView中。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyDiffCallback</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> oldList: List<String>, <span class="keyword">private</span> <span class="keyword">val</span> newList: List<String>) : DiffUtil.Callback() {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">getOldListSize</span><span class="params">()</span></span>: <span class="built_in">Int</span> {</span><br><span class="line"> <span class="keyword">return</span> oldList.size</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">getNewListSize</span><span class="params">()</span></span>: <span class="built_in">Int</span> {</span><br><span class="line"> <span class="keyword">return</span> newList.size</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">areItemsTheSame</span><span class="params">(oldItemPosition: <span class="type">Int</span>, newItemPosition: <span class="type">Int</span>)</span></span>: <span class="built_in">Boolean</span> {</span><br><span class="line"> <span class="keyword">return</span> oldList[oldItemPosition] == newList[newItemPosition]</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">areContentsTheSame</span><span class="params">(oldItemPosition: <span class="type">Int</span>, newItemPosition: <span class="type">Int</span>)</span></span>: <span class="built_in">Boolean</span> {</span><br><span class="line"> <span class="keyword">return</span> oldList[oldItemPosition] == newList[newItemPosition]</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在Adapter中应用DiffUtil</span></span><br><span class="line"><span class="keyword">val</span> diffResult = DiffUtil.calculateDiff(MyDiffCallback(oldList, newList))</span><br><span class="line">diffResult.dispatchUpdatesTo(<span class="keyword">this</span>)</span><br></pre></td></tr></table></figure>
<ol start="2">
<li>限制列表项的数量</li>
</ol>
<p>如果列表中的数据量非常大,可以考虑进行分页加载或者只加载可见范围内的数据,以减少内存占用和渲染时间。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 仅加载可见范围内的数据</span></span><br><span class="line">recyclerView.layoutManager?.setInitialPrefetchItemCount(<span class="number">10</span>)</span><br></pre></td></tr></table></figure>
<h2 id="滑动优化"><a href="#滑动优化" class="headerlink" title="滑动优化"></a>滑动优化</h2><ol>
<li>在onCreateViewHolder中进行必要的初始化操作</li>
</ol>
<p>在ViewHolder的创建阶段,进行必要的初始化操作,如设置监听器等,避免在<code>onBindViewHolder()</code>中进行耗时操作,提高滚动性能。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreateViewHolder</span><span class="params">(parent: <span class="type">ViewGroup</span>, viewType: <span class="type">Int</span>)</span></span>: RecyclerView.ViewHolder {</span><br><span class="line"> <span class="keyword">val</span> view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, <span class="literal">false</span>)</span><br><span class="line"> <span class="keyword">val</span> viewHolder = ViewHolder(view)</span><br><span class="line"> <span class="comment">// 进行必要的初始化操作</span></span><br><span class="line"> <span class="keyword">return</span> viewHolder</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol start="2">
<li>滑动停止加载操作</li>
</ol>
<p>可以通过 <code>RecyclerView.addOnScrollListener(listener)</code> 方法添加一个滚动监听器,然后在监听器中进行相应的操作,进一步优化滑动的效果。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> recyclerView = findViewById<RecyclerView>(R.id.recyclerView)</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> layoutManager = LinearLayoutManager(<span class="keyword">this</span>)</span><br><span class="line">recyclerView.layoutManager = layoutManager</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> adapter = MyAdapter(dataList)</span><br><span class="line">recyclerView.adapter = adapter</span><br><span class="line"></span><br><span class="line">recyclerView.addOnScrollListener(<span class="keyword">object</span> : RecyclerView.OnScrollListener() {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onScrollStateChanged</span><span class="params">(recyclerView: <span class="type">RecyclerView</span>, newState: <span class="type">Int</span>)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onScrollStateChanged(recyclerView, newState)</span><br><span class="line"> <span class="comment">// 判断滚动状态是否为停止滚动状态</span></span><br><span class="line"> <span class="keyword">if</span> (newState == RecyclerView.SCROLL_STATE_IDLE) {</span><br><span class="line"> startLoaidng()</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 执行停止加载操作,例如停止图片加载等</span></span><br><span class="line"> stopLoading()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
<h2 id="预加载"><a href="#预加载" class="headerlink" title="预加载"></a>预加载</h2><ol>
<li>启动calculateExtraLayoutSpace</li>
</ol>
<p><code>calculateExtraLayoutSpace</code> 方法可以用来增加RecyclerView预留的额外空间,有助于提前加载屏幕外的Item,避免滑动过程中的卡顿。</p>
<p>您可以通过重写<code>calculateExtraLayoutSpace</code>方法来返回额外的空间大小,以便RecyclerView在滑动过程中预加载屏幕外的Item。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CustomLayoutManager</span> : <span class="type">LinearLayoutManager {</span></span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(context: Context) : <span class="keyword">super</span>(context)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(context: Context, orientation: <span class="built_in">Int</span>, reverseLayout: <span class="built_in">Boolean</span>) : <span class="keyword">super</span>(context, orientation, reverseLayout)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">calculateExtraLayoutSpace</span><span class="params">(state: <span class="type">RecyclerView</span>.<span class="type">State</span>, extraLayoutSpace: <span class="type">IntArray</span>)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.calculateExtraLayoutSpace(state, extraLayoutSpace)</span><br><span class="line"> <span class="comment">// 设置额外的布局空间,可以根据需要动态计算</span></span><br><span class="line"> extraLayoutSpace[<span class="number">0</span>] = <span class="number">200</span> </span><br><span class="line"> extraLayoutSpace[<span class="number">1</span>] = <span class="number">200</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol start="2">
<li>重写collectAdjacentPrefetchPositions</li>
</ol>
<p><code>collectAdjacentPrefetchPositions</code>方法是RecyclerView中的一个保护方法,用于收集与给定位置相邻的预取位置。这个方法主要用于RecyclerView的预取机制,用于在滑动过程中预取与当前位置相邻的Item数据,提高滑动的流畅度。</p>
<p>你可以在自定义LayoutManager中重写<code>collectAdjacentPrefetchPositions</code>方法来实现相邻位置的预取逻辑。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CustomLayoutManager</span> : <span class="type">LinearLayoutManager {</span></span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(context: Context) : <span class="keyword">super</span>(context)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(context: Context, orientation: <span class="built_in">Int</span>, reverseLayout: <span class="built_in">Boolean</span>) : <span class="keyword">super</span>(context, orientation, reverseLayout)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">collectAdjacentPrefetchPositions</span><span class="params">(dx: <span class="type">Int</span>, dy: <span class="type">Int</span>, state: <span class="type">RecyclerView</span>.<span class="type">State</span>?, layoutPrefetchRegistry: <span class="type">LayoutPrefetchRegistry</span>)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.collectAdjacentPrefetchPositions(dx, dy, state, layoutPrefetchRegistry)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 根据滑动方向(dx, dy)收集相邻的预取位置</span></span><br><span class="line"> <span class="keyword">val</span> anchorPos = findFirstVisibleItemPosition()</span><br><span class="line"> <span class="keyword">if</span> (dy > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 向下滑动,预取下面的Item数据</span></span><br><span class="line"> <span class="keyword">for</span> (i <span class="keyword">in</span> anchorPos + <span class="number">1</span> until state?.itemCount ?: <span class="number">0</span>) {</span><br><span class="line"> layoutPrefetchRegistry.addPosition(i, <span class="number">0</span>)</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 向上滑动,预取上面的Item数据</span></span><br><span class="line"> <span class="keyword">for</span> (i <span class="keyword">in</span> anchorPos - <span class="number">1</span> downTo <span class="number">0</span>) {</span><br><span class="line"> layoutPrefetchRegistry.addPosition(i, <span class="number">0</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="内存优化"><a href="#内存优化" class="headerlink" title="内存优化"></a>内存优化</h2><ol>
<li>共用RecyclerViewPool</li>
</ol>
<p>如果多个 <code>RecycledView</code> 的 <code>Adapter</code> 是一样的,可以让RecyclerView之间共享一个RecycledViewPool以提高性能</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个共享的RecycledViewPool</span></span><br><span class="line"><span class="keyword">val</span> recycledViewPool = RecyclerView.RecycledViewPool()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置共享的RecycledViewPool给多个RecyclerView</span></span><br><span class="line">recyclerView1.setRecycledViewPool(recycledViewPool)</span><br><span class="line">recyclerView2.setRecycledViewPool(recycledViewPool)</span><br></pre></td></tr></table></figure>
<p>这种做法特别适用于多个RecyclerView之间的数据或布局结构有较大相似性的情况下,通过共享RecycledViewPool可以进一步提升性能。</p>
<ol start="2">
<li>使用Adapter.setHasStableIds(true)提高Item稳定性</li>
</ol>
<p>设置Adapter的<code>setHasStableIds(true)</code>可以提高Item的稳定性,帮助RecyclerView更好地识别和复用ViewHolder,避免频繁创建和销毁ViewHolder,减少内存消耗。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adapter.setHasStableIds(<span class="literal">true</span>)</span><br></pre></td></tr></table></figure>
<ol start="3">
<li>使用RecyclerView.setItemViewCacheSize(size)设置缓存大小</li>
</ol>
<p>通过设置RecyclerView的<code>setItemViewCacheSize(size)</code>方法来设置缓存大小,可以控制RecyclerView中缓存ViewHolder的数量,避免过多的缓存占用过多内存。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">recyclerView.setItemViewCacheSize(<span class="number">20</span>) <span class="comment">// 设置缓存大小为20</span></span><br></pre></td></tr></table></figure>
<ol start="4">
<li>共享事件</li>
</ol>
<p>例如点击事件,可以创建一个共用的监听器对象,并将其设置给所有的ItemView。然后根据ID来区分执行不同的操作。从而避免了对每个Item都创建监听器对象,优化了资源消耗。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 共用的监听器对象</span></span><br><span class="line"><span class="keyword">val</span> itemClickListener = View.OnClickListener { view -></span><br><span class="line"> <span class="comment">// 根据view的ID来执行不同的操作</span></span><br><span class="line"> <span class="keyword">when</span> (view.id) {</span><br><span class="line"> R.id.button -> {</span><br><span class="line"> <span class="comment">// 执行按钮点击操作</span></span><br><span class="line"> }</span><br><span class="line"> R.id.imageView -> {</span><br><span class="line"> <span class="comment">// 执行图片点击操作</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 其他ID的处理...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在ViewHolder中为ItemView设置共用的监听器</span></span><br><span class="line"><span class="keyword">inner</span> <span class="class"><span class="keyword">class</span> <span class="title">ViewHolder</span></span>(itemView: View) : RecyclerView.ViewHolder(itemView) {</span><br><span class="line"> <span class="keyword">init</span> {</span><br><span class="line"> <span class="comment">// 为所有需要的ItemView设置共用的监听器</span></span><br><span class="line"> itemView.setOnClickListener(itemClickListener)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol start="5">
<li>重写RecyclerView.onViewRecycled()回收资源</li>
</ol>
<p>在 <code>onViewRecycled(holder: ViewHolder)</code> 方法中,我们可以执行一些资源释放操作,例如释放ViewHolder中的图片资源、移除监听器等,以便在ViewHolder被回收时及时释放相关资源,避免内存泄漏和资源浪费。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onViewRecycled</span><span class="params">(holder: <span class="type">ViewHolder</span>)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onViewRecycled(holder)</span><br><span class="line"> <span class="comment">// 释放ViewHolder中的图片资源</span></span><br><span class="line"> holder.imageView.setImageDrawable(<span class="literal">null</span>)</span><br><span class="line"> <span class="comment">// 移除ViewHolder中的监听器</span></span><br><span class="line"> holder.itemView.setOnClickListener(<span class="literal">null</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过选择合适的优化布局、减少绘制、滑动优化、预加载与内存优化策略,可以有效提升RecyclerView的性能,使其在各种情况下都能保持流畅。在实际开发中,还需要根据具体情况选择合适的优化策略,并进行适当的测试和调整,以达到最佳的性能效果。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Androi
Android布局耗时监测的三种方式,你的选择将决定你的高度
https://www.rousetime.com/2024/03/22/Android布局耗时监测的三种方式,你的选择将决定你的高度/
2024-03-22T01:35:55.000Z
2024-03-22T01:36:30.386Z
<p>在Android应用开发中,性能优化是一个至关重要的方面。其中,布局渲染的性能直接影响用户体验,特别是在一些复杂页面中,布局渲染的耗时可能会导致界面卡顿,影响用户体验。因此,为了更好地监测布局渲染的耗时,我们需要一种可靠的实现方案。本文将介绍三种针对Android布局耗时监测的实现方案,帮助开发者及时发现并解决布局性能问题。</p>
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>布局渲染的耗时是指从布局文件加载到界面显示完成所花费的时间。通常,我们使用开发者选项中的布局边界线来查看布局渲染的性能情况,但是这种方法并不能准确地反映布局渲染的耗时。因此,我们需要一种更精确的监测方案来定位布局性能问题。</p>
<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>布局耗时监测的原理就是在布局过程中的关键节点插入计时代码,记录每个阶段的耗时,从而分析出布局耗时的瓶颈所在。</p>
<h2 id="手动埋点"><a href="#手动埋点" class="headerlink" title="手动埋点"></a>手动埋点</h2><p>最简单的布局耗时监测方案就是在布局过程中的关键节点手动插入计时代码,例如:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">override fun onCreate(savedInstanceState: Bundle?) {</span><br><span class="line"> super.onCreate(savedInstanceState)</span><br><span class="line"></span><br><span class="line"> long startTime = System.currentTimeMillis();</span><br><span class="line"> // 解析 XML 布局文件</span><br><span class="line"> setContentView(R.layout.activity_main);</span><br><span class="line"> long endTime = System.currentTimeMillis();</span><br><span class="line"> long cost = endTime - startTime;</span><br><span class="line"> Log.d("TAG", "布局耗时:" + cost + "ms"); </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这种方案的缺点是需要手动插入代码,比较繁琐,而且不够灵活。</p>
<h2 id="AOP切面编程"><a href="#AOP切面编程" class="headerlink" title="AOP切面编程"></a>AOP切面编程</h2><p>使用<code>AOP</code>切面编程可以更加优雅地实现布局耗时监测。例如,使用<code>AspectJ</code>框架可以定义一个切面,在<code>setContentView()</code>方法执行前后分别插入计时代码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">@Aspect</span><br><span class="line">public class LayoutTimeAspect {</span><br><span class="line"></span><br><span class="line"> @Around("call(* android.app.Activity.setContentView(..))")</span><br><span class="line"> public void aroundSetContentView(ProceedingJoinPoint joinPoint) throws Throwable {</span><br><span class="line"> long startTime = System.currentTimeMillis();</span><br><span class="line"> joinPoint.proceed();</span><br><span class="line"> long endTime = System.currentTimeMillis();</span><br><span class="line"> long cost = endTime - startTime;</span><br><span class="line"> Log.d("TAG", "布局耗时:" + cost + "ms");</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这种方案的优点是代码更加简洁优雅,而且可以扩展到其他需要监测耗时的操作。</p>
<p>类似的还有通过ASM的方法进行插桩,本质都是一样,在特定的方法时机中插入对应的监测代码。</p>
<h2 id="Factory"><a href="#Factory" class="headerlink" title="Factory"></a>Factory</h2><p>如果我们要知道特定的某个view的耗时,这个时候就可以使用<code>LayoutInflaterCompat.Factory2</code></p>
<p>该方法用于设置一个<code>LayoutInflater.Factory2</code>对象,用于替换<code>LayoutInflater</code>在解析XML布局文件时创建View的行为。通过自定义 <code>LayoutInflater.Factory2</code>,我们可以拦截和修改布局的加载过程,包括创建View和设置属性等。</p>
<p>简单的理解,就是它能拦截view的创建过程,所以我们可以通过这个特性来监听布局中每一个view的具体耗时。</p>
<p>下面是一个简单的示例:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">class MyFactory2 : LayoutInflater.Factory2 {</span><br><span class="line"></span><br><span class="line"> override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {</span><br><span class="line"> val startTime = System.nanoTime()</span><br><span class="line"> val view = LayoutInflater.from(context).createView(name, attrs)</span><br><span class="line"> val endTime = System.nanoTime()</span><br><span class="line"> val costTime = endTime - startTime</span><br><span class="line"> Log.d("TAG", "View $name layout cost time: $costTime")</span><br><span class="line"> return view</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这种方案的有点是能够更加具体化,可以帮助监测到具体的view。</p>
<h2 id="开发工具"><a href="#开发工具" class="headerlink" title="开发工具"></a>开发工具</h2><p>除了线上的监测功能,线下的分析也至关重要。Android提供了一些相关分析工具,能够很好的帮助开发者分析各种性能。</p>
<p>例如,<code>Systrace</code>是Android开发者工具中的一部分,它可以用来分析应用程序在Android系统上的性能问题,包括布局、绘制、CPU、内存等方面的性能问题。</p>
<p>所以针对布局耗时,我们也可以使用<code>Systrace</code>来进行线下分析。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">TraceCompat.beginSection("start");</span><br><span class="line">TraceCompat.endSection();</span><br></pre></td></tr></table></figure>
<p>在需要检测的地方做标记,然后再进行收集数据</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ python systrace.py --time=10 -o my_trace.html sched gfx view</span><br></pre></td></tr></table></figure>
<p>这条命令会在10秒内收集与调度、图形渲染、视图布局相关的性能数据,并将数据保存到名为 my_trace.html 的文件中。</p>
<p>生成完之后,我们只需要打开得到的html,可以直接在浏览器中打开。报告文件中包含了各种性能指标的图表和分析,可以通过查看相关的部分来了解布局的耗时情况。</p>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0a67aaf58ba14256980f7a811e21c03a~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=958&h=422&s=78593&e=jpg&b=f6f4f4" alt="1710229845178.jpg"><br>除此之外还有,<code>TraceView</code>、<code>LayoutInspector</code>等工具都能有效的辅助分析布局的耗时。</p>
<h2 id="优化技巧"><a href="#优化技巧" class="headerlink" title="优化技巧"></a>优化技巧</h2><p>在监测并发现到布局问题的时候,剩下的就是解决布局耗时问题。下面提供一些优化布局耗时的方案。</p>
<ul>
<li>注意避免在布局渲染过程中进行耗时操作,以免影响性能。</li>
<li>使用合适的布局管理器和布局优化技巧,减少布局层次和复杂度,提高布局渲染效率。</li>
<li>对于一些复杂布局,可以考虑使用异步加载布局来减少布局加载时间。</li>
</ul>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过以上实现方案,我们可以准确监测Android应用中布局渲染的耗时,及时发现并解决布局性能问题,从而提升用户体验。希望本文能帮助到Android开发者更好地优化应用性能。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<p>在Android应用开发中,性能优化是一个至关&#
提高10倍开发效率?APT如何让Android开发变得更轻松
https://www.rousetime.com/2024/03/16/提高10倍开发效率?APT如何让Android开发变得更轻松/
2024-03-16T02:27:27.000Z
2024-03-16T02:27:57.363Z
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在Android开发中,APT(Annotation Processing Tool)是一种强大的工具,它可以让开发者在编译期间处理注解,生成额外的代码。通过APT,我们可以实现很多高级功能,比如自动生成代码、实现依赖注入、生成路由表等。本文将深入探讨APT的运用以及背后的原理。</p>
<h2 id="APT的基本原理"><a href="#APT的基本原理" class="headerlink" title="APT的基本原理"></a>APT的基本原理</h2><p>APT的基本原理是在编译期间扫描和处理源代码中的注解,然后根据注解生成相应的Java代码。这些生成的代码可以在编译后被编译器包含到最终的APK中。</p>
<h2 id="APT的运作流程"><a href="#APT的运作流程" class="headerlink" title="APT的运作流程"></a>APT的运作流程</h2><ol>
<li><strong>扫描注解</strong>: 首先,编译器会扫描源代码中的注解,找到被标记的元素。</li>
<li><strong>解析注解</strong>: 然后,APT会解析这些注解,获取注解中定义的信息。</li>
<li><strong>生成代码</strong>: 接着,根据注解中的信息,APT会生成相应的Java代码。</li>
<li><strong>编译代码</strong>: 最后,生成的Java代码会被编译器编译成.class文件,与其他源代码一起构建成APK。</li>
</ol>
<h2 id="APT的应用场景"><a href="#APT的应用场景" class="headerlink" title="APT的应用场景"></a>APT的应用场景</h2><p>APT在Android开发中有着广泛的应用,其中一些典型的应用场景包括:</p>
<ol>
<li><strong>自动生成代码:</strong> 通过APT,我们可以在编译期间生成一些重复性的代码,比如<code>Parcelable</code>实现、<code>ViewHolder</code>的生成与添加日志等,从而减少手动编写重复代码的工作量。</li>
<li><strong>依赖注入:</strong> APT可以结合注解处理器,实现依赖注入的功能。通过在注解中指定依赖的对象,注解处理器可以在编译期间生成依赖注入的代码,从而实现依赖注入的功能。</li>
<li><strong>路由管理:</strong> APT可以用来生成路由表,从而实现页面跳转的管理。通过在注解中指定页面的路径和参数,APT可以在编译期间生成路由表,从而实现页面跳转的自动化管理。</li>
</ol>
<h2 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h2><p>APT 具有以下优势:</p>
<ol>
<li><strong>提高开发效率:</strong> APT 可以自动生成代码,减少开发人员的手动编码工作。</li>
<li><strong>代码更加简洁优雅:</strong> 通过 APT 生成的代码,通常更加简洁优雅,易于理解和维护。</li>
<li><strong>提高代码质量:</strong> APT 可以帮助开发人员避免一些常见的错误,提高代码质量。</li>
</ol>
<h2 id="使用代码示例"><a href="#使用代码示例" class="headerlink" title="使用代码示例"></a>使用代码示例</h2><p>下面通过一个简单的例子来演示APT的使用,实现一个简单的<code>findViewById</code>与<code>setText</code>的功能。</p>
<h3 id="定义注解"><a href="#定义注解" class="headerlink" title="定义注解"></a>定义注解</h3><p>首先定义一个注解<code>@BindView</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">@Retention(RetentionPolicy.SOURCE)</span><br><span class="line">@Target(ElementType.FIELD)</span><br><span class="line">public @interface BindView {</span><br><span class="line"> @IdRes int[] value();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这里使用了<code>@Target</code>和<code>@Retention</code>两个重要的元注解,用来对注解进行定义和修饰。</p>
<h4 id="Target"><a href="#Target" class="headerlink" title="@Target"></a>@Target</h4><p><code>@Target</code>注解用于指定注解可以应用的范围,即注解可以放置在哪些元素之上。它有一个参数,类型为<code>ElementType[]</code>,表示注解可以应用的目标元素类型。</p>
<p>常见的<code>ElementType</code>包括:</p>
<ul>
<li><code>ElementType.TYPE</code>: 类、接口、枚举</li>
<li><code>ElementType.FIELD</code>: 字段</li>
<li><code>ElementType.METHOD</code>: 方法</li>
<li><code>ElementType.PARAMETER</code>: 参数</li>
<li><code>ElementType.CONSTRUCTOR</code>: 构造方法</li>
<li><code>ElementType.LOCAL_VARIABLE</code>: 局部变量</li>
<li><code>ElementType.ANNOTATION_TYPE</code>: 注解类型</li>
<li><code>ElementType.PACKAGE</code>: 包</li>
</ul>
<p>例如,当我们指定<code>@Target(ElementType.FIELD)</code>时,表示该注解只能应用在字段上,不能应用在其他元素上。</p>
<h4 id="Retention"><a href="#Retention" class="headerlink" title="@Retention"></a>@Retention</h4><p><code>@Retention</code>注解用于指定注解的生命周期,即注解在编译后是否保留到运行时。它有一个参数,类型为<code>RetentionPolicy</code>,表示注解的保留策略。</p>
<p>常见的保留策略包括:</p>
<ul>
<li><code>RetentionPolicy.SOURCE</code>: 注解仅保留在源代码中,编译时会被丢弃,不会包含在生成的class文件中。</li>
<li><code>RetentionPolicy.CLASS</code>: 注解保留在编译后的class文件中,但在运行时会被忽略,默认值。在Kotlin中对应的是<code>BINARY</code>。</li>
<li><code>RetentionPolicy.RUNTIME</code>: 注解保留在编译后的class文件中,并且在运行时可以通过反射获取到。</li>
</ul>
<p>通常情况下,我们希望自定义注解在运行时保留,以便在运行时通过反射来获取注解信息,因此,一般会指定<code>@Retention(RetentionPolicy.RUNTIME)</code>。</p>
<p>例如,当我们指定<code>@Retention(RetentionPolicy.RUNTIME)</code>时,表示该注解在编译后的class文件中保留,并且可以在运行时通过反射获取到。</p>
<h3 id="编写注解器"><a href="#编写注解器" class="headerlink" title="编写注解器"></a>编写注解器</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br></pre></td><td class="code"><pre><span class="line">public class Processor extends AbstractProcessor {</span><br><span class="line"></span><br><span class="line"> private Filer mFiler;</span><br><span class="line"> private Messager mMessager;</span><br><span class="line"> private Elements mElementUtils;</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public synchronized void init(ProcessingEnvironment processingEnv) {</span><br><span class="line"> super.init(processingEnv);</span><br><span class="line"> mFiler = processingEnv.getFiler();</span><br><span class="line"> mMessager = processingEnv.getMessager();</span><br><span class="line"> mElementUtils = processingEnv.getElementUtils();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {</span><br><span class="line"> if (!roundEnv.processingOver()) {</span><br><span class="line"> //获取与annotation相匹配的TypeElement,即有注释声明的class</span><br><span class="line"> Set<TypeElement> elements = getTypeElementsByAnnotationType(annotations, roundEnv.getRootElements());</span><br><span class="line"></span><br><span class="line"> for (TypeElement typeElement : elements) {</span><br><span class="line"> //包名</span><br><span class="line"> String packageName = mElementUtils.getPackageOf(typeElement).getQualifiedName().toString();</span><br><span class="line"> //类名</span><br><span class="line"> String typeName = typeElement.getSimpleName().toString();</span><br><span class="line"> //全称类名</span><br><span class="line"> ClassName className = ClassName.get(packageName, typeName);</span><br><span class="line"> //自动生成类全称名</span><br><span class="line"> ClassName autoGenerationClassName = ClassName.get(packageName,</span><br><span class="line"> NameUtils.getAutoGeneratorTypeName(typeName));</span><br><span class="line"></span><br><span class="line"> //构建自动生成的类</span><br><span class="line"> TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(autoGenerationClassName)</span><br><span class="line"> .addModifiers(Modifier.PUBLIC)</span><br><span class="line"> .addAnnotation(Keep.class);</span><br><span class="line"></span><br><span class="line"> //添加构造方法</span><br><span class="line"> typeBuilder.addMethod(MethodSpec.constructorBuilder()</span><br><span class="line"> .addModifiers(Modifier.PUBLIC)</span><br><span class="line"> .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY)</span><br><span class="line"> .addStatement("$N($N)",</span><br><span class="line"> NameUtils.Method.BIND_VIEW,</span><br><span class="line"> NameUtils.Variable.ANDROID_ACTIVITY)</span><br><span class="line"> .build());</span><br><span class="line"></span><br><span class="line"> //添加bindView成员方法</span><br><span class="line"> MethodSpec.Builder bindViewBuilder = MethodSpec.methodBuilder(NameUtils.Method.BIND_VIEW)</span><br><span class="line"> .addModifiers(Modifier.PRIVATE)</span><br><span class="line"> .returns(TypeName.VOID)</span><br><span class="line"> .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY);</span><br><span class="line"></span><br><span class="line"> //添加方法内容</span><br><span class="line"> for (VariableElement variableElement : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) {</span><br><span class="line"> BindView bindView = variableElement.getAnnotation(BindView.class);</span><br><span class="line"> if (bindView != null) {</span><br><span class="line"> bindViewBuilder.addStatement("$N.$N=($T)$N.findViewById($L)",</span><br><span class="line"> NameUtils.Variable.ANDROID_ACTIVITY,</span><br><span class="line"> variableElement.getSimpleName(),</span><br><span class="line"> variableElement,</span><br><span class="line"> NameUtils.Variable.ANDROID_ACTIVITY,</span><br><span class="line"> bindView.value()[0]</span><br><span class="line"> ).addStatement("$N.$N.setText($N.getString($L))",</span><br><span class="line"> NameUtils.Variable.ANDROID_ACTIVITY,</span><br><span class="line"> variableElement.getSimpleName(),</span><br><span class="line"> NameUtils.Variable.ANDROID_ACTIVITY,</span><br><span class="line"> bindView.value()[1]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> typeBuilder.addMethod(bindViewBuilder.build());</span><br><span class="line"></span><br><span class="line"> //写入java文件</span><br><span class="line"> try {</span><br><span class="line"> JavaFile.builder(packageName, typeBuilder.build()).build().writeTo(mFiler);</span><br><span class="line"> } catch (IOException e) {</span><br><span class="line"> mMessager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), typeElement);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return true;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private Set<TypeElement> getTypeElementsByAnnotationType(Set<? extends TypeElement> annotations, Set<? extends Element> elements) {</span><br><span class="line"> ....</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> @Override</span><br><span class="line"> public Set<String> getSupportedAnnotationTypes() {</span><br><span class="line"> return new TreeSet<>(Arrays.asList(</span><br><span class="line"> BindView.class.getCanonicalName(),</span><br><span class="line"> Keep.class.getCanonicalName())</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个处理器中,按照一个类的创建顺序做了以下几步:</p>
<ol>
<li>生成构建的类</li>
<li>添加类的构造方法,并在构造方法中引用我们需要的<code>bindView</code>方法</li>
<li>为类添加<code>bindView</code>成员方法</li>
<li>向<code>bindView</code>方法中添加实现代码,也就是<code>findVieById</code>与<code>setText</code>的代码实现</li>
<li>通过<code>javaPoet</code>写入到<code>java</code>文件中</li>
</ol>
<p><code>JavaPoet</code>是一个用于生成Java代码的库,它提供了一套API来构建Java源代码,并且可以输出成Java文件。</p>
<p>经过上面的步骤,机会自动帮我们生成一个绑定View的代码类。</p>
<h3 id="注解运用"><a href="#注解运用" class="headerlink" title="注解运用"></a>注解运用</h3><p>接下来,我们来演示如何使用@BindView注解:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">class MainActivity : AppCompatActivity() {</span><br><span class="line"></span><br><span class="line"> @BindView(R.id.public_service, R.string.public_service)</span><br><span class="line"> lateinit var sName: TextView</span><br><span class="line"></span><br><span class="line"> @BindView(R.id.personal_wx, R.string.personal_wx)</span><br><span class="line"> lateinit var sPhone: TextView</span><br><span class="line"></span><br><span class="line"> override fun onCreate(savedInstanceState: Bundle?) {</span><br><span class="line"> super.onCreate(savedInstanceState)</span><br><span class="line"> setContentView(R.layout.activity_main)</span><br><span class="line"> Butterknife.bind(this)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在<code>MainActivity</code>中,我们使用了<code>@BindView</code>注解来标记<code>TextView</code>字段,然后在<code>onCreate</code>方法中调用<code>Butterknife.bind(this)</code>方法,即可自动为<code>textView</code>字段进行赋值,无需手动调用<code>findViewById</code>方法。</p>
<p><code>Butterknife</code>是一个自定义的类,内部提供<code>bind</code>方法,通过反射来构建上面我们自动生成的绑定类的实例。</p>
<h3 id="自动生成的类"><a href="#自动生成的类" class="headerlink" title="自动生成的类"></a>自动生成的类</h3><p>最后,再来看下自动生成的类的真正面目。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">@Keep</span><br><span class="line">public class MainActivity$Binding {</span><br><span class="line"> public MainActivity$Binding(MainActivity activity) {</span><br><span class="line"> bindView(activity);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private void bindView(MainActivity activity) {</span><br><span class="line"> activity.sName=(TextView)activity.findViewById(2131165265);</span><br><span class="line"> activity.sName.setText(activity.getString(2131427362));</span><br><span class="line"> activity.sPhone=(TextView)activity.findViewById(2131165262);</span><br><span class="line"> activity.sPhone.setText(activity.getString(2131427360));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="注意事项与优化技巧"><a href="#注意事项与优化技巧" class="headerlink" title="注意事项与优化技巧"></a>注意事项与优化技巧</h2><p>在使用APT时,有一些注意事项和优化技巧需要我们注意:</p>
<ul>
<li><strong>避免滥用APT</strong>: 虽然APT能够帮助我们实现很多高级功能,但是滥用APT会导致编译时间过长,增加项目的复杂度。因此,在使用APT时,需要权衡利弊,避免过度使用。</li>
<li><strong>优化代码生成</strong>: 在编写注解处理器时,需要尽量优化生成的代码,减少生成的代码量,提高代码的执行效率。</li>
<li><strong>处理异常情况</strong>: 在处理注解时,需要考虑到各种异常情况,比如注解不存在、注解参数错误等情况,从而提高代码的健壮性。</li>
</ul>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过本文的介绍,相信大家已经对APT有了更深入的理解,并且能够在实际的项目中运用APT来提高开发效率。APT作为一种强大的工具,在Android开发中有着广泛的应用前景,希望大家能够善加利用,发挥其最大的作用。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在Androi
你真的懂 Kotlin 中的 by 关键字吗?3分钟搞懂精髓
https://www.rousetime.com/2024/03/11/你真的懂-Kotlin-中的-by-关键字吗?3分钟搞懂精髓/
2024-03-11T00:42:52.000Z
2024-03-11T00:43:35.947Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Kotlin中,<code>by</code> 关键字主要用于实现委托模式。委托模式是一种设计模式,它允许一个对象将部分职责委托给另一个对象。在Kotlin中,<code>by</code> 关键字提供了一种简洁的语法,使得委托的实现变得更加轻松。</p>
<h2 id="委托模式概述"><a href="#委托模式概述" class="headerlink" title="委托模式概述"></a>委托模式概述</h2><p>在委托模式中,有两个主要角色:</p>
<ol>
<li><strong>委托类(Delegated Class):</strong> 持有实际的工作对象,负责将部分职责委托给这个对象。</li>
<li><strong>委托属性(Delegated Property):</strong> 在委托类中声明的属性,使用 <code>by</code> 关键字将其委托给其他类。</li>
</ol>
<h2 id="by关键字的工作原理"><a href="#by关键字的工作原理" class="headerlink" title="by关键字的工作原理"></a>by关键字的工作原理</h2><p>当使用 <code>by</code> 关键字将属性委托给其他类时,编译器会在后台生成一些额外的代码,实际上是将属性的 getter 和 setter 方法委托给特定的委托类。下面是一个简单的例子来说明 <code>by</code> 关键字的工作原理:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">Printer</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">print</span><span class="params">(message: <span class="type">String</span>)</span></span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DefaultPrinter</span> : <span class="type">Printer {</span></span></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">print</span><span class="params">(message: <span class="type">String</span>)</span></span> {</span><br><span class="line"> println(<span class="string">"Default: <span class="variable">$message</span>"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CustomPrinter</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> delegate: Printer) : Printer <span class="keyword">by</span> delegate</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">val</span> customPrinter = CustomPrinter(DefaultPrinter())</span><br><span class="line"> customPrinter.print(<span class="string">"Hello, Kotlin!"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个例子中,<code>CustomPrinter</code> 类通过 <code>by</code> 关键字将 <code>Printer</code> 接口的实现委托给了 <code>DefaultPrinter</code> 类。编译器会生成类似下面的代码:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CustomPrinter</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> delegate: Printer) : Printer {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">print</span><span class="params">(message: <span class="type">String</span>)</span></span> {</span><br><span class="line"> delegate.print(message)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>实际上,<code>CustomPrinter</code> 中的 <code>print</code> 方法被委托给了 <code>DefaultPrinter</code> 的 <code>print</code> 方法。</p>
<h2 id="自定义委托类"><a href="#自定义委托类" class="headerlink" title="自定义委托类"></a>自定义委托类</h2><p>除了使用接口作为委托的对象外,我们还可以自定义委托类。自定义委托类需要实现属性委托的接口,即具备 <code>getValue</code> 和 <code>setValue</code> 方法。以下是一个简单的自定义委托类的例子:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> kotlin.reflect.KProperty</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CustomDelegate</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">var</span> value: String = <span class="string">""</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> <span class="title">getValue</span><span class="params">(thisRef: <span class="type">Any</span>?, property: <span class="type">KProperty</span><*>)</span></span>: String {</span><br><span class="line"> println(<span class="string">"Getting value: <span class="variable">$value</span>"</span>)</span><br><span class="line"> <span class="keyword">return</span> value</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> <span class="title">setValue</span><span class="params">(thisRef: <span class="type">Any</span>?, property: <span class="type">KProperty</span><*>, newValue: <span class="type">String</span>)</span></span> {</span><br><span class="line"> println(<span class="string">"Setting value: <span class="variable">$newValue</span>"</span>)</span><br><span class="line"> value = newValue</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Example</span> </span>{</span><br><span class="line"> <span class="keyword">var</span> customProperty: String <span class="keyword">by</span> CustomDelegate()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">val</span> example = Example()</span><br><span class="line"> example.customProperty = <span class="string">"Hello, Kotlin!"</span></span><br><span class="line"> println(example.customProperty)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上面的例子中,<code>CustomDelegate</code> 类实现了属性委托的接口,通过重写 <code>getValue</code> 和 <code>setValue</code> 方法实现了属性的读取和设置。<code>Example</code> 类中的 <code>customProperty</code> 属性通过 <code>by</code> 关键字委托给了 <code>CustomDelegate</code> 类。</p>
<h2 id="lazy原理"><a href="#lazy原理" class="headerlink" title="lazy原理"></a>lazy原理</h2><p>有了上面的基础,再来看<code>lazy</code>的实现就非常简单。</p>
<p><code>lazy</code> 是 Kotlin 标准库中的一个函数,用于实现延迟初始化。它的主要作用是将一个 lambda 表达式作为参数传递给 <code>lazy</code> 函数,该 lambda 表达式将在首次访问属性时执行,并且只会执行一次。<code>lazy</code> 返回一个 <code>Lazy</code> 类型的实例,该实例包含一个被委托的属性,以及相应的初始化逻辑。</p>
<p>以下是 <code>lazy</code> 的简化实现原理,为了更好地理解,我们将采用伪代码的形式:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Lazy</span><<span class="type">T</span>></span>(<span class="keyword">private</span> <span class="keyword">val</span> initializer: () -> T) {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">var</span> value: T? = <span class="literal">null</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">var</span> isInitialized: <span class="built_in">Boolean</span> = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> <span class="title">getValue</span><span class="params">(thisRef: <span class="type">Any</span>?, property: <span class="type">KProperty</span><*>)</span></span>: T {</span><br><span class="line"> <span class="keyword">if</span> (!isInitialized) {</span><br><span class="line"> value = initializer()</span><br><span class="line"> isInitialized = <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> value!!</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="type"><T></span> <span class="title">lazy</span><span class="params">(initializer: () -> <span class="type">T</span>)</span></span>: Lazy<T> = Lazy(initializer)</span><br></pre></td></tr></table></figure>
<p>上述代码中,我们定义了一个 <code>Lazy</code> 类,它接受一个 lambda 表达式 <code>initializer</code>,这个 lambda 表达式包含了属性首次访问时的初始化逻辑。<code>Lazy</code> 类包含一个泛型参数 <code>T</code>,表示被委托的属性的类型。</p>
<ul>
<li><code>value</code> 存储被委托属性的值,初始值为 null。</li>
<li><code>isInitialized</code> 用于追踪属性是否已经被初始化。</li>
</ul>
<p><code>Lazy</code> 类还实现了 <code>getValue</code> 操作符函数,这是属性委托的关键。当被委托的属性首次被访问时,<code>getValue</code> 函数会执行 <code>initializer</code> lambda 表达式,初始化属性的值,并将 <code>isInitialized</code> 设置为 true。之后,再次访问该属性时,直接返回已经初始化过的值。</p>
<p>最后,我们通过 <code>lazy</code> 函数创建了一个 <code>Lazy</code> 类的实例,用于实际的属性委托。在实际使用中,<code>lazy</code> 函数可以直接作为属性的委托,如下所示:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> myProperty: String <span class="keyword">by</span> lazy {</span><br><span class="line"> println(<span class="string">"Initializing myProperty"</span>)</span><br><span class="line"> <span class="string">"Hello, Kotlin!"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> println(myProperty) <span class="comment">// 第一次访问,会执行初始化逻辑</span></span><br><span class="line"> println(myProperty) <span class="comment">// 后续访问,直接返回已初始化的值</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上述例子中,<code>myProperty</code> 的初始化逻辑只在首次访问时执行,之后的访问直接返回已经初始化的值。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过 <code>by</code> 关键字,Kotlin 提供了一种优雅而强大的委托模式实现方式。无论是通过接口还是自定义委托类,都能够轻松地实现代码的重用和解耦。了解 <code>by</code> 关键字的实现原理有助于更深入地理解 Kotlin 的委托模式,并在实际开发中更加灵活地运用。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Kotlin
光速2分钟,精通HandlerThread,你还在等什么?
https://www.rousetime.com/2024/03/10/光速2分钟,精通HandlerThread,你还在等什么?/
2024-03-10T09:03:56.000Z
2024-03-10T09:04:17.371Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p><code>HandlerThread</code>是<code>Android</code>中的一个重要类,它可以帮助我们在子线程中方便地使用<code>Handler</code>。在本文中,我们将详细介绍<code>HandlerThread</code>的原理和运用,并结合代码示例,帮助读者更好地理解和掌握<code>HandlerThread</code>的使用方法。</p>
<h2 id="什么是HandlerThread?"><a href="#什么是HandlerThread?" class="headerlink" title="什么是HandlerThread?"></a>什么是HandlerThread?</h2><p>在Android开发中,<code>HandlerThread</code>是<code>Thread</code>的一个特殊子类,它结合了线程和消息处理机制,使得在一个单独的线程中执行任务变得更加简便。它内部包含一个<code>Looper</code>和一个<code>Handler</code>,使得我们可以方便地发送和处理消息。</p>
<h2 id="HandlerThread的原理"><a href="#HandlerThread的原理" class="headerlink" title="HandlerThread的原理"></a>HandlerThread的原理</h2><h3 id="Looper"><a href="#Looper" class="headerlink" title="Looper"></a>Looper</h3><p><code>Looper</code>是<code>HandlerThread</code>的核心组件之一,它负责建立消息队列,并按照队列中的顺序处理消息。每个<code>HandlerThread</code>都有一个独立的<code>Looper</code>,确保线程之间消息的独立性。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> handlerThread = HandlerThread(<span class="string">"MyHandlerThread"</span>)</span><br><span class="line">handlerThread.start()</span><br><span class="line"><span class="keyword">val</span> looper = handlerThread.looper</span><br></pre></td></tr></table></figure>
<h3 id="Handler"><a href="#Handler" class="headerlink" title="Handler"></a>Handler</h3><p><code>Handler</code>是与<code>Looper</code>关联的,用于发送和处理消息。它与特定的线程相关联,因此当我们使用<code>HandlerThread</code>中<code>Handler</code>时,它会自动与该线程的<code>Looper</code>绑定。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">@NonNull</span><br><span class="line">public Handler getThreadHandler() {</span><br><span class="line"> if (mHandler == null) {</span><br><span class="line"> mHandler = new Handler(getLooper());</span><br><span class="line"> }</span><br><span class="line"> return mHandler;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="HandlerThread的run-方法"><a href="#HandlerThread的run-方法" class="headerlink" title="HandlerThread的run()方法"></a>HandlerThread的run()方法</h3><p><code>HandlerThread</code>的<code>run()</code>方法是其核心方法,该方法负责创建<code>Looper</code>并启动消息循环。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">HandlerThread</span></span>(name: String) : Thread(name) {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">run</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 初始化 Looper</span></span><br><span class="line"> Looper.prepare()</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 启动消息循环</span></span><br><span class="line"> Looper.loop()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在<code>run()</code>方法中,首先调用<code>Looper.prepare()</code>方法来初始化<code>Looper。Looper.prepare()</code>方法会创建一个<code>Looper</code>对象,并将其绑定到当前线程。</p>
<p>然后,调用<code>Looper.loop()</code>方法来启动消息循环。<code>Looper.loop()</code>方法会一直从消息队列中获取消息,并将消息交给<code>Handler</code>处理。</p>
<h3 id="消息处理流程"><a href="#消息处理流程" class="headerlink" title="消息处理流程"></a>消息处理流程</h3><ol>
<li>创建<code>HandlerThread</code>并启动。</li>
<li>通过<code>HandlerThread</code>的<code>Looper</code>创建<code>Handler</code>。</li>
<li>使用<code>Handler</code>发送消息。</li>
<li>在<code>HandlerThread</code>的<code>Looper</code>中处理消息。</li>
</ol>
<h3 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h3><p><code>HandlerThread</code>适用于以下场景:</p>
<ul>
<li>在子线程中执行耗时操作,如网络请求、文件读写等。</li>
<li>可以用来执行定时任务。</li>
<li>在子线程中与其他线程通信。</li>
</ul>
<h2 id="HandlerThread的使用"><a href="#HandlerThread的使用" class="headerlink" title="HandlerThread的使用"></a>HandlerThread的使用</h2><h3 id="创建HandlerThread"><a href="#创建HandlerThread" class="headerlink" title="创建HandlerThread"></a>创建HandlerThread</h3><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> handlerThread = HandlerThread(<span class="string">"MyHandlerThread"</span>)</span><br><span class="line">handlerThread.start()</span><br></pre></td></tr></table></figure>
<p>上述代码会创建一个名为<code>MyHandlerThread</code>的<code>HandlerThread</code>。然后,调用<code>start()</code>方法来启动<code>HandlerThread</code>。</p>
<h3 id="创建Handler"><a href="#创建Handler" class="headerlink" title="创建Handler"></a>创建Handler</h3><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> handler = Handler(handlerThread.looper)</span><br></pre></td></tr></table></figure>
<p>上述代码会为<code>HandlerThread</code>创建一个<code>Handler</code>。并将当前的<code>HandlerThread</code>的<code>looper</code>传递进去。让<code>Handler</code>能够向其中推送消息。</p>
<h3 id="发送消息"><a href="#发送消息" class="headerlink" title="发送消息"></a>发送消息</h3><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">handler.post {</span><br><span class="line"> <span class="comment">// 在HandlerThread中执行的任务</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="停止HandlerThread"><a href="#停止HandlerThread" class="headerlink" title="停止HandlerThread"></a>停止HandlerThread</h3><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">handlerThread.quit()</span><br></pre></td></tr></table></figure>
<h2 id="注意事项与优化技巧"><a href="#注意事项与优化技巧" class="headerlink" title="注意事项与优化技巧"></a>注意事项与优化技巧</h2><ol>
<li><p><strong>避免内存泄漏:</strong> 在退出页面或不再需要<code>HandlerThread</code>时,务必调用<code>handlerThread.quit()</code>或<code>handlerThread.quitSafely()</code>以防止内存泄漏。</p>
</li>
<li><p><strong>合理使用消息队列:</strong> 不要在<code>HandlerThread</code>上发送过多的消息,以免影响性能。合理利用消息的优先级和延迟机制。</p>
</li>
<li><p><strong>处理异常:</strong> 在<code>HandlerThread</code>的任务中,适当地处理异常,避免因未捕获的异常导致线程崩溃。</p>
</li>
<li><p><strong>线程安全:</strong> <code>HandlerThread</code>是线程安全的,但是<code>Handler</code>不一定是线程安全的。因此,在使用<code>Handler</code>时,需要注意线程安全问题。</p>
</li>
</ol>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p><code>HandlerThread</code>是一个非常有用的类,它可以帮助我们在子线程中方便地使用<code>Handler</code>。在使用<code>HandlerThread</code>时,需要注意其原理和注意事项,以便更好的运用它。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p><code>HandlerT
你真的了解ViewModel的设计思想吗?
https://www.rousetime.com/2024/02/01/你真的了解ViewModel的设计思想吗?/
2024-02-01T02:19:18.000Z
2024-02-01T02:20:05.759Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在<code>Android</code>开发中,数据的管理是一个至关重要的问题。随着应用复杂度的增加,我们需要一种能够有效管理数据和处理UI相关逻辑的机制。<code>Android</code>架构组件中的<code>ViewModel</code>应运而生。本文将深入探讨<code>ViewModel</code>的原理,并介绍其高级运用,旨在帮助开发者更好地理解和运用这一组件。</p>
<h2 id="什么是ViewModel?"><a href="#什么是ViewModel?" class="headerlink" title="什么是ViewModel?"></a>什么是ViewModel?</h2><p><code>ViewModel</code>是一种设计模式,它的目标是将UI控制器(Activity、Fragment等)与数据分离,同时保持UI的状态。在<code>Android</code>中,<code>ViewModel</code>通常用于存储和管理与UI相关的数据,以确保这些数据在屏幕旋转或配置更改等情况下不会丢失。</p>
<h2 id="原理解析"><a href="#原理解析" class="headerlink" title="原理解析"></a>原理解析</h2><p><code>ViewModel</code>的原理是基于<code>ViewModelStore</code>类。<code>ViewModelStore</code>类是一个存储<code>ViewModel</code>的容器。当UI控制器创建时,系统会为其创建一个<code>ViewModelStore</code>实例。当 UI控制器销毁时,系统会销毁其对应的<code>ViewModelStore</code>实例。</p>
<p><code>ViewModel</code>在创建时,会将自身注册到其所在的<code>ViewModelStore</code>实例中。当UI控制器销毁时,系统会从其对应的<code>ViewModelStore</code>实例中移除<code>ViewModel</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">open class ViewModelStore {</span><br><span class="line"></span><br><span class="line"> //存储viewmodel</span><br><span class="line"> private val map = mutableMapOf<String, ViewModel>()</span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 自动创建</span><br><span class="line">getLifecycle().addObserver(new LifecycleEventObserver() {</span><br><span class="line"> @Override</span><br><span class="line"> public void onStateChanged(@NonNull LifecycleOwner source,</span><br><span class="line"> @NonNull Lifecycle.Event event) {</span><br><span class="line"> ensureViewModelStore();</span><br><span class="line"> getLifecycle().removeObserver(this);</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">// 销毁</span><br><span class="line">getLifecycle().addObserver(new LifecycleEventObserver() {</span><br><span class="line"> @Override</span><br><span class="line"> public void onStateChanged(@NonNull LifecycleOwner source,</span><br><span class="line"> @NonNull Lifecycle.Event event) {</span><br><span class="line"> if (event == Lifecycle.Event.ON_DESTROY) {</span><br><span class="line"> // Clear out the available context</span><br><span class="line"> mContextAwareHelper.clearAvailableContext();</span><br><span class="line"> // And clear the ViewModelStore</span><br><span class="line"> if (!isChangingConfigurations()) {</span><br><span class="line"> getViewModelStore().clear();</span><br><span class="line"> }</span><br><span class="line"> mReportFullyDrawnExecutor.activityDestroyed();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>所以<code>ViewModelStore</code>的创建与<code>ViewModel</code>的销毁,都与<code>Lifecycle</code>有关,这样就让<code>ViewModel</code>具有以下特性:</p>
<ol>
<li>生命周期感知: <code>ViewModel</code>是生命周期感知的,它会自动跟踪UI控制器的生命周期,并在适当的时候进行清理。这意味着我们无需担心内存泄漏或不必要的资源占用。</li>
<li>持久性存储: <code>ViewModel</code>可以存储大量的数据,并且在配置更改时仍然保持活动。这是通过将<code>ViewModel</code>与UI控制器分离,将其保存在<code>ViewModelStore</code>中实现的。</li>
<li>数据共享: 多个UI控制器可以共享同一个<code>ViewModel</code>,这为不同组件之间的数据共享提供了便利。这对于在Activity和Fragment之间传递数据非常有用。</li>
</ol>
<h2 id="为什么需要ViewModelStore"><a href="#为什么需要ViewModelStore" class="headerlink" title="为什么需要ViewModelStore"></a>为什么需要ViewModelStore</h2><p><code>ViewModelStore</code>是用于管理<code>ViewModel</code>实例的生命周期的一种机制。它的存在是为了解决以下问题:</p>
<ol>
<li>生命周期一致性:在Android开发中,我们经常遇到配置更改(如屏幕旋转)导致Activity或Fragment被销毁并重新创建的情况。这种情况下,我们希望保持与UI相关的数据的一致性,即使UI重新创建,数据也不会丢失。<code>ViewModelStore</code>通过管理<code>ViewModel</code>实例的生命周期,确保在重新创建UI时,旧的<code>ViewModel</code>实例被正确地销毁,而新的<code>ViewModel</code>实例被正确地创建。</li>
<li>资源管理:每个<code>ViewModel</code>实例可能持有一些资源,如数据库连接、网络连接等。如果这些资源没有被正确地释放,就会导致内存泄漏和资源浪费。<code>ViewModelStore</code>通过在适当的时机销毁<code>ViewModel</code>实例,确保这些资源可以被正确地释放,避免了内存泄漏和资源浪费。</li>
<li>数据共享:<code>ViewModelStore</code>允许多个组件共享同一个<code>ViewModel</code>实例。这在某些情况下非常有用,比如一个Activity和它的多个Fragment需要访问和更新相同的数据。通过使用<code>ViewModelStore</code>,这些组件可以共享同一个<code>ViewModel</code>实例,避免了数据的重复加载和同步问题。</li>
</ol>
<h2 id="简单示例"><a href="#简单示例" class="headerlink" title="简单示例"></a>简单示例</h2><p>以下是一个简单的<code>ViewModel</code>示例,演示了如何使用<code>ViewModel</code>来保存和管理数据:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyViewModel</span> : <span class="type">ViewModel</span></span>() {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> state = MutableLiveData<String>()</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">setData</span><span class="params">(value: <span class="type">String</span>)</span></span> {</span><br><span class="line"> sate.value = value</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">getData</span><span class="params">()</span></span>: LiveData<String> {</span><br><span class="line"> <span class="keyword">return</span> state</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyActivity</span> : <span class="type">AppCompatActivity</span></span>() {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> viewModel: MyViewModel <span class="keyword">by</span> viewModels()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">(savedInstanceState: <span class="type">Bundle</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState)</span><br><span class="line"> setContentView(R.layout.activity_main)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 观察 ViewModel 的数据变化</span></span><br><span class="line"> viewModel.getData().observe(<span class="keyword">this</span>, { <span class="keyword">data</span> -></span><br><span class="line"> <span class="comment">// 更新 UI</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上述代码中,<code>ViewModel</code>包含一个<code>MutableLiveData</code>来存储数据。通过setData和getData方法,我们可以设置和获取数据。这个<code>ViewModel</code>将在配置更改时保持活动,确保数据不会丢失。</p>
<h2 id="高级运用"><a href="#高级运用" class="headerlink" title="高级运用"></a>高级运用</h2><h3 id="使用SavedStateHandle"><a href="#使用SavedStateHandle" class="headerlink" title="使用SavedStateHandle"></a>使用SavedStateHandle</h3><p><code>SavedStateHandle</code>是一个可用于在配置更改后保持数据的工具。它允许我们将数据与<code>ViewModel</code>关联,以便在应用重新创建时检索。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyViewModel</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> savedStateHandle: SavedStateHandle) : ViewModel() {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> dataKey = <span class="string">"data_key"</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">setData</span><span class="params">(value: <span class="type">String</span>)</span></span> {</span><br><span class="line"> savedStateHandle.<span class="keyword">set</span>(dataKey, value)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">getData</span><span class="params">()</span></span>: String? {</span><br><span class="line"> <span class="keyword">return</span> savedStateHandle.<span class="keyword">get</span>(dataKey)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="处理异步操作"><a href="#处理异步操作" class="headerlink" title="处理异步操作"></a>处理异步操作</h3><p><code>ViewModel</code>可以与协程结合,以处理异步操作。这使得在<code>ViewModel</code>中执行耗时操作成为可能,而不会阻塞UI线程。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyViewModel</span> : <span class="type">ViewModel</span></span>() {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> <span class="keyword">data</span> = MutableLiveData<String>()</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">fetchData</span><span class="params">()</span></span> {</span><br><span class="line"> viewModelScope.launch {</span><br><span class="line"> <span class="comment">// 执行耗时操作</span></span><br><span class="line"> <span class="keyword">val</span> result = fetchDataFromRepository()</span><br><span class="line"> <span class="keyword">data</span>.value = result</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">getData</span><span class="params">()</span></span>: LiveData<String> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">data</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="使用Factory"><a href="#使用Factory" class="headerlink" title="使用Factory"></a>使用Factory</h3><p><code>ViewModelProvider.Factory</code>用于自定义<code>ViewModel</code>的创建过程,可以传递参数<code>ViewModel</code>的构造函数。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">class MyViewModelFactory(private val repository: MyRepository) : ViewModelProvider.Factory {</span><br><span class="line"></span><br><span class="line"> override fun <T : ViewModel?> create(modelClass: Class<T>): T {</span><br><span class="line"> if (modelClass.isAssignableFrom(MyViewModel::class.java)) {</span><br><span class="line"> return MyViewModel(repository) as T</span><br><span class="line"> }</span><br><span class="line"> throw IllegalArgumentException("Unknown ViewModel class")</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="注意事项和优化技巧"><a href="#注意事项和优化技巧" class="headerlink" title="注意事项和优化技巧"></a>注意事项和优化技巧</h2><ul>
<li>避免在<code>ViewModel</code>中持有<code>View</code>的引用,以防止内存泄漏。</li>
<li>将<code>ViewModel</code>的职责限制在处理UI相关的逻辑,不要包含过多的业务逻辑。</li>
<li>谨慎使用<code>SavedStateHandle</code>,避免将大量数据存储在其中导致性能问题。</li>
</ul>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过深入理解<code>ViewModel</code>的原理和高级运用,我们可以更好地利用这一强大的架构组件。<code>ViewModel</code>的设计模式和生命周期感知使其成为<code>Android</code>开发中不可或缺的一部分。希望本文能够帮助大家更好地应用和理解<code>ViewModel</code>。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在<code>
Android Lifecycle 深度解剖:三大类、五种状态、七大事件全面解读!
https://www.rousetime.com/2024/01/24/Android-Lifecycle-深度解剖:三大类、五种状态、七大事件全面解读!/
2024-01-24T01:49:51.000Z
2024-01-24T01:50:23.263Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Android开发领域,掌握Jetpack是一位专业Android开发者必备的技能。本文将围绕Android Jetpack展开,深度解析面试中可能涉及到的高级疑难问题,我将分享一些关于Android Jetpack的面试技巧,帮助你更好地准备面试。</p>
<h2 id="Navigation组件的理解"><a href="#Navigation组件的理解" class="headerlink" title="Navigation组件的理解"></a>Navigation组件的理解</h2><p><strong>问题:</strong> 请详细说明Navigation组件的使用场景以及与传统Fragment事务的比较。</p>
<p><strong>出发点:</strong> 在回答这个问题时,要突出Navigation组件的优势,以及它在处理导航和页面交互时相对于传统Fragment事务的创新之处。</p>
<p><strong>参考简答:</strong><br>Navigation组件是Jetpack中用于处理应用内导航的强大工具。它的使用场景包括但不限于:</p>
<ul>
<li><strong>单一活动多Fragment架构:</strong> 通过将所有Fragment集中在一个活动中,简化了导航的管理和传递数据的复杂性。</li>
<li><strong>深层链接:</strong> 支持通过深层链接直接导航到应用中的特定目标,提高用户体验。</li>
<li><strong>类型安全的导航:</strong> 使用安全Args插件,避免了传统Bundle传递参数时的类型错误。</li>
</ul>
<p>相对于传统Fragment事务,Navigation组件的优势在于:</p>
<ul>
<li><strong>导航图的可视化:</strong> 使用导航图直观展示应用中的导航流程,方便理解和修改。</li>
<li><strong>类型安全:</strong> 利用Kotlin的类型安全特性,减少在导航时的错误。</li>
<li><strong>生命周期感知:</strong> 自动处理Fragment的生命周期,避免了一些常见的生命周期相关问题。</li>
</ul>
<p><strong>问题:</strong> 请解释Navigation组件的作用,并介绍Navigation组件的核心组件以及它们之间的关系。</p>
<p><strong>出发点:</strong> 解释说明Navigation的几个核心组件,以及他们的作用。</p>
<p><strong>参考简答:</strong></p>
<p>其作用和核心组件包括:</p>
<ul>
<li><p><strong>作用:</strong> Navigation组件用于实现应用内的导航结构,使得从一个目的地(Destination)到另一个目的地的导航变得更加容易管理和统一。</p>
</li>
<li><p><strong>核心组件:</strong></p>
<ul>
<li><strong>NavGraph(导航图):</strong> 包含应用中所有目的地和它们之间的导航关系。</li>
<li><strong>NavController(导航控制器):</strong> 管理导航操作的控制器,负责管理与目的地的交互。</li>
<li><strong>NavDestination(导航目的地):</strong> 表示导航图中的一个页面或操作,定义了目的地的属性和行为。</li>
</ul>
</li>
</ul>
<p>这三个核心组件共同构建了整个导航体系,使得在Android应用中实现复杂的导航结构变得更加简单和可维护。</p>
<h2 id="ViewModel与LiveData的理解"><a href="#ViewModel与LiveData的理解" class="headerlink" title="ViewModel与LiveData的理解"></a>ViewModel与LiveData的理解</h2><p><strong>问题:</strong> 请详细说明ViewModel的作用,并介绍使用ViewModel的主要优势。</p>
<p><strong>出发点:</strong> 在解答这个问题时,不仅要强调ViewModel的用途,还要深入讨论其在Android架构中的角色和优势。</p>
<p><strong>参考简答:</strong></p>
<p>ViewModel的作用在于解决Android应用中活动和碎片(Fragment)的生命周期问题。它允许数据在屏幕旋转等配置更改时存活,并确保数据在不同组件之间共享而不丢失。主要优势包括:</p>
<ul>
<li>生命周期感知:ViewModel能够感知与UI相关的生命周期变化,确保数据存活时间比短暂的UI组件更长。</li>
<li>数据共享:通过ViewModel,可以在不同的UI组件之间共享和管理数据,避免重复加载或丢失数据。</li>
<li>状态保存:ViewModel在配置变更时保持其状态,例如屏幕旋转,避免重新加载数据和执行耗时操作。</li>
</ul>
<p><strong>问题:</strong> 详细说明LiveData和ViewModel的工作原理,并讨论在实际项目中如何解决常见的生命周期问题。</p>
<p><strong>出发点:</strong> 对LiveData和ViewModel的底层机制有深入理解,能够在复杂的生命周期场景中保证数据的正确性。</p>
<p><strong>参考简答:</strong></p>
<p>LiveData是一种可观察的数据持有者,ViewModel用于存储和管理与用户界面相关的数据。深入理解包括:</p>
<ul>
<li><p><strong>LiveData的粘性事件:</strong> 了解<code>postValue</code>和<code>setValue</code>的区别,以及如何避免LiveData的粘性事件在特定场景中引发的问题。</p>
</li>
<li><p><strong>ViewModel的存活周期:</strong> 使用<code>ViewModel</code>正确处理配置变化,保证数据在屏幕旋转等情况下不丢失。</p>
</li>
<li><p><strong>LiveData和View绑定:</strong> 结合<code>DataBinding</code>,实现LiveData与View之间的绑定,确保数据的实时更新。</p>
</li>
</ul>
<p><strong>问题:</strong> 请对比LiveData和Observable,分析它们在Android应用中的应用场景,以及在何种情况下选择使用哪种。</p>
<p><strong>出发点:</strong> 这个问题涉及到Android应用中的数据响应式编程,考察面试者对LiveData和RxJava的理解,以及在实际应用中的合理选择。</p>
<p><strong>参考简答:</strong></p>
<p>LiveData和Observable都是用于实现响应式编程的工具,但有一些关键区别:</p>
<ul>
<li><p><strong>生命周期感知:</strong> LiveData是生命周期感知的,它会在观察者(通常是UI组件)的生命周期内自动启动和停止。这使得在处理UI数据时更加安全,避免了潜在的内存泄漏。</p>
</li>
<li><p><strong>背压处理:</strong> Observable在RxJava中通常使用背压策略来处理数据流,而LiveData则通过生命周期感知来实现反应式响应,避免了背压问题。</p>
</li>
</ul>
<p>根据实际需求,选择使用LiveData还是Observable取决于应用的具体场景。对于需要与UI组件绑定的数据,以及对生命周期敏感的场景,LiveData是更好的选择。而在需要更强大的操作符和背压处理的情况下,可以考虑使用Observable。</p>
<h2 id="Paging库的性能优化"><a href="#Paging库的性能优化" class="headerlink" title="Paging库的性能优化"></a>Paging库的性能优化</h2><p><strong>问题:</strong> 请解释Paging库的基本原理,并提出在处理大型数据集时如何进行性能优化。</p>
<p><strong>出发点:</strong> 考察队Paging的日常运用,可以从它的一些特性进行回答</p>
<p><strong>参考简答:</strong> </p>
<p>Paging库通过在RecyclerView中异步加载数据,实现了对大型数据集的高效处理。性能优化的关键在于以下几个方面:</p>
<ul>
<li><p><strong>DataSource的定制:</strong> 实现自定义的<code>DataSource</code>,根据实际需求定制加载规则,如预加载、缓存等。</p>
</li>
<li><p><strong>数据缓存策略:</strong> 使用<code>BoundaryCallback</code>来实现边界回调,可以在数据边界到达时触发预加载,减少用户等待时间。</p>
</li>
<li><p><strong>DiffUtil的合理使用:</strong> 配合<code>PagedListAdapter</code>,使用<code>DiffUtil</code>来计算并刷新列表的差异,减少不必要的数据刷新,提高界面流畅度。</p>
</li>
</ul>
<h2 id="WorkManager的高级任务调度"><a href="#WorkManager的高级任务调度" class="headerlink" title="WorkManager的高级任务调度"></a>WorkManager的高级任务调度</h2><p><strong>问题:</strong> 请解释WorkManager的工作原理,并讨论在需要复杂任务调度时如何设计和实现。</p>
<p><strong>出发点:</strong> 可以从WorkManager的调度方式进行分析</p>
<p><strong>参考简答:</strong></p>
<p>WorkManager是一种用于在后台执行任务的库,它建立在JobScheduler、AlarmManager和JobIntentService之上。在高级任务调度中,我们可以:</p>
<ul>
<li><p><strong>自定义Worker:</strong> 实现<code>Worker</code>类以执行具体任务,并通过<code>Constraints</code>来定义任务的触发条件,如网络状态、电量等。</p>
</li>
<li><p><strong>任务链和顺序执行:</strong> 使用<code>OneTimeWorkRequest</code>和<code>WorkContinuation</code>来构建任务链,实现复杂任务的顺序执行。</p>
</li>
<li><p><strong>灵活的重试机制:</strong> 结合<code>BackoffPolicy</code>,实现任务的灵活重试策略,应对不同类型的任务失败情况。</p>
</li>
</ul>
<h2 id="Hilt依赖注入的优势和基本原理"><a href="#Hilt依赖注入的优势和基本原理" class="headerlink" title="Hilt依赖注入的优势和基本原理"></a>Hilt依赖注入的优势和基本原理</h2><p><strong>问题:</strong> 请解释Hilt依赖注入框架的优势,以及在Android应用中的使用方式。</p>
<p><strong>出发点:</strong> 考察面试者对依赖注入的理解以及在Android开发中的应用经验。</p>
<p><strong>参考简答:</strong></p>
<p>Hilt作为依赖注入框架,具有以下优势:</p>
<ul>
<li><strong>简化依赖注入:</strong> Hilt通过标准化依赖注入的方式,大大简化了在Android应用中的依赖注入过程,减少了样板代码。</li>
<li><strong>与Jetpack集成:</strong> Hilt与其他Jetpack组件无缝集成,使得在使用其他Jetpack库时能够更加方便地进行依赖注入。</li>
</ul>
<p>通过在应用类上添加<code>@HiltAndroidApp</code>注解,以及使用<code>@Inject</code>注解来标记依赖关系,等多种注解,可以轻松地实现依赖注入。</p>
<h2 id="Room数据库的性能优化"><a href="#Room数据库的性能优化" class="headerlink" title="Room数据库的性能优化"></a>Room数据库的性能优化</h2><p><strong>问题:</strong> 在使用Room数据库时,有哪些性能优化的手段可以提高数据库访问的效率?</p>
<p><strong>出发点:</strong> 了解在实际项目中,如何通过一些技巧提高Room数据库的性能。</p>
<p><strong>参考简答:</strong><br>Room数据库的性能优化手段包括:</p>
<ul>
<li><strong>合理使用索引:</strong> 根据查询需求创建合适的索引,提高查询效率。</li>
<li><strong>批量操作:</strong> 使用<code>@Transaction</code>注解将多个操作放在同一个事务中,减少数据库事务的开销。</li>
<li><strong>异步查询:</strong> 在后台线程执行查询操作,避免在主线程中执行耗时的数据库操作,防止ANR。</li>
<li><strong>适度使用Room的内存缓存:</strong> 使用<code>@Query</code>注解的<code>LiveData</code>返回值时,Room会自动在内存中维护缓存,但要注意不要过度依赖,以免造成内存浪费。</li>
</ul>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>在Android Jetpack面试中,深入理解核心库的使用和底层原理是展现专业水平的关键。希望这些面试技巧能够帮助各位Android开发者更好地准备面试,展现出自己的技术实力。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Androi
3分钟搞定,学会Android滑动冲突解决技巧
https://www.rousetime.com/2024/01/23/3分钟搞定,学会Android滑动冲突解决技巧/
2024-01-23T02:08:45.000Z
2024-01-23T02:09:14.120Z
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><code>Android</code>滑动冲突是<code>Android</code>开发中常见的问题。在一个界面中,可能存在多个<code>View</code>可以响应滑动事件。如果这些<code>View</code>滑动方向一致,则会导致滑动冲突。本文将从原理、使用与优化三个方面,详细介绍<code>Android</code>滑动冲突的解决方式。</p>
<h2 id="滑动冲突的原理"><a href="#滑动冲突的原理" class="headerlink" title="滑动冲突的原理"></a>滑动冲突的原理</h2><p><code>Android</code>的事件分发机制是基于<code>ViewGroup</code>的。当用户在屏幕上触摸时,事件会首先传递给最顶层的<code>ViewGroup</code>。<code>ViewGroup</code>会根据自己的滑动方向和滑动能力来决定是否拦截事件。如果<code>ViewGroup</code>拦截了事件,则事件不会传递给子<code>View</code>。如果<code>ViewGroup</code>没有拦截事件,则事件会传递给子<code>View</code>。</p>
<p>如果子<code>View</code>也需要响应滑动事件,则子<code>View</code>需要重写<code>onTouchEvent()</code>方法来处理事件。子<code>View</code>可以通过<code>requestDisallowInterceptTouchEvent()</code>方法来告诉父<code>ViewGroup</code>不要拦截事件。</p>
<p>滑动冲突是指两个或多个<code>View</code>同时收到滑动事件,导致无法正常滑动。滑动冲突的原因有很多,例如:</p>
<ol>
<li>两个<code>View</code>的滑动方向相同,例如<code>RecyclerView</code>和<code>ScrollView</code>同时滑动。</li>
<li>两个<code>View</code>的滑动方向不同,但滑动范围重叠,例如<code>HorizontalScrollView</code>和<code>WebView</code>同时滑动。</li>
</ol>
<h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p><code>Android</code>滑动冲突的主要解决思想有两种:外部拦截法和内部拦截法。</p>
<ul>
<li>外部拦截法:由父<code>View</code>拦截事件,然后根据需要将事件传递给子<code>View</code>。</li>
<li>内部拦截法:由子<code>View</code>拦截事件,然后根据需要将事件传递给父<code>View</code>。</li>
</ul>
<h3 id="外部拦截法"><a href="#外部拦截法" class="headerlink" title="外部拦截法"></a>外部拦截法</h3><p>外部拦截法是<code>Android</code>默认的滑动冲突解决方式。在这种方式下,父<code>View</code>会先拦截事件,然后根据需要将事件传递给子<code>View</code>。</p>
<p>父<code>View</code>可以通过重写<code>onInterceptTouchEvent()</code>方法来实现外部拦截法。在<code>onInterceptTouchEvent()</code>方法中,我们可以根据事件的类型和位置来判断是否需要拦截事件。如果需要拦截事件,则返回<code>true</code>,否则返回<code>false</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">class CustomParentView(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs) {</span><br><span class="line"></span><br><span class="line"> private var downX: Float = 0F</span><br><span class="line"> private var downY: Float = 0F</span><br><span class="line"></span><br><span class="line"> override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {</span><br><span class="line"> when (ev.action) {</span><br><span class="line"> MotionEvent.ACTION_DOWN -> {</span><br><span class="line"> downX = ev.x</span><br><span class="line"> downY = ev.y</span><br><span class="line"> }</span><br><span class="line"> MotionEvent.ACTION_MOVE -> {</span><br><span class="line"> val deltaX = ev.x - downX</span><br><span class="line"> val deltaY = ev.y - downY</span><br><span class="line"></span><br><span class="line"> // 根据滑动方向判断是否拦截事件</span><br><span class="line"> if (Math.abs(deltaX) > Math.abs(deltaY)) {</span><br><span class="line"> return true</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return super.onInterceptTouchEvent(ev)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> override fun onTouchEvent(event: MotionEvent): Boolean {</span><br><span class="line"> // 处理滑动逻辑</span><br><span class="line"> return true</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 其他相关代码</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>优点:</strong> 简单易用,适用于大多数滑动冲突问题。</p>
<p><strong>缺点:</strong> 可能会导致父<code>ViewGroup</code>无法响应事件,例如父<code>ViewGroup</code>的子<code>View</code>正在滑动,而父<code>ViewGroup</code>的滑动事件也被拦截了。</p>
<h3 id="内部拦截法"><a href="#内部拦截法" class="headerlink" title="内部拦截法"></a>内部拦截法</h3><p>内部拦截法是指由子<code>View</code>拦截事件,然后根据需要将事件传递给父<code>View</code>。</p>
<p>子<code>View</code>可以通过重写<code>dispatchTouchEvent()</code>方法来实现内部拦截法。在<code>dispatchTouchEvent()</code>方法中,我们可以根据事件的类型和位置来判断是否需要拦截事件。如果需要拦截事件,则调用<code>requestDisallowInterceptTouchEvent()</code>方法来告诉父<code>View</code>不要拦截事件。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">class MyView : View {</span><br><span class="line"></span><br><span class="line"> // 通过重写 dispatchTouchEvent 方法实现内部拦截</span><br><span class="line"> override fun dispatchTouchEvent(ev: MotionEvent): Boolean {</span><br><span class="line"> when (ev.action) {</span><br><span class="line"> MotionEvent.ACTION_DOWN -> {</span><br><span class="line"> // 按下时,禁止父View拦截事件</span><br><span class="line"> parent.requestDisallowInterceptTouchEvent(true)</span><br><span class="line"> }</span><br><span class="line"> MotionEvent.ACTION_MOVE -> {</span><br><span class="line"> // 根据业务逻辑判断是否拦截事件</span><br><span class="line"> if (shouldInterceptTouchEvent(ev)) {</span><br><span class="line"> return true</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> MotionEvent.ACTION_UP -> {</span><br><span class="line"> // 手指抬起时,允许父View拦截事件</span><br><span class="line"> parent.requestDisallowInterceptTouchEvent(false)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return super.dispatchTouchEvent(ev)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>优点:</strong> 不会导致父<code>ViewGroup</code>无法响应事件,适用于父<code>ViewGroup</code>和子<code>View</code>都需要滑动的情况。</p>
<p><strong>缺点:</strong> 需要重写子<code>View</code>的<code>dispatchTouchEvent()</code>方法,可能会导致代码复杂。</p>
<h2 id="注意事项和优化技巧"><a href="#注意事项和优化技巧" class="headerlink" title="注意事项和优化技巧"></a>注意事项和优化技巧</h2><ul>
<li>在判断是否需要拦截事件时,需要考虑事件的方向、滑动距离等因素。</li>
<li>如果父<code>ViewGroup</code>和子<code>View</code>都需要滑动,则可以使用事件分发机制来解决滑动冲突。</li>
<li>避免过多的嵌套, 尽量减少布局的嵌套层次,以降低滑动冲突的概率。</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><code>Android</code>滑动冲突的解决方式主要有外部拦截法和内部拦截法两种。希望本文能帮助读者解决滑动冲突问题,提高<code>Android</code>开发水平。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><code>Android<
Android大图监测的这三种实现方式,你最喜欢哪种?
https://www.rousetime.com/2024/01/11/Android大图监测的这三种实现方式,你最喜欢哪种?/
2024-01-11T02:43:08.000Z
2024-01-11T02:43:32.498Z
<p>在<code>Android</code>应用中,大图的加载和显示可能导致内存占用过高,进而引发<code>OOM</code>(Out Of Memory)异常,影响应用的稳定性和用户体验。为了更好地管理大图资源,我们需要建立起一套可靠的大图监测系统。</p>
<h2 id="原理解析"><a href="#原理解析" class="headerlink" title="原理解析"></a>原理解析</h2><ol>
<li><strong>内存占用计算</strong></li>
</ol>
<p>首先,我们需要了解如何计算一张图片在内存中的占用大小。Android中,图片占用的内存主要由其宽、高和每个像素的位数决定。我们可以使用以下公式计算:</p>
<p>[ 内存占用大小 = 宽 \times 高 \times 像素位数 / 8 ]</p>
<ol start="2">
<li><strong>大图判定标准</strong></li>
</ol>
<p>一般情况下,大图的定义是指超过一定阈值的图片。这个阈值可以根据应用的实际需求来设定,通常建议根据设备的内存情况和应用场景动态调整。</p>
<ol start="3">
<li><strong>监测策略</strong></li>
</ol>
<p>大图监测一般采用两种策略:<strong>主动监测</strong>和<strong>被动监测</strong>。主动监测通过周期性地扫描内存中的图片资源,识别大图,进行处理。而被动监测则是在图片加载过程中实时判断是否为大图。</p>
<h2 id="主动监测"><a href="#主动监测" class="headerlink" title="主动监测"></a>主动监测</h2><p>主动监测只要获取到内存中的图片资源,通过扫描判断是否超过设置的阈值即可。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LargeImageScanner</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">scanLargeImages</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 遍历内存中的图片资源</span></span><br><span class="line"> <span class="keyword">for</span> (image <span class="keyword">in</span> MemoryManager.getAllImages()) {</span><br><span class="line"> <span class="keyword">val</span> imageSize = calculateImageSize(image)</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 判断是否为大图</span></span><br><span class="line"> <span class="keyword">if</span> (imageSize > LARGE_IMAGE_THRESHOLD) {</span><br><span class="line"> <span class="comment">// 进行处理,如压缩、裁剪或异步加载</span></span><br><span class="line"> handleLargeImage(image)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">calculateImageSize</span><span class="params">(image: <span class="type">Bitmap</span>)</span></span>: <span class="built_in">Int</span> {</span><br><span class="line"> <span class="comment">// 计算图片占用的内存大小</span></span><br><span class="line"> <span class="keyword">return</span> image.width * image.height * (image.config.bitsPerPixel / <span class="number">8</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">handleLargeImage</span><span class="params">(image: <span class="type">Bitmap</span>)</span></span> {</span><br><span class="line"> <span class="comment">// 实现大图的处理逻辑,例如压缩、裁剪或异步加载</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="被动监测"><a href="#被动监测" class="headerlink" title="被动监测"></a>被动监测</h2><p>被动监测的目的是,让图在加载的过程中,自动获取到加载图片的大小。所以切入的时机就非常重要。</p>
<h3 id="在第三方图片加载库回调中进行大图监测"><a href="#在第三方图片加载库回调中进行大图监测" class="headerlink" title="在第三方图片加载库回调中进行大图监测"></a>在第三方图片加载库回调中进行大图监测</h3><p>如果你使用的是第三方图片加载库<code>Glide</code>,最简单的直接的是在图片加载的成功的时机进行监测。<br><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GlideImageLoader</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">loadWithLargeImageCheck</span><span class="params">(context: <span class="type">Context</span>, url: <span class="type">String</span>, target: <span class="type">ImageView</span>)</span></span> {</span><br><span class="line"> Glide.with(context)</span><br><span class="line"> .asBitmap()</span><br><span class="line"> .load(url)</span><br><span class="line"> .listener(<span class="keyword">object</span> : RequestListener<Bitmap> {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onLoadFailed</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params"> e: <span class="type">GlideException</span>?,</span></span></span><br><span class="line"><span class="function"><span class="params"> model: <span class="type">Any</span>?,</span></span></span><br><span class="line"><span class="function"><span class="params"> target: <span class="type">Target</span><<span class="type">Bitmap</span>>?,</span></span></span><br><span class="line"><span class="function"><span class="params"> isFirstResource: <span class="type">Boolean</span></span></span></span><br><span class="line"><span class="function"><span class="params"> )</span></span>: <span class="built_in">Boolean</span> {</span><br><span class="line"> <span class="comment">// 图片加载失败处理</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onResourceReady</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params"> resource: <span class="type">Bitmap</span>?,</span></span></span><br><span class="line"><span class="function"><span class="params"> model: <span class="type">Any</span>?,</span></span></span><br><span class="line"><span class="function"><span class="params"> target: <span class="type">Target</span><<span class="type">Bitmap</span>>?,</span></span></span><br><span class="line"><span class="function"><span class="params"> dataSource: <span class="type">DataSource</span>?,</span></span></span><br><span class="line"><span class="function"><span class="params"> isFirstResource: <span class="type">Boolean</span></span></span></span><br><span class="line"><span class="function"><span class="params"> )</span></span>: <span class="built_in">Boolean</span> {</span><br><span class="line"> <span class="comment">// 图片加载成功,检查是否为大图</span></span><br><span class="line"> resource?.let {</span><br><span class="line"> <span class="keyword">val</span> imageSize = calculateImageSize(it)</span><br><span class="line"> <span class="keyword">if</span> (imageSize > LARGE_IMAGE_THRESHOLD) {</span><br><span class="line"> <span class="comment">// 处理大图逻辑,如压缩、裁剪或异步加载</span></span><br><span class="line"> handleLargeImage(it)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> .into(target)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">calculateImageSize</span><span class="params">(image: <span class="type">Bitmap</span>)</span></span>: <span class="built_in">Int</span> {</span><br><span class="line"> <span class="comment">// 计算图片占用的内存大小</span></span><br><span class="line"> <span class="keyword">return</span> image.width * image.height * (image.config.bitsPerPixel / <span class="number">8</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">handleLargeImage</span><span class="params">(image: <span class="type">Bitmap</span>)</span></span> {</span><br><span class="line"> <span class="comment">// 实现大图的处理逻辑,例如压缩、裁剪或异步加载</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>
<p>但上面这种方式存在几个弊端</p>
<ol>
<li>适用性低,强制要求所以图片加载都要调用<code>loadWithLargeImageCheck</code>方法,如果是一个现有的大项目,将无法改造。</li>
<li>强依赖于第三方加载库<code>Glide</code>,后续换库也不兼容</li>
</ol>
<p>所以为了解决上面的这几个问题,我们要想的是,能否不依赖于第三方图片加载库呢?</p>
<p>于是就有了下面这种方式</p>
<h3 id="在网络加载图片时进行大图监测"><a href="#在网络加载图片时进行大图监测" class="headerlink" title="在网络加载图片时进行大图监测"></a>在网络加载图片时进行大图监测</h3><p>现在使用网络请求基本都是使用<code>Okhttp</code>,在这种情况下,你可以考虑使用拦截器(Interceptor)来实现通用的大图监测逻辑。拦截器是<code>OkHttp</code> 中的一种强大的机制,可以在请求发起和响应返回的过程中进行拦截、修改和监测。</p>
<p>以下是一个使用<code>OkHttp</code>拦截器进行大图监测的示例:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> okhttp3.Interceptor</span><br><span class="line"><span class="keyword">import</span> okhttp3.OkHttpClient</span><br><span class="line"><span class="keyword">import</span> okhttp3.Response</span><br><span class="line"><span class="keyword">import</span> java.io.IOException</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LargeImageInterceptor</span> : <span class="type">Interceptor {</span></span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@Throws(IOException::class)</span></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">intercept</span><span class="params">(chain: <span class="type">Interceptor</span>.<span class="type">Chain</span>)</span></span>: Response {</span><br><span class="line"> <span class="keyword">val</span> request = chain.request()</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 发起请求前的处理,可以在这里记录请求时间等信息</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> response = chain.proceed(request)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 请求返回后的处理</span></span><br><span class="line"> <span class="keyword">if</span> (response.isSuccessful) {</span><br><span class="line"> <span class="keyword">val</span> contentType = response.body()?.contentType()?.toString()</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 检查是否为图片资源</span></span><br><span class="line"> <span class="keyword">if</span> (contentType?.startsWith(<span class="string">"image/"</span>) == <span class="literal">true</span>) {</span><br><span class="line"> <span class="comment">// 获取图片大小并进行大图监测</span></span><br><span class="line"> <span class="keyword">val</span> imageSize = calculateImageSize(response.body()?.byteStream())</span><br><span class="line"> <span class="keyword">if</span> (imageSize > LARGE_IMAGE_THRESHOLD) {</span><br><span class="line"> <span class="comment">// 处理大图逻辑,如压缩、裁剪或异步加载</span></span><br><span class="line"> handleLargeImage()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> response</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">calculateImageSize</span><span class="params">(inputStream: <span class="type">InputStream</span>?)</span></span>: <span class="built_in">Int</span> {</span><br><span class="line"> <span class="comment">// 通过输入流计算图片占用的内存大小</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">handleLargeImage</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 实现大图的处理逻辑,例如压缩、裁剪或异步加载</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后,在创建<code>OkHttpClient</code>时,添加这个拦截器:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> okHttpClient = OkHttpClient.Builder()</span><br><span class="line"> .addInterceptor(LargeImageInterceptor())</span><br><span class="line"> .build()</span><br></pre></td></tr></table></figure>
<p>通过这种方式,你只需要在<code>OkHttp</code>中添加一次拦截器,即可在每个图片请求中进行通用的大图监测处理,而不用在每个请求的响应回调中添加监测代码。这样使得代码更加清晰、易于维护。</p>
<p>可能又有人会说,我网络加载库换了,那不是一样无法兼容吗?</p>
<p>确实,虽然概率比直接换第三方图片加载库还低,但既然有可能,就要尽可能的解决。</p>
<p>于是就是了下面的这种终极方法。</p>
<h2 id="使用ASM插桩进行大图监控"><a href="#使用ASM插桩进行大图监控" class="headerlink" title="使用ASM插桩进行大图监控"></a>使用ASM插桩进行大图监控</h2><p>这就升级到图片加载的本质了,任何图片加载最终都是要填充到<code>ImageView</code>上。而在这过程中自然避免不了使用<code>ImageView</code>的方法进行填充图片。</p>
<p>例如:<code>setImageDrawable</code>等等。</p>
<p>当然也可以直接<code>hook</code>整个<code>ImageView</code>,全局将其替换成<code>HookImageView</code>,再到其内部实现大图监测。<br>这两种都是通过<code>ASM</code>,只是对象不一样,但原理都基本一致。</p>
<p>以下是一个简单的示例,使用<code>ASM</code>对<code>Android</code>中的 <code>ImageView</code> 的 <code>setImageDrawable</code> 方法进行拦截:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.objectweb.asm.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ImageViewInterceptor</span> <span class="keyword">implements</span> <span class="title">ClassVisitor</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> ClassVisitor cv;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">ImageViewInterceptor</span><span class="params">(ClassVisitor cv)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.cv = cv;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> MethodVisitor <span class="title">visitMethod</span><span class="params">(<span class="keyword">int</span> access, String name, String desc, String signature, String[] exceptions)</span> </span>{</span><br><span class="line"> MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);</span><br><span class="line"> <span class="keyword">if</span> (name.equals(<span class="string">"setImageDrawable"</span>) && desc.equals(<span class="string">"(Landroid/graphics/drawable/Drawable;)V"</span>)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ImageViewMethodVisitor(mv);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> mv;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 其他方法省略,你可以根据需要实现其他 visitX 方法</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ImageViewMethodVisitor</span> <span class="keyword">extends</span> <span class="title">MethodVisitor</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">ImageViewMethodVisitor</span><span class="params">(MethodVisitor mv)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(Opcodes.ASM5, mv);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">visitCode</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.visitCode();</span><br><span class="line"> <span class="comment">// 在方法开头插入大图监测逻辑的字节码</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">visitInsn</span><span class="params">(<span class="keyword">int</span> opcode)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (opcode == Opcodes.RETURN) {</span><br><span class="line"> <span class="comment">// 在 RETURN 指令前插入大图监测逻辑的字节码</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">super</span>.visitInsn(opcode);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在某处,使用 ASM 进行字节码修改</span></span><br><span class="line">ClassReader cr = <span class="keyword">new</span> ClassReader(<span class="string">"android/widget/ImageView"</span>);</span><br><span class="line">ClassWriter cw = <span class="keyword">new</span> ClassWriter(ClassWriter.COMPUTE_MAXS);</span><br><span class="line">ImageViewInterceptor interceptor = <span class="keyword">new</span> ImageViewInterceptor(cw);</span><br><span class="line">cr.accept(interceptor, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">....</span><br></pre></td></tr></table></figure>
<p>这个示例中,<code>ImageViewInterceptor</code> 对 <code>ImageView</code> 的 <code>setImageDrawable</code> 方法进行了拦截,<code>ImageViewMethodVisitor</code> 中插入了大图监测逻辑的字节码。</p>
<p>需要注意的是。在实际应用中,需谨慎考虑因字节码操作而引起的潜在问题和兼容性风险。</p>
<h2 id="注意事项与优化技巧"><a href="#注意事项与优化技巧" class="headerlink" title="注意事项与优化技巧"></a>注意事项与优化技巧</h2><p>在实现大图监测时,我们需要注意以下事项:</p>
<ul>
<li><strong>灵活设置阈值:</strong> 根据不同设备和应用场景,动态调整大图的阈值,以保证监测的准确性和及时性。</li>
<li><strong>合理选择处理方式:</strong> 对于大图,可以选择合适的处理方式,如压缩、裁剪或异步加载,以降低内存占用。</li>
<li><strong>异步处理:</strong> 将大图的处理放在异步线程中,避免阻塞主线程,提高应用的响应性。</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过本文的学习,相信你已经对Android大图监测有了深入的理解,并可以在实际项目中应用这些知识,提升应用的性能和用户体验。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<p>在<code>Android</code>应用中,大图的加载和显示可€
ThreadLocal:你不知道的优化技巧,Android开发者都在用
https://www.rousetime.com/2024/01/09/ThreadLocal:你不知道的优化技巧,Android开发者都在用/
2024-01-09T02:33:11.000Z
2024-01-09T02:33:44.152Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在<code>Android</code>开发中,多线程是一个常见的话题。为了有效地处理多线程的并发问题,<code>Android</code>提供了一些工具和机制。其中,<code>ThreadLocal</code>是一个强大的工具,它可以使得每个线程都拥有自己独立的变量副本,从而避免了线程安全问题。</p>
<p>本文将深入探讨Android中的<code>ThreadLocal</code>原理及其使用技巧, 帮助你更好的理解和使用<code>ThreadLocal</code>。</p>
<h2 id="ThreadLocal的原理"><a href="#ThreadLocal的原理" class="headerlink" title="ThreadLocal的原理"></a>ThreadLocal的原理</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public class Thread implements Runnable {</span><br><span class="line"></span><br><span class="line"> /* ThreadLocal values pertaining to this thread. This map is maintained</span><br><span class="line"> * by the ThreadLocal class. */</span><br><span class="line"> ThreadLocal.ThreadLocalMap threadLocals = null;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>ThreadLocal</code>的原理是基于每个线程都有一个独立的<code>ThreadLocalMap</code>对象。<code>ThreadLocalMap</code>对象是一个<code>Map</code>,它的键是<code>ThreadLocal</code>对象,值是<code>ThreadLocal</code>对象保存的值。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">public void set(T value) {</span><br><span class="line"> Thread t = Thread.currentThread();</span><br><span class="line"> ThreadLocalMap map = getMap(t);</span><br><span class="line"> if (map != null) {</span><br><span class="line"> map.set(this, value);</span><br><span class="line"> } else {</span><br><span class="line"> createMap(t, value);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public T get() {</span><br><span class="line"> Thread t = Thread.currentThread();</span><br><span class="line"> ThreadLocalMap map = getMap(t);</span><br><span class="line"> if (map != null) {</span><br><span class="line"> ThreadLocalMap.Entry e = map.getEntry(this);</span><br><span class="line"> if (e != null) {</span><br><span class="line"> @SuppressWarnings("unchecked")</span><br><span class="line"> T result = (T)e.value;</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return setInitialValue();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>当我们调用<code>ThreadLocal</code>的<code>set()</code>方法时,会将值存储到当前线程的<code>ThreadLocalMap</code>对象中。当我们调用<code>ThreadLocal</code>的<code>get()</code>方法时,会从当前线程的<code>ThreadLocalMap</code>对象中获取值。</p>
<h2 id="ThreadLocal的使用"><a href="#ThreadLocal的使用" class="headerlink" title="ThreadLocal的使用"></a>ThreadLocal的使用</h2><p>使用<code>ThreadLocal</code>非常简单,首先需要创建一个<code>ThreadLocal</code>对象,然后通过<code>set</code>和<code>get</code>方法来设置和获取线程的局部变量。以下是一个简单的例子:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> threadLocal = ThreadLocal<String>()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">setThreadName</span><span class="params">(name: <span class="type">String</span>)</span></span> {</span><br><span class="line"> threadLocal.<span class="keyword">set</span>(name)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">getThreadName</span><span class="params">()</span></span>: String {</span><br><span class="line"> <span class="keyword">return</span> threadLocal.<span class="keyword">get</span>() ?: <span class="string">"DefaultThreadName"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在<code>Android</code>开发中,<code>ThreadLocal</code>的使用场景非常多,比如:</p>
<ul>
<li>在<code>Activity</code>中存储<code>Fragment</code>的状态</li>
<li>在<code>Handler</code>中存储消息的上下文</li>
<li>在<code>RecyclerView</code>中存储滚动位置</li>
</ul>
<h2 id="实际应用场景"><a href="#实际应用场景" class="headerlink" title="实际应用场景"></a>实际应用场景</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">// 在 Activity 中存储 Fragment 的状态</span><br><span class="line">class MyActivity : AppCompatActivity() {</span><br><span class="line"></span><br><span class="line"> private val mFragmentState = ThreadLocal<FragmentState>()</span><br><span class="line"></span><br><span class="line"> override fun onCreate(savedInstanceState: Bundle?) {</span><br><span class="line"> super.onCreate(savedInstanceState)</span><br><span class="line"> setContentView(R.layout.activity_my)</span><br><span class="line"></span><br><span class="line"> // 获取 Fragment 的状态</span><br><span class="line"> val fragmentState = mFragmentState.get()</span><br><span class="line"> if (fragmentState == null) {</span><br><span class="line"> // 初始化 Fragment 的状态</span><br><span class="line"> fragmentState = FragmentState()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 设置 Fragment 的状态</span><br><span class="line"> mFragmentState.set(fragmentState)</span><br><span class="line"></span><br><span class="line"> // 创建 Fragment</span><br><span class="line"> val fragment = MyFragment()</span><br><span class="line"> fragment.arguments = fragmentState.toBundle()</span><br><span class="line"> supportFragmentManager.beginTransaction().add(R.id.container, fragment).commit()</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">class FragmentState {</span><br><span class="line"></span><br><span class="line"> var name: String? = null</span><br><span class="line"> var age: Int? = null</span><br><span class="line"></span><br><span class="line"> fun toBundle(): Bundle {</span><br><span class="line"> val bundle = Bundle()</span><br><span class="line"> bundle.putString("name", name)</span><br><span class="line"> bundle.putInt("age", age)</span><br><span class="line"> return bundle</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这段代码在<code>Activity</code>中使用<code>ThreadLocal</code>来存储<code>Fragment</code>的状态。当<code>Activity</code>第一次启动时,会初始化<code>Fragment</code>的状态。当<code>Activity</code>重新启动时,会从<code>ThreadLocal</code>中获取<code>Fragment</code>的状态,并将其传递给<code>Fragment</code>。</p>
<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul>
<li><strong>内存泄漏风险:</strong> </li>
</ul>
<p><code>ThreadLocal</code>变量的生命周期与线程的生命周期是一致的。这意味着,如果一个线程一直不结束,那么它所持有的<code>ThreadLocal</code>变量也不会被释放。这可能会导致内存泄漏。</p>
<p>为了避免内存泄漏,我们应该在不再需要<code>ThreadLocal</code>变量时,显式地将其移除。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">threadLocal.remove()</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>不适合全局变量:</strong> <code>ThreadLocal</code>适用于需要在线程间传递的局部变量,但不适合作为全局变量的替代品。</li>
</ul>
<h2 id="优化技巧"><a href="#优化技巧" class="headerlink" title="优化技巧"></a>优化技巧</h2><ul>
<li><strong>合理使用默认值:</strong> 在获取<code>ThreadLocal</code>值时,可以通过提供默认值来避免返回<code>null</code>,确保代码的健壮性。</li>
</ul>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">getThreadName</span><span class="params">()</span></span>: String {</span><br><span class="line"> <span class="keyword">return</span> threadLocal.<span class="keyword">get</span>() ?: <span class="string">"DefaultThreadName"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>懒加载初始化:</strong> 避免在声明<code>ThreadLocal</code>时就初始化,可以使用<code>initialValue</code>方法进行懒加载,提高性能。</li>
</ul>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> threadLocal = <span class="keyword">object</span> : ThreadLocal<String>() {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">initialValue</span><span class="params">()</span></span>: String {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"DefaultValue"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>尽量避免在<code>ThreadLocal</code>中保存大对象</strong></li>
</ul>
<h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>在本文中,我们介绍了<code>ThreadLocal</code>的原理和使用技巧,希望这些知识能够帮助你更好地理解和使用它。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在<code>
你不知道的CoroutineContext:协程上下文大揭秘!
https://www.rousetime.com/2024/01/06/你不知道的CoroutineContext:协程上下文大揭秘!/
2024-01-06T09:46:37.000Z
2024-01-06T09:47:07.777Z
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>协程(Coroutine)是一种并发编程技术,它允许我们在一个线程中执行多个任务,而不需要创建多个线程。协程与线程的区别在于,线程是操作系统的概念,而协程是编程语言的概念。协程可以暂停和恢复执行,而线程只能被终止。</p>
<p>在 Android 中,协程由 Kotlin 语言支持。Kotlin 协程库提供了丰富的 API,可以帮助我们轻松地编写并发代码。其中,<code>CoroutineContext</code>是一个非常重要的概念,它定义了协程的执行环境。</p>
<p>在本篇文章中,我们将从以下几个方面来介绍<code>CoroutineContext</code>的工作原理:</p>
<ul>
<li><code>CoroutineContext</code>的概念</li>
<li><code>CoroutineContext</code>的组成</li>
<li><code>CoroutineContext</code>的继承</li>
<li><code>CoroutineContext</code>的注意事项</li>
</ul>
<h2 id="CoroutineContext的概念"><a href="#CoroutineContext的概念" class="headerlink" title="CoroutineContext的概念"></a>CoroutineContext的概念</h2><p>CoroutineContext是一个容器,它包含了协程的所有上下文信息。这些上下文信息包括:</p>
<ul>
<li><strong>协程的状态</strong>:协程的状态表示协程的生命周期。协程可以处于 <strong>Active</strong>、<strong>Completed</strong>、<strong>Canceled</strong> 等状态。</li>
<li><strong>协程的调度策略</strong>:协程的调度策略决定了协程在哪里执行。协程可以执行在主线程、后台线程、或其他协程池中。</li>
<li><strong>协程的标签</strong>:协程的标签用于标识协程。</li>
<li><strong>协程的拦截器</strong>:协程的拦截器用于拦截协程的执行流程。</li>
<li><strong>协程的异常捕获</strong>:用于处理协程内部发生的未捕获异常。</li>
</ul>
<p>CoroutineContext可以通过 <code>coroutineContext</code>获取。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking {</span><br><span class="line"> <span class="keyword">val</span> context = coroutineContext</span><br><span class="line"></span><br><span class="line"> println(context)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>输出:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[CoroutineId(2), "coroutine#2":BlockingCoroutine{Active}@769c9116, BlockingEventLoop@6aceb1a5]</span><br></pre></td></tr></table></figure>
<h2 id="CoroutineContext的组成"><a href="#CoroutineContext的组成" class="headerlink" title="CoroutineContext的组成"></a>CoroutineContext的组成</h2><p>CoroutineContext由多个组件组成,这些组件可以通过 <code>context.get<T>()</code> 函数来获取。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">public operator fun <E : Element> get(key: Key<E>): E?</span><br></pre></td></tr></table></figure>
<p>由于重新定义了<code>get</code>操作符,所以可以直接使用<code>context[key]</code>来获取对应的上下文组件元素。</p>
<ul>
<li><strong>Dispatcher</strong>:协程的调度策略。</li>
</ul>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking {</span><br><span class="line"> <span class="keyword">val</span> context = coroutineContext + Dispatchers.Main</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> dispatcher = context[CoroutineDispatcher]</span><br><span class="line"></span><br><span class="line"> println(dispatcher)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>输出:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Dispatchers.Main[missing]</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>Job</strong>:协程的状态。Job 表示协程的生命周期。</li>
</ul>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking {</span><br><span class="line"> <span class="keyword">val</span> context = coroutineContext + SupervisorJob()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> job = context[Job]</span><br><span class="line"></span><br><span class="line"> println(job)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>输出:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SupervisorJobImpl{Active}@50675690</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>获取协程的状态</strong>:协程的状态表示协程的生命周期。协程可以处于 <strong>Active</strong>、<strong>Completed</strong>、<strong>Canceled</strong> 等状态。</li>
</ul>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking {</span><br><span class="line"> <span class="keyword">val</span> context = coroutineContext + SupervisorJob()</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取协程的状态</span></span><br><span class="line"> <span class="keyword">val</span> job = context[Job]</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 判断协程是否处于 Active 状态</span></span><br><span class="line"> <span class="keyword">if</span> (job?.isActive == <span class="literal">true</span>) {</span><br><span class="line"> println(<span class="string">"协程处于 Active 状态"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>输出:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">协程处于 Active 状态</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>CoroutineName</strong>:协程的标签。CoroutineName 用于标识协程。</li>
</ul>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking {</span><br><span class="line"> <span class="keyword">val</span> context = coroutineContext + CoroutineName(<span class="string">"张三"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> coroutineName = context[CoroutineName]</span><br><span class="line"></span><br><span class="line"> println(coroutineName)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>输出:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CoroutineName(张三)</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>添加拦截器</strong>:拦截器可以拦截协程的执行流程,例如:</li>
</ul>
<ol>
<li>在协程开始执行之前进行一些初始化操作。</li>
<li>在协程执行期间进行一些监控操作。</li>
<li>在协程执行完成之后进行一些清理操作。</li>
</ol>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyContinuationInterceptor</span> : <span class="type">ContinuationInterceptor {</span></span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">interceptContinuation</span><span class="params">(continuation: <span class="type">Continuation</span><<span class="type">Unit</span>>)</span></span>: Continuation<<span class="built_in">Unit</span>> {</span><br><span class="line"> <span class="comment">// 在协程开始执行之前进行一些初始化操作</span></span><br><span class="line"> println(<span class="string">"MyContinuationInterceptor: 协程开始执行之前"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回原始的 continuation</span></span><br><span class="line"> <span class="keyword">return</span> continuation</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">key</span><span class="params">()</span></span>: CoroutineContext.Key<ContinuationInterceptor> = ContinuationInterceptor.Key</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 启动一个协程</span></span><br><span class="line"> launch(Dispatchers.IO + MyContinuationInterceptor()) {</span><br><span class="line"> <span class="comment">// 执行一些耗时操作</span></span><br><span class="line"> delay(<span class="number">1000</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个示例中,协程在开始执行之前会打印一条消息:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MyContinuationInterceptor: 协程开始执行之前</span><br></pre></td></tr></table></figure>
<ul>
<li><strong>CoroutineExceptionHandler</strong>:处理协程内部发生的未捕获异常</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">import kotlinx.coroutines.*</span><br><span class="line"></span><br><span class="line">fun main() {</span><br><span class="line"> // 创建CoroutineExceptionHandler</span><br><span class="line"> val exceptionHandler = CoroutineExceptionHandler { _, exception -></span><br><span class="line"> println("Caught an exception: $exception")</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 启动一个协程,并指定CoroutineExceptionHandler</span><br><span class="line"> runBlocking {</span><br><span class="line"> val context = coroutineContext + exceptionHandler</span><br><span class="line"> val job = GlobalScope.launch(context) {</span><br><span class="line"> // 模拟一个可能抛出异常的操作</span><br><span class="line"> println("Coroutine is doing some work")</span><br><span class="line"> delay(1000)</span><br><span class="line"> throw CustomException("Something went wrong!")</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 等待协程执行结束</span><br><span class="line"> job.join()</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 自定义异常类</span><br><span class="line">class CustomException(message: String) : Exception(message)</span><br></pre></td></tr></table></figure>
<p>在这个示例中,为原有的<code>coroutineContext</code>增加了捕获异常的<code>exceptionHandler</code>,以至于协程内容抛出异常时,会被<code>CoroutineExceptionHandler</code>所捕获。</p>
<p>使用<code>CoroutineExceptionHandler</code>的好处在于,你可以集中处理协程内部的所有异常,而不必在每个协程体中都使用<code>try-catch</code>块来捕获异常。</p>
<ul>
<li><strong>EmptyCoroutineContext</strong>:一个空的 CoroutineContext。</li>
</ul>
<h2 id="CoroutineContext的继承"><a href="#CoroutineContext的继承" class="headerlink" title="CoroutineContext的继承"></a>CoroutineContext的继承</h2><p><code>CoroutineContext</code>支持继承。子<code>CoroutineContext</code>可以继承父<code>CoroutineContext</code>的所有组件。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking {</span><br><span class="line"> <span class="keyword">val</span> parentContext = coroutineContext + Dispatchers.Main + SupervisorJob() + CoroutineName(<span class="string">"张三"</span>)</span><br><span class="line"> <span class="keyword">val</span> childContext = parentContext + Dispatchers.IO</span><br><span class="line"></span><br><span class="line"> println(childContext)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>输出:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[CoroutineId(2), SupervisorJobImpl{Active}@1b40d5f0, CoroutineName(张三), Dispatchers.IO]</span><br></pre></td></tr></table></figure>
<p>在这个例子中,<code>parentContext</code> 包含 <code>Dispatchers.Main</code>、<code>Job()</code>和<code>CoroutineName("张三")</code>,<code>childContext</code> 继承了 <code>parentContext</code> 的所有组件,并添加了 <code>Dispatchers.IO</code>,由于与<code>Dispatchers.Main</code>同为调度器,所以最终保留的是最后的<code>Dispatchers.IO</code>。</p>
<h2 id="CoroutineContext的注意事项"><a href="#CoroutineContext的注意事项" class="headerlink" title="CoroutineContext的注意事项"></a>CoroutineContext的注意事项</h2><p>在使用<code>CoroutineContext</code>时,需要注意以下几点:</p>
<ul>
<li><strong>合理选择调度器:</strong>根据任务的性质选择合适的调度器,避免在IO密集型任务中使用CPU密集型的调度器,以及反之。</li>
<li><strong>细致管理CoroutineContext:</strong>合理管理CoroutineContext的元素,不要过度添加不必要的元素,以免引起不必要的性能开销。</li>
<li><strong>异常处理:</strong>及时处理协程中的异常,可以通过在CoroutineContext中添加CoroutineExceptionHandler元素来实现。</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总而言之,<code>CoroutineContext</code>是协程的一个重要概念。充分理解<code>CoroutineContext</code>的工作原理和使用方法,这样才能更好地利用<code>CoroutineContext</code>来控制协程的执行。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>协ޠ
SharedFlow vs StateFlow,一篇看懂选择和使用技巧
https://www.rousetime.com/2023/12/31/SharedFlow-vs-StateFlow,一篇看懂选择和使用技巧/
2023-12-31T05:56:15.000Z
2023-12-31T05:56:42.879Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Android应用开发中,数据流是一个至关重要的概念。而在Jetpack库中,<code>SharedFlow</code> 和 <code>StateFlow</code> 是两个处理数据流的利器,它们基于协程,提供了一种响应式的编程方式。本文将深入探讨这两个类的原理,以及在实际开发中的使用技巧。</p>
<h2 id="原理分析"><a href="#原理分析" class="headerlink" title="原理分析"></a>原理分析</h2><p><code>SharedFlow</code> 和 <code>StateFlow</code> 基于协程构建,它们利用协程的轻量级特性,在异步操作中更加高效。</p>
<p><code>SharedFlow</code> 使用了一种基于事件溯源的机制,当有新的事件产生时,将事件添加到共享的事件序列中,然后通知所有订阅者。而 <code>StateFlow</code> 则维护了一个可变的状态,并在状态发生变化时通知所有观察者。</p>
<h2 id="热流与冷流"><a href="#热流与冷流" class="headerlink" title="热流与冷流"></a>热流与冷流</h2><p>热流和冷流是关于数据流的两个基本概念,它们描述了数据流何时开始以及如何传递事件的方式。</p>
<ol>
<li>热流是一种主动的数据流。它在创建时就开始发射事件,无论是否有观察者订阅。即使没有观察者,热流也会持续产生事件。当观察者订阅时,它只是加入了已经运行的数据流,开始接收当前已经产生的事件。</li>
<li>冷流是一种被动的数据流。它在有观察者订阅时才开始发射事件。每个观察者都会获得相同的事件序列,而不会受到其他观察者的影响。</li>
</ol>
<p><code>SharedFlow</code> 和 <code>StateFlow</code>都是热流。即没有观察者,数据会持续更新,与<code>LiveData</code>类似。<br>其中<code>MutableSharedFlow</code>与<code>MutableStateFlow</code>是它们的可变类型。</p>
<h3 id="热流的示例"><a href="#热流的示例" class="headerlink" title="热流的示例"></a>热流的示例</h3><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> kotlinx.coroutines.*</span><br><span class="line"><span class="keyword">import</span> kotlinx.coroutines.flow.*</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking {</span><br><span class="line"> <span class="keyword">val</span> hotFlow = MutableSharedFlow<<span class="built_in">Int</span>>()</span><br><span class="line"></span><br><span class="line"> launch {</span><br><span class="line"> repeat(<span class="number">5</span>) {</span><br><span class="line"> delay(<span class="number">1000</span>)</span><br><span class="line"> hotFlow.emit(it)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 观察者1</span></span><br><span class="line"> hotFlow.collect {</span><br><span class="line"> println(<span class="string">"Observer 1: <span class="variable">$it</span>"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 观察者2</span></span><br><span class="line"> delay(<span class="number">3000</span>) <span class="comment">// 观察者2延迟3秒后订阅</span></span><br><span class="line"> hotFlow.collect {</span><br><span class="line"> println(<span class="string">"Observer 2: <span class="variable">$it</span>"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> delay(<span class="number">5000</span>) <span class="comment">// 为了保持主线程运行</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个例子中,<code>hotFlow</code>是一个热流,它在启动后每隔一秒产生一个事件。观察者1从一开始就订阅,而观察者2在3秒后订阅,观察者2不会接收到观察者1在订阅之前已经接收的事件。</p>
<h3 id="冷流的示例"><a href="#冷流的示例" class="headerlink" title="冷流的示例"></a>冷流的示例</h3><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> kotlinx.coroutines.*</span><br><span class="line"><span class="keyword">import</span> kotlinx.coroutines.flow.*</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking {</span><br><span class="line"> <span class="keyword">val</span> coldFlow = flow {</span><br><span class="line"> emit(<span class="string">"Line 1"</span>)</span><br><span class="line"> delay(<span class="number">1000</span>)</span><br><span class="line"> emit(<span class="string">"Line 2"</span>)</span><br><span class="line"> delay(<span class="number">1000</span>)</span><br><span class="line"> emit(<span class="string">"Line 3"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 观察者1</span></span><br><span class="line"> coldFlow.collect {</span><br><span class="line"> println(<span class="string">"Observer 1: <span class="variable">$it</span>"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 观察者2</span></span><br><span class="line"> delay(<span class="number">2000</span>) <span class="comment">// 观察者2延迟2秒后订阅</span></span><br><span class="line"> coldFlow.collect {</span><br><span class="line"> println(<span class="string">"Observer 2: <span class="variable">$it</span>"</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> delay(<span class="number">5000</span>) <span class="comment">// 为了保持主线程运行</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个例子中,<code>coldFlow</code>是一个冷流,它在有观察者订阅时才开始发射事件。观察者1从一开始就订阅,而观察者2在2秒后订阅,但它能够接收到从开始运行的事件序列。</p>
<h2 id="MutableSharedFlow"><a href="#MutableSharedFlow" class="headerlink" title="MutableSharedFlow"></a>MutableSharedFlow</h2><p><code>MutableSharedFlow</code>是一种可变的、用于创建共享流的类。下面是<code>MutableSharedFlow</code>的一些主要构造函数参数及其默认值:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="type"><T></span> <span class="title">MutableSharedFlow</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params"> replay: <span class="type">Int</span> = <span class="number">0</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"> extraBufferCapacity: <span class="type">Int</span> = <span class="number">0</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"> onBufferOverflow: <span class="type">BufferOverflow</span> = BufferOverflow.SUSPEND</span></span></span><br><span class="line"><span class="function"><span class="params">)</span></span> : MutableSharedFlow<T> { <span class="comment">/*...*/</span> }</span><br></pre></td></tr></table></figure>
<ol>
<li><p><strong><code>replay</code>:</strong> 表示在订阅时从流中回放的元素数量。默认值为 <code>0</code>,表示不回放任何元素。如果设置为正整数 <code>n</code>,则在订阅时将向新订阅者回放最近的 <code>n</code> 个元素。</p>
</li>
<li><p><strong><code>extraBufferCapacity</code>:</strong> 表示额外的缓冲容量,用于存储订阅者尚未消耗的元素。默认值为 <code>0</code>,表示不使用额外的缓冲容量。设置为正整数 <code>m</code> 时,会在内部使用一个带有额外 <code>m</code> 容量的缓冲区。</p>
</li>
<li><p><strong><code>onBufferOverflow</code>:</strong> 表示在缓冲区溢出时的处理策略。默认值为 <code>BufferOverflow.SUSPEND</code>,表示当缓冲区溢出时暂停发射,等待订阅者消费。其他选项还包括 <code>BufferOverflow.DROP_OLDEST</code> 和 <code>BufferOverflow.DROP_LATEST</code>,它们分别表示在缓冲区溢出时丢弃最老的元素或最新的元素。</p>
</li>
</ol>
<p>使用示例:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> sharedFlow = MutableSharedFlow<<span class="built_in">Int</span>>(replay = <span class="number">10</span>, extraBufferCapacity = <span class="number">5</span>, onBufferOverflow = BufferOverflow.DROP_OLDEST)</span><br></pre></td></tr></table></figure>
<p>在这个示例中,创建了一个带有回放数量为10、额外缓冲容量为5、缓冲溢出处理策略为丢弃最老元素的<code>MutableSharedFlow</code>。这里的参数值是可根据具体需求进行调整的。</p>
<h2 id="MutableStateFlow"><a href="#MutableStateFlow" class="headerlink" title="MutableStateFlow"></a>MutableStateFlow</h2><p><code>MutableStateFlow</code> 的构造函数有一个默认参数,即初始状态值。以下是 <code>MutableStateFlow</code> 构造函数:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">fun</span> <span class="type"><T></span> <span class="title">MutableStateFlow</span><span class="params">(value: <span class="type">T</span>)</span></span>: MutableStateFlow<T> = StateFlowImpl(value ?: NULL)</span><br></pre></td></tr></table></figure>
<p>构造函数中的 <code>value</code> 参数表示 <code>MutableStateFlow</code> 的初始状态值。在创建 <code>MutableStateFlow</code> 时,需要提供这个初始状态值。</p>
<p>以下是一个示例,演示如何创建一个带有初始状态值的 <code>MutableStateFlow</code>:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> kotlinx.coroutines.flow.MutableStateFlow</span><br><span class="line"><span class="keyword">import</span> kotlinx.coroutines.flow.collect</span><br><span class="line"><span class="keyword">import</span> kotlinx.coroutines.runBlocking</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> = runBlocking {</span><br><span class="line"> <span class="keyword">val</span> initialState = <span class="string">"Initial State"</span></span><br><span class="line"> <span class="keyword">val</span> stateFlow = MutableStateFlow(initialState)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 观察者</span></span><br><span class="line"> <span class="keyword">val</span> job = launch {</span><br><span class="line"> stateFlow.collect { value -></span><br><span class="line"> println(<span class="string">"Received: <span class="variable">$value</span>"</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 修改状态</span></span><br><span class="line"> stateFlow.value = <span class="string">"New State"</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 等待观察者执行</span></span><br><span class="line"> job.join()</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个例子中,<code>initialState</code> 是 <code>MutableStateFlow</code> 的初始状态值,通过构造函数传递给 <code>MutableStateFlow</code>。然后,通过修改 <code>stateFlow.value</code>,可以更新 <code>MutableStateFlow</code> 的状态值。</p>
<h2 id="SharedFlow、StateFlow与LiveData的区别"><a href="#SharedFlow、StateFlow与LiveData的区别" class="headerlink" title="SharedFlow、StateFlow与LiveData的区别"></a>SharedFlow、StateFlow与LiveData的区别</h2><p><code>StateFlow</code>就是<code>SharedFlow</code>的一种特殊类型,特点有三:</p>
<ol>
<li>它的replay容量为 1;即可缓存最近的一次粘性事件,如果想避免粘性事件问题,使用<code>SharedFlow</code>,replay默认值0。</li>
<li>初始化时必须给它设置一个初始值</li>
<li>每次发送数据都会与上次缓存的数据作比较,只有不一样才会发送。 它还可直接访问它自己的value参数获取当前结果值,在使用上与<code>LiveData</code>相似。</li>
</ol>
<p>与<code>LiveData</code>的不同点</p>
<ol>
<li><code>StateFlow</code>必须在构建的时候传入初始值,<code>LiveData</code>不需要;</li>
<li><code>StateFlow</code>默认是防抖的,即相同值不更新,<code>LiveData</code>默认不防抖;</li>
<li><code>StateFlow</code>默认没有和生命周期绑定</li>
</ol>
<h2 id="简单示例"><a href="#简单示例" class="headerlink" title="简单示例"></a>简单示例</h2><p>为了帮助大家更好地理解,以下是使用 <code>SharedFlow</code> 和 <code>StateFlow</code> 的简单示例:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SharedFlow 示例</span></span><br><span class="line"><span class="keyword">val</span> sharedFlow = MutableSharedFlow<String>()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 订阅</span></span><br><span class="line">sharedFlow.collect { value -></span><br><span class="line"> println(<span class="string">"Received: <span class="variable">$value</span>"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 发送数据</span></span><br><span class="line">sharedFlow.emit(<span class="string">"Hello, SharedFlow!"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// StateFlow 示例</span></span><br><span class="line"><span class="keyword">val</span> stateFlow = MutableStateFlow(<span class="string">"Initial State"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 订阅</span></span><br><span class="line">stateFlow.collect { value -></span><br><span class="line"> println(<span class="string">"Current State: <span class="variable">$value</span>"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新状态</span></span><br><span class="line">stateFlow.value = <span class="string">"New State"</span></span><br></pre></td></tr></table></figure>
<h2 id="高级使用技巧"><a href="#高级使用技巧" class="headerlink" title="高级使用技巧"></a>高级使用技巧</h2><ol>
<li><strong>错误处理</strong></li>
</ol>
<p>在订阅流时,考虑添加错误处理机制,以确保在流中出现错误时能够得到适当的处理,防止错误传播导致应用崩溃。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sharedFlow.<span class="keyword">catch</span> { exception -></span><br><span class="line"> <span class="comment">// 处理错误</span></span><br><span class="line">}.collect { value -></span><br><span class="line"> <span class="comment">// 处理正常数据</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol start="2">
<li><strong>流的完成处理</strong></li>
</ol>
<p>使用<code>onCompletion</code>来处理流的完成事件,可以在流完成时执行一些清理工作。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">sharedFlow.onCompletion { cause -></span><br><span class="line"> <span class="keyword">if</span> (cause != <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// 处理流异常完成的情况</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 处理正常完成的情况</span></span><br><span class="line"> }</span><br><span class="line">}.collect { value -></span><br><span class="line"> <span class="comment">// 处理正常数据</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol start="3">
<li><strong>共享的冷流</strong></li>
</ol>
<p>使用<code>SharingStarted.WhileSubscribed</code>来创建共享的冷流,确保只有至少一个订阅者时,共享流才会激活。这在事件通知的场景中非常有用。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> sharedFlow = flowOf(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>).shareIn(viewModelScope, SharingStarted.WhileSubscribed())</span><br></pre></td></tr></table></figure>
<ol start="4">
<li><strong>背压策略</strong></li>
</ol>
<p>在使用<code>buffer</code>或<code>conflate</code>等背压策略时,注意根据实际场景选择合适的策略,以平衡性能和内存的消耗。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sharedFlow</span><br><span class="line"> .buffer(Channel.CONFLATED) <span class="comment">// 或者 buffer(size = n)</span></span><br><span class="line"> .collect { value -></span><br><span class="line"> <span class="comment">// 处理数据</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<ol start="5">
<li><strong>过滤重复的状态</strong></li>
</ol>
<p>使用<code>distinctUntilChanged</code>来过滤掉重复的状态,确保只在状态发生变化时通知订阅者。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">stateFlow</span><br><span class="line"> .distinctUntilChanged()</span><br><span class="line"> .collect { state -></span><br><span class="line"> <span class="comment">// 处理状态变化</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h2 id="实践运用"><a href="#实践运用" class="headerlink" title="实践运用"></a>实践运用</h2><h3 id="全局主题模式管理"><a href="#全局主题模式管理" class="headerlink" title="全局主题模式管理"></a>全局主题模式管理</h3><p>假设我们需要在应用中管理全局的主题模式,我们可以使用 StateFlow。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">object</span> ThemeManager {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> _themeStateFlow = MutableStateFlow(Theme.Light)</span><br><span class="line"> <span class="keyword">val</span> themeStateFlow: StateFlow<Theme> <span class="keyword">get</span>() = _themeStateFlow</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">setTheme</span><span class="params">(theme: <span class="type">Theme</span>)</span></span> {</span><br><span class="line"> viewModelScope.launch {</span><br><span class="line"> _themeStateFlow.value = theme</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上述示例中,<code>ThemeManager</code> 使用 <code>MutableStateFlow</code> 来创建一个管理主题模式的 StateFlow。当主题模式发生变化时,通过 <code>setTheme</code> 方法来更新 StateFlow,所有订阅者都会收到最新的主题模式。</p>
<p>在需要订阅主题模式的地方,可以这样使用:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ThemedFragment</span> : <span class="type">Fragment</span></span>() {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onViewCreated</span><span class="params">(view: <span class="type">View</span>, savedInstanceState: <span class="type">Bundle</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onViewCreated(view, savedInstanceState)</span><br><span class="line"> viewModelScope.launch {</span><br><span class="line"> ThemeManager.themeStateFlow.collect { theme -></span><br><span class="line"> <span class="comment">// 根据主题模式更新 UI</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="即时聊天应用"><a href="#即时聊天应用" class="headerlink" title="即时聊天应用"></a>即时聊天应用</h3><p>当涉及到共享数据状态的场景时,<code>SharedFlow</code> 通常是一个合理的选择。<br>假设我们要实现一个即时聊天应用,多个页面或组件需要获取最近的聊天消息。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">object</span> ChatManager {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> _chatMessagesFlow = MutableSharedFlow<ChatMessage>(replay = <span class="number">5</span>, extraBufferCapacity = <span class="number">1</span>, onBufferOverflow = BufferOverflow.DROP_LATEST)</span><br><span class="line"> <span class="keyword">val</span> chatMessagesFlow: SharedFlow<ChatMessage> <span class="keyword">get</span>() = _chatMessagesFlow</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">sendChatMessage</span><span class="params">(message: <span class="type">String</span>, sender: <span class="type">String</span>)</span></span> {</span><br><span class="line"> <span class="keyword">val</span> chatMessage = ChatMessage(message, sender, System.currentTimeMillis())</span><br><span class="line"> viewModelScope.launch {</span><br><span class="line"> _chatMessagesFlow.emit(chatMessage)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个示例中,<code>ChatManager</code> 使用 <code>MutableSharedFlow</code> 来创建一个实时通知聊天消息变化的 <code>SharedFlow</code>。当有新的聊天消息时,通过 <code>sendChatMessage</code> 方法更新 <code>SharedFlow</code>,所有订阅者都能获取到最近的数据序列。</p>
<p>在需要订阅聊天消息的地方,可以这样使用:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChatFragment</span> : <span class="type">Fragment</span></span>() {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onViewCreated</span><span class="params">(view: <span class="type">View</span>, savedInstanceState: <span class="type">Bundle</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onViewCreated(view, savedInstanceState)</span><br><span class="line"> viewModelScope.launch {</span><br><span class="line"> ChatManager.chatMessagesFlow.collect { chatMessage -></span><br><span class="line"> <span class="comment">// 处理收到的聊天消息,更新 UI</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过本文的介绍,相信读者已经对<code>SharedFlow</code>和<code>StateFlow</code>有了更深入的了解。在实际应用中,提高Android应用的开发效率。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Androi
协程与互斥锁: Kotlin Mutex的终极指南
https://www.rousetime.com/2023/12/30/协程与互斥锁-Kotlin-Mutex的终极指南/
2023-12-30T02:09:15.000Z
2023-12-30T02:10:11.963Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>今天我们将深入研究Kotlin中的Mutex(互斥锁)原理以及在实际开发中的使用技巧。Mutex是多线程编程中的关键工具,它可以有效地解决多线程访问共享资源时可能发生的竞态条件问题。</p>
<h2 id="Mutex的基本原理"><a href="#Mutex的基本原理" class="headerlink" title="Mutex的基本原理"></a>Mutex的基本原理</h2><p>Mutex是互斥锁的缩写,它是一种同步工具,用于保护共享资源,确保在任何时刻只有一个线程可以访问该资源。在Kotlin中,Mutex是通过<code>kotlinx.coroutines.sync</code>包实现的。</p>
<h3 id="Mutex的实现原理"><a href="#Mutex的实现原理" class="headerlink" title="Mutex的实现原理"></a>Mutex的实现原理</h3><p>Mutex的实现基于挂起函数和协程的概念。当一个协程请求进入受Mutex保护的临界区时,如果Mutex已经被占用,请求的协程将被挂起,直到Mutex可用。这样可以避免多个协程同时访问共享资源,确保线程安全。</p>
<h3 id="状态变量"><a href="#状态变量" class="headerlink" title="状态变量"></a>状态变量</h3><p><code>Mutex</code> 类的状态变量包括以下两个:</p>
<ul>
<li><code>owner</code>: 表示锁的拥有者。</li>
<li><code>availablePermits</code>: 表示可用的许可证数量。</li>
</ul>
<h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p><code>Mutex</code> 类的初始化会将 <code>owner</code> 变量初始化为 <code>NO_OWNER</code>,表示锁没有被任何线程获取。</p>
<h3 id="获取锁"><a href="#获取锁" class="headerlink" title="获取锁"></a>获取锁</h3><p><code>Mutex</code> 类的 <code>lock()</code> 方法会尝试获取锁。如果锁没有被其他线程获取,则该方法会成功获取锁。如果锁已经被其他线程获取,则该方法会将线程放入到等待队列中,并阻塞线程。</p>
<p><code>lock()</code> 方法的实现如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">lock</span><span class="params">(owner: <span class="type">Any</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">if</span> (tryLock(owner)) <span class="keyword">return</span></span><br><span class="line"> lockSuspend(owner)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">lockSuspend</span><span class="params">(owner: <span class="type">Any</span>?)</span></span> = suspendCancellableCoroutineReusable<<span class="built_in">Unit</span>> { cont -></span><br><span class="line"> <span class="keyword">val</span> contWithOwner = CancellableContinuationWithOwner(cont, owner)</span><br><span class="line"> acquire(contWithOwner)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>lock()</code> 方法首先会调用 <code>tryLock()</code> 方法尝试获取锁。如果 <code>tryLock()</code> 方法成功,则表示锁没有被其他线程获取,<code>lock()</code> 方法会直接返回。</p>
<p>如果 <code>tryLock()</code> 方法失败,则表示锁已经被其他线程获取。在这种情况下,<code>lock()</code> 方法会调用 <code>lockSuspend()</code> 方法来获取锁。</p>
<p><code>lockSuspend()</code> 方法会创建一个 <code>CancellableContinuationWithOwner</code> 对象,并将其传递给 <code>acquire()</code> 方法。<code>acquire()</code> 方法会尝试获取锁。如果成功,则会将 <code>CancellableContinuationWithOwner</code> 对象的 <code>owner</code> 变量设置为 <code>owner</code> 参数。如果失败,则会将 <code>CancellableContinuationWithOwner</code> 对象放入到等待队列中。</p>
<h3 id="释放锁"><a href="#释放锁" class="headerlink" title="释放锁"></a>释放锁</h3><p><code>Mutex</code> 类的 <code>unlock()</code> 方法会释放锁。如果锁的拥有者是当前线程,则该方法会成功释放锁。如果锁的拥有者不是当前线程,则该方法会抛出异常。</p>
<p><code>unlock()</code> 方法的实现如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">unlock</span><span class="params">(owner: <span class="type">Any</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> <span class="comment">// Is this mutex locked?</span></span><br><span class="line"> check(isLocked) { <span class="string">"This mutex is not locked"</span> }</span><br><span class="line"> <span class="comment">// Read the owner, waiting until it is set in a spin-loop if required.</span></span><br><span class="line"> <span class="keyword">val</span> curOwner = <span class="keyword">this</span>.owner.value</span><br><span class="line"> <span class="keyword">if</span> (curOwner === NO_OWNER) <span class="keyword">continue</span> <span class="comment">// <-- ATTENTION, BLOCKING PART HERE</span></span><br><span class="line"> <span class="comment">// Check the owner.</span></span><br><span class="line"> check(curOwner === owner || owner == <span class="literal">null</span>) { <span class="string">"This mutex is locked by <span class="variable">$curOwner</span>, but <span class="variable">$owner</span> is expected"</span> }</span><br><span class="line"> <span class="comment">// Try to clean the owner first. We need to use CAS here to synchronize with concurrent `unlock(..)`-s.</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.owner.compareAndSet(curOwner, NO_OWNER)) <span class="keyword">continue</span></span><br><span class="line"> <span class="comment">// Release the semaphore permit at the end.</span></span><br><span class="line"> release()</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>unlock()</code> 方法首先会检查锁是否已经被获取。如果锁没有被获取,则会抛出异常。</p>
<p>如果锁已经被获取,则会获取锁的拥有者。然后,会检查锁的拥有者是否是当前线程。如果是,则会将锁的拥有者设置为 <code>NO_OWNER</code>,并释放一个许可证。</p>
<h3 id="其他细节"><a href="#其他细节" class="headerlink" title="其他细节"></a>其他细节</h3><p><code>Mutex</code> 类还提供了以下一些其他细节:</p>
<ul>
<li><code>holdsLock()</code> 方法用于检查当前线程是否持有锁。</li>
<li><code>tryLock()</code> 方法用于尝试获取锁。如果成功,则会立即返回。如果失败,则会立即返回。</li>
<li><code>onLock</code> 属性用于指定协程在获取锁时要执行的操作。</li>
</ul>
<p><code>Mutex</code> 类的实现原理是基于信号量的。<code>Mutex</code> 类维护了一个 <code>availablePermits</code> 变量,表示可用的许可证数量。如果 <code>availablePermits</code> 变量的值为 0,则表示锁已经被其他线程获取</p>
<h2 id="Mutex的使用技巧"><a href="#Mutex的使用技巧" class="headerlink" title="Mutex的使用技巧"></a>Mutex的使用技巧</h2><p>下面我们将介绍在实际开发中使用Mutex的一些技巧,以及注意事项和优化技巧。</p>
<h3 id="如何使用-Mutex-处理特定问题"><a href="#如何使用-Mutex-处理特定问题" class="headerlink" title="如何使用 Mutex 处理特定问题"></a>如何使用 Mutex 处理特定问题</h3><p>考虑一个简单的 Android 项目场景,其中有多个协程同时进行网络请求并更新 UI。在这个场景中,我们希望确保网络请求和 UI 更新的顺序正确,避免竞态条件和 UI 不一致的问题。以下是一个使用 Mutex 的示例代码:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.os.Bundle</span><br><span class="line"><span class="keyword">import</span> androidx.appcompat.app.AppCompatActivity</span><br><span class="line"><span class="keyword">import</span> kotlinx.android.synthetic.main.activity_main.*</span><br><span class="line"><span class="keyword">import</span> kotlinx.coroutines.*</span><br><span class="line"><span class="keyword">import</span> kotlinx.coroutines.sync.Mutex</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MainActivity</span> : <span class="type">AppCompatActivity</span></span>() {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> mutex = Mutex()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">(savedInstanceState: <span class="type">Bundle</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState)</span><br><span class="line"> setContentView(R.layout.activity_main)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 启动多个协程进行网络请求和 UI 更新</span></span><br><span class="line"> repeat(<span class="number">5</span>) {</span><br><span class="line"> launch {</span><br><span class="line"> performNetworkRequestAndUIUpdate(it)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">performNetworkRequestAndUIUpdate</span><span class="params">(index: <span class="type">Int</span>)</span></span> {</span><br><span class="line"> <span class="comment">// 模拟网络请求</span></span><br><span class="line"> delay(<span class="number">1000</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 使用 Mutex 保护对 UI 更新的临界区域</span></span><br><span class="line"> mutex.withLock {</span><br><span class="line"> updateUI(<span class="string">"Task <span class="variable">$index</span> completed"</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">updateUI</span><span class="params">(message: <span class="type">String</span>)</span></span> {</span><br><span class="line"> <span class="comment">// 在主线程更新 UI</span></span><br><span class="line"> runOnUiThread {</span><br><span class="line"> textView.append(<span class="string">"<span class="variable">$message</span>\n"</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个示例中,<code>performNetworkRequestAndUIUpdate</code> 函数模拟了网络请求,然后使用 Mutex 保护了对 UI 更新的临界区域。这样,我们确保了网络请求和 UI 更新的顺序,避免了可能的竞态条件。</p>
<h3 id="Mutex-的作用和效果"><a href="#Mutex-的作用和效果" class="headerlink" title="Mutex 的作用和效果"></a>Mutex 的作用和效果</h3><p><strong>保护 UI 更新的临界区域</strong></p>
<p>通过在 <code>performNetworkRequestAndUIUpdate</code> 函数中使用 Mutex,我们确保了对 UI 更新的访问是线程安全的。在任一时刻,只有一个协程能够执行更新操作,避免了多个协程同时修改 UI 导致的问题。</p>
<p><strong>避免竞态条件和数据不一致性</strong></p>
<p>在 Android 中,由于涉及 UI 操作,确保在主线程上按正确的顺序更新 UI 是至关重要的。Mutex 的作用在于协调多个协程对 UI 的访问,避免竞态条件和数据不一致性。</p>
<p><strong>简化异步操作的同步控制</strong></p>
<p>Mutex 提供了一种简单而有效的方式来同步多个协程,特别是在涉及到异步操作(如网络请求)和 UI 更新时。通过在关键区域使用 Mutex,我们可以确保这些操作按照正确的顺序执行,提高了代码的可维护性和稳定性。</p>
<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol>
<li><strong>协程间互斥</strong>:Mutex主要用于协程之间的互斥,确保同一时间只有一个协程能够访问共享资源,避免竞态条件。</li>
<li><strong>避免死锁</strong>:在使用Mutex时,要注意避免死锁的情况,即协程获取Mutex后未释放就被挂起,导致其他协程无法继续执行。</li>
<li><strong>协程取消</strong>:在使用Mutex时,要注意协程的取消情况,确保在协程取消时能够正确释放Mutex,避免资源泄漏。</li>
<li><strong>性能开销</strong>:过多地使用Mutex可能会导致性能开销,需要谨慎设计代码,避免频繁的互斥操作。</li>
</ol>
<h2 id="优化技巧"><a href="#优化技巧" class="headerlink" title="优化技巧"></a>优化技巧</h2><ol>
<li><strong>精细化锁定</strong>:只在需要保护的临界区使用Mutex,避免过多地使用全局的Mutex。</li>
<li><strong>使用tryLock</strong>:在一些情况下,可以使用<code>tryLock</code>来尝试获取Mutex,避免协程被挂起,提高执行效率。</li>
</ol>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过本文的介绍,相信大家对Kotlin中Mutex的原理和使用有了更深入的了解。在实际开发中,灵活使用Mutex,结合协程的优势,可以更好地处理多线程场景,提高程序的健壮性。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>今֒
360度解析Android动画:哪个更引人注目?
https://www.rousetime.com/2023/12/28/360度解析Android动画:哪个更引人注目?/
2023-12-28T14:29:44.000Z
2023-12-28T14:30:14.986Z
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>动画是Android应用中不可或缺的一部分,它可以让应用更加生动、有趣,还可以提升用户体验。Android提供了多种动画实现方式,在本文中,我们将深入研究Android动画的方方面面。从基本的View动画和属性动画开始。我们将介绍高级动画技巧,包括使用自定义插值器、实现复杂效果,以及性能优化的最佳实践。</p>
<h2 id="Android动画基础"><a href="#Android动画基础" class="headerlink" title="Android动画基础"></a>Android动画基础</h2><p>Android动画系统提供了两种主要类型的动画:View动画和属性动画。这两种动画类型分别适用于不同的场景,但都为开发者提供了丰富的选项来创造各种令人印象深刻的用户界面效果。</p>
<h3 id="View动画"><a href="#View动画" class="headerlink" title="View动画"></a>View动画</h3><ol>
<li><p>补间动画</p>
<p>补间动画是指在动画开始和结束时只关心动画的起始状态和结束状态,而不关心中间的过程。在Android中,常见的补间动画包括平移、缩放、旋转和透明度变化。下面是一个简单的平移动画示例:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个平移动画,将View从当前位置移动到x=200的位置</span></span><br><span class="line"><span class="keyword">val</span> translateAnimation = TranslateAnimation(<span class="number">0f</span>, <span class="number">200f</span>, <span class="number">0f</span>, <span class="number">0f</span>)</span><br><span class="line">translateAnimation.duration = <span class="number">1000</span> <span class="comment">// 动画持续时间为1秒</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 将动画应用到View</span></span><br><span class="line">view.startAnimation(translateAnimation)</span><br></pre></td></tr></table></figure>
</li>
<li><p>逐帧动画</p>
<p>逐帧动画是通过一系列预先定义好的图片(帧)连续播放,形成动画效果。在Android中,通常使用XML资源文件定义逐帧动画。以下是一个简单的逐帧动画XML文件:</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- res/anim/frame_animation.xml --></span></span><br><span class="line"><span class="tag"><<span class="name">animation-list</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:oneshot</span>=<span class="string">"false"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">item</span> <span class="attr">android:drawable</span>=<span class="string">"@drawable/frame1"</span> <span class="attr">android:duration</span>=<span class="string">"100"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">item</span> <span class="attr">android:drawable</span>=<span class="string">"@drawable/frame2"</span> <span class="attr">android:duration</span>=<span class="string">"100"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">item</span> <span class="attr">android:drawable</span>=<span class="string">"@drawable/frame3"</span> <span class="attr">android:duration</span>=<span class="string">"100"</span> /></span></span><br><span class="line"> <span class="comment"><!-- 添加更多帧... --></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">animation-list</span>></span></span><br></pre></td></tr></table></figure>
<p>在代码中加载并应用逐帧动画:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 加载逐帧动画</span></span><br><span class="line"><span class="keyword">val</span> frameAnimation = AnimationUtils.loadAnimation(context, R.anim.frame_animation)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将动画应用到ImageView</span></span><br><span class="line">imageView.startAnimation(frameAnimation)</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h3 id="属性动画"><a href="#属性动画" class="headerlink" title="属性动画"></a>属性动画</h3><p>属性动画允许对视图的任何属性进行平滑的动画变换,包括自定义属性。它提供了更灵活的方式来实现复杂的动画效果,并通过<code>ObjectAnimator</code>类实现。</p>
<ol>
<li><p>值动画(ValueAnimator)</p>
<p>值动画允许我们在一定时间范围内逐渐改变某个值,可以用于实现更复杂的动画效果。以下是一个简单的值动画示例,实现一个颜色过渡:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个值动画,逐渐改变背景颜色从红色到蓝色</span></span><br><span class="line"><span class="keyword">val</span> colorAnimator = ValueAnimator.ofArgb(Color.RED, Color.BLUE)</span><br><span class="line">colorAnimator.duration = <span class="number">2000</span> <span class="comment">// 动画持续时间为2秒</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加值动画的监听器,实时更新背景颜色</span></span><br><span class="line">colorAnimator.addUpdateListener { animator -></span><br><span class="line"> <span class="keyword">val</span> color = animator.animatedValue <span class="keyword">as</span> <span class="built_in">Int</span></span><br><span class="line"> view.setBackgroundColor(color)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 启动值动画</span></span><br><span class="line">colorAnimator.start()</span><br></pre></td></tr></table></figure>
</li>
<li><p>对象动画(ObjectAnimator)</p>
<p>对象动画是值动画的扩展,它不仅可以改变基本数据类型的值,还可以改变对象的属性。以下是一个简单的对象动画示例,旋转一个ImageView:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个对象动画,逐渐旋转ImageView的角度</span></span><br><span class="line"><span class="keyword">val</span> rotateAnimator = ObjectAnimator.ofFloat(imageView, <span class="string">"rotation"</span>, <span class="number">0f</span>, <span class="number">360f</span>)</span><br><span class="line">rotateAnimator.duration = <span class="number">1000</span> <span class="comment">// 动画持续时间为1秒</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 启动对象动画</span></span><br><span class="line">rotateAnimator.start()</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="属性动画原理"><a href="#属性动画原理" class="headerlink" title="属性动画原理"></a>属性动画原理</h2><p>属性动画的实现原理是通过<strong>PropertyValuesHolder</strong>来描述属性值的变化。<strong>PropertyValuesHolder</strong>可以描述一个或多个属性值的变化,每个属性值的变化可以是一个线性变化、一个非线性变化或一个关键帧变化。</p>
<p><strong>PropertyValuesHolder</strong>的构造方法如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">PropertyValuesHolder</span><span class="params">(propertyName: <span class="type">String</span>, valueToInterpolate: <span class="type">Float</span>)</span></span>: PropertyValuesHolder {</span><br><span class="line"> <span class="keyword">return</span> PropertyValuesHolder(propertyName).apply {</span><br><span class="line"> setFloatValues(valueToInterpolate)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>其中,<code>propertyName</code>是属性名称,<code>valueToInterpolate</code>是属性值。</p>
<p><strong>PropertyValuesHolder</strong>可以通过<code>setFloatValues()</code>方法来设置多个属性值,也可以通过<code>setKeyframe()</code>方法来设置关键帧。</p>
<p><strong>PropertyValuesHolder</strong>的<code>setFloatValues()</code>方法的使用示例如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> alphaHolder = PropertyValuesHolder.ofFloat(<span class="string">"alpha"</span>, <span class="number">0.0f</span>, <span class="number">1.0f</span>)</span><br></pre></td></tr></table></figure>
<p>该代码创建了一个<code>alphaHolder</code>对象,它描述了控件的<code>alpha</code>属性从0.0f到1.0f的线性变化。</p>
<p><strong>PropertyValuesHolder</strong>的<code>setKeyframe()</code>方法的使用示例如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> alphaHolder = PropertyValuesHolder.ofFloat(<span class="string">"alpha"</span>)</span><br><span class="line"></span><br><span class="line">alphaHolder.setKeyframe(<span class="number">0.0f</span>, <span class="number">0.0f</span>)</span><br><span class="line">alphaHolder.setKeyframe(<span class="number">0.5f</span>, <span class="number">0.5f</span>)</span><br><span class="line">alphaHolder.setKeyframe(<span class="number">1.0f</span>, <span class="number">1.0f</span>)</span><br></pre></td></tr></table></figure>
<p>该代码创建了一个<code>alphaHolder</code>对象,它描述了控件的<code>alpha</code>属性从0.0f到1.0f的非线性变化。</p>
<p><strong>PropertyValuesHolder</strong>创建完成后,就可以将其添加到<strong>ObjectAnimator</strong>对象中来创建动画了。</p>
<p><strong>ObjectAnimator</strong>的构造方法如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">ObjectAnimator</span><span class="params">(target: <span class="type">Any</span>, propertyName: <span class="type">String</span>, propertyValuesHolder: <span class="type">PropertyValuesHolder</span>)</span></span>: ObjectAnimator {</span><br><span class="line"> <span class="keyword">return</span> ObjectAnimator().apply {</span><br><span class="line"> setTarget(target)</span><br><span class="line"> setProperty(propertyName)</span><br><span class="line"> setPropertyValuesHolder(propertyValuesHolder)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>其中,<code>target</code>是动画的目标对象,<code>propertyName</code>是属性名称,<code>propertyValuesHolder</code>是属性值的描述对象。</p>
<h2 id="插值器"><a href="#插值器" class="headerlink" title="插值器"></a>插值器</h2><p>插值器可以改变动画的执行速率,让动画效果更加生动、有趣。Android提供了多种插值器,可以满足不同的需求。</p>
<ol>
<li><p>系统内置插值器</p>
<p>以下是一些常用的系统内置插值器:</p>
<ul>
<li><code>AccelerateDecelerateInterpolator</code>: 先加速后减速的插值器。</li>
<li><code>AccelerateInterpolator</code>: 先加速后匀速的插值器。</li>
<li><code>DecelerateInterpolator</code>: 先减速后匀速的插值器。</li>
<li><code>LinearInterpolator</code>: 线性匀速的插值器。</li>
</ul>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用系统内置插值器的例子,实现先加速后减速的动画</span></span><br><span class="line"><span class="keyword">val</span> scaleAnimator = ObjectAnimator.ofFloat(view, <span class="string">"scaleX"</span>, <span class="number">0.5f</span>, <span class="number">2f</span>)</span><br><span class="line">scaleAnimator.duration = <span class="number">1000</span></span><br><span class="line">scaleAnimator.interpolator = AccelerateDecelerateInterpolator()</span><br><span class="line"></span><br><span class="line">scaleAnimator.start()</span><br></pre></td></tr></table></figure>
</li>
<li><p>自定义插值器</p>
<p>对于特定的动画效果,我们还可以创建自定义插值器。自定义插值器需要实现 <code>Interpolator</code> 接口。以下是一个简单的自定义插值器的示例,实现先减速后加速的效果:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DecelerateAccelerateInterpolator</span> : <span class="type">Interpolator {</span></span></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">getInterpolation</span><span class="params">(input: <span class="type">Float</span>)</span></span>: <span class="built_in">Float</span> {</span><br><span class="line"> <span class="comment">// 使用数学函数实现先减速后加速的插值器</span></span><br><span class="line"> <span class="keyword">return</span> Math.cos((input + <span class="number">1</span>) * Math.PI).toFloat() / <span class="number">2.0f</span> + <span class="number">0.5f</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用自定义插值器的例子,实现先减速后加速的动画</span></span><br><span class="line"><span class="keyword">val</span> rotateAnimator = ObjectAnimator.ofFloat(view, <span class="string">"rotation"</span>, <span class="number">0f</span>, <span class="number">360f</span>)</span><br><span class="line">rotateAnimator.duration = <span class="number">1000</span></span><br><span class="line">rotateAnimator.interpolator = DecelerateAccelerateInterpolator()</span><br><span class="line"></span><br><span class="line">rotateAnimator.start()</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="使用估值器"><a href="#使用估值器" class="headerlink" title="使用估值器"></a>使用估值器</h2><p>估值器可以让动画更加精准、灵活。估值器可以将属性值的变化从线性变化转换为非线性变化。</p>
<p><strong>ObjectAnimator</strong>对象的<code>setEvaluator()</code>方法可以设置估值器。</p>
<p><code>setEvaluator()</code>方法的使用示例如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> objectAnimator = ObjectAnimator.ofFloat(button, <span class="string">"alpha"</span>, <span class="number">0.0f</span>, <span class="number">1.0f</span>)</span><br><span class="line"></span><br><span class="line">objectAnimator.setEvaluator(ArgbEvaluator()())</span><br></pre></td></tr></table></figure>
<h2 id="使用关键帧"><a href="#使用关键帧" class="headerlink" title="使用关键帧"></a>使用关键帧</h2><p>关键帧可以让动画更加复杂、多样。关键帧可以指定动画在特定时间点的属性值。</p>
<p><strong>PropertyValuesHolder</strong>对象的<code>setKeyframe()</code>方法可以设置关键帧。</p>
<p><code>setKeyframe()</code>方法的使用示例如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> alphaHolder = PropertyValuesHolder.ofFloat(<span class="string">"alpha"</span>)</span><br><span class="line"></span><br><span class="line">alphaHolder.setKeyframe(<span class="number">0.0f</span>, <span class="number">0.0f</span>)</span><br><span class="line">alphaHolder.setKeyframe(<span class="number">0.5f</span>, <span class="number">0.5f</span>)</span><br><span class="line">alphaHolder.setKeyframe(<span class="number">1.0f</span>, <span class="number">1.0f</span>)</span><br></pre></td></tr></table></figure>
<p>该代码创建了一个<code>alphaHolder</code>对象,它描述了控件的<code>alpha</code>属性从0.0f到1.0f的非线性变化。</p>
<h2 id="使用动画组合"><a href="#使用动画组合" class="headerlink" title="使用动画组合"></a>使用动画组合</h2><p>动画组合可以让动画更加丰富、生动。动画组合可以将多个动画组合在一起,形成一个复杂的动画效果。</p>
<p><strong>AnimatorSet</strong>类可以用于创建动画组合。</p>
<p><strong>AnimatorSet</strong>的构造方法如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">AnimatorSet</span><span class="params">()</span></span>: AnimatorSet {</span><br><span class="line"> <span class="keyword">return</span> AnimatorSet()</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>AnimatorSet</strong>对象可以通过<code>play()</code>方法来添加动画。</p>
<p><code>play()</code>方法的使用示例如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> objectAnimator1 = ObjectAnimator.ofFloat(button, <span class="string">"alpha"</span>, <span class="number">0.0f</span>, <span class="number">1.0f</span>)</span><br><span class="line"><span class="keyword">val</span> objectAnimator2 = ObjectAnimator.ofFloat(button, <span class="string">"translationX"</span>, <span class="number">0f</span>, <span class="number">100f</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> animatorSet = AnimatorSet()</span><br><span class="line">animatorSet.playTogether(objectAnimator1, objectAnimator2)</span><br><span class="line"></span><br><span class="line">animatorSet.duration = <span class="number">2000</span></span><br><span class="line">animatorSet.start()</span><br></pre></td></tr></table></figure>
<p>该代码创建了一个动画组合,它将按钮的<code>alpha</code>属性从0.0f变为1.0f,并将按钮的<code>translationX</code>属性从0f变为100f。</p>
<h2 id="使用动画监听器"><a href="#使用动画监听器" class="headerlink" title="使用动画监听器"></a>使用动画监听器</h2><p>动画监听器可以让开发者在动画的不同阶段进行监听和操作。动画监听器可以监听动画的开始、结束、重复、取消等事件。</p>
<p><strong>ObjectAnimator</strong>对象的<code>addListener()</code>方法可以添加动画监听器。</p>
<p><code>addListener()</code>方法的使用示例如下:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> objectAnimator = ObjectAnimator.ofFloat(button, <span class="string">"alpha"</span>, <span class="number">0.0f</span>, <span class="number">1.0f</span>)</span><br><span class="line"></span><br><span class="line">objectAnimator.addListener(<span class="keyword">object</span> : Animator.AnimatorListener {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onAnimationStart</span><span class="params">(animation: <span class="type">Animator</span>)</span></span> {</span><br><span class="line"> <span class="comment">// 动画开始时执行的操作</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onAnimationEnd</span><span class="params">(animation: <span class="type">Animator</span>)</span></span> {</span><br><span class="line"> <span class="comment">// 动画结束时执行的操作</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onAnimationCancel</span><span class="params">(animation: <span class="type">Animator</span>)</span></span> {</span><br><span class="line"> <span class="comment">// 动画取消时执行的操作</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onAnimationRepeat</span><span class="params">(animation: <span class="type">Animator</span>)</span></span> {</span><br><span class="line"> <span class="comment">// 动画重复时执行的操作</span></span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">objectAnimator.start()</span><br></pre></td></tr></table></figure>
<h2 id="性能优化与最佳实践"><a href="#性能优化与最佳实践" class="headerlink" title="性能优化与最佳实践"></a>性能优化与最佳实践</h2><h3 id="内存管理与动画"><a href="#内存管理与动画" class="headerlink" title="内存管理与动画"></a>内存管理与动画</h3><ol>
<li><p>使用<code>ViewPropertyAnimator</code></p>
<p><code>ViewPropertyAnimator</code> 是一种轻量级的动画系统,它在大多数情况下都比传统的属性动画更高效。使用 <code>ViewPropertyAnimator</code> 可以避免创建大量的临时对象,从而减小内存占用。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用ViewPropertyAnimator的例子</span></span><br><span class="line">view.animate()</span><br><span class="line"> .translationX(<span class="number">200f</span>)</span><br><span class="line"> .setDuration(<span class="number">1000</span>)</span><br><span class="line"> .start()</span><br></pre></td></tr></table></figure>
</li>
<li><p>避免使用大型位图</p>
<p>在动画中使用大型位图可能会导致内存占用过高,引起性能问题。可以考虑使用矢量图或适当压缩和缩放位图。</p>
</li>
</ol>
<h3 id="GPU过度绘制的处理"><a href="#GPU过度绘制的处理" class="headerlink" title="GPU过度绘制的处理"></a>GPU过度绘制的处理</h3><ol>
<li><p>使用<code>HardwareLayer</code></p>
<p>将动画目标的视图标记为硬件层可以减少过度绘制,提高性能。在动画开始前将视图设置为硬件层,动画结束后清除硬件层。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 将View标记为硬件层</span></span><br><span class="line">view.setLayerType(View.LAYER_TYPE_HARDWARE, <span class="literal">null</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在动画结束后清除硬件层</span></span><br><span class="line">translateAnimator.addListener(<span class="keyword">object</span> : AnimatorListenerAdapter() {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onAnimationEnd</span><span class="params">(animation: <span class="type">Animator</span>?)</span></span> {</span><br><span class="line"> view.setLayerType(View.LAYER_TYPE_NONE, <span class="literal">null</span>)</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure>
</li>
<li><p>使用<code>View.setWillNotDraw(true)</code></p>
<p>如果视图不需要手动绘制内容,可以通过 <code>setWillNotDraw(true)</code> 来避免触发不必要的绘制操作。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在View初始化时设置</span></span><br><span class="line">view.setWillNotDraw(<span class="literal">true</span>)</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h3 id="硬件加速与动画性能"><a href="#硬件加速与动画性能" class="headerlink" title="硬件加速与动画性能"></a>硬件加速与动画性能</h3><p>启用硬件加速可以提高动画性能,但在某些情况下可能导致问题。确保测试硬件加速对应用性能的影响,并根据需要进行调整。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在AndroidManifest.xml中启用硬件加速</span></span><br><span class="line"><application android:hardwareAccelerated=<span class="string">"true"</span>></span><br><span class="line"> <!-- ... --></span><br><span class="line"></application></span><br></pre></td></tr></table></figure>
<h3 id="性能建议"><a href="#性能建议" class="headerlink" title="性能建议"></a>性能建议</h3><ol>
<li>避免过多的图层叠加:减少视图层级,降低过度绘制的可能性。</li>
<li>使用<code>Handler</code>和<code>Runnable</code>进行动画更新</li>
<li>避免在<code>onDraw</code>方法中执行复杂的计算:这可能导致界面卡顿。</li>
<li>使用简单的插值器或估值器。</li>
</ol>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Android动画是每一个开发者必备的技能,它具有简单易用、灵活性强等优点。通过掌握属性动画的原理和高级技巧,可以让开发者创建出更加丰富、生动的动画效果。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>动ݓ
错过Android主线程空闲期,你可能损失的不仅仅是性能
https://www.rousetime.com/2023/12/15/错过Android主线程空闲期,你可能损失的不仅仅是性能/
2023-12-15T01:29:31.000Z
2023-12-15T01:29:57.887Z
<p>在Android应用程序的开发过程中,性能优化一直是开发者关注的焦点之一。在这个背景下,Android系统提供了一项强大的工具——IdleHandler,它能够帮助开发者在应用程序的空闲时段执行任务,从而提高应用的整体性能。IdleHandler的机制基于Android主线程的空闲状态,使得开发者能够巧妙地利用这些空闲时间执行一些耗时的操作,而不影响用户界面的流畅性。</p>
<p>在深入研究IdleHandler之前,让我们先了解一下它的基本原理,以及为何它成为Android性能优化的重要组成部分。</p>
<h2 id="IdleHandler的基本原理"><a href="#IdleHandler的基本原理" class="headerlink" title="IdleHandler的基本原理"></a>IdleHandler的基本原理</h2><p>Android应用的主线程通过一个消息循环(Message Loop)来处理各种事件和任务。当主线程没有新的消息需要处理时,它就处于空闲状态。这就是IdleHandler发挥作用的时机。</p>
<p>通过注册IdleHandler来告诉系统在主线程空闲时执行特定的任务。当主线程进入空闲状态时,系统会依次调用注册的IdleHandler,执行相应的任务。</p>
<p>IdleHandler与Handler和MessageQueue密切相关。它通过MessageQueue的空闲时间来执行任务。每当主线程处理完一个消息后,系统会检查是否有注册的IdleHandler需要执行。</p>
<h3 id="空闲状态的定义"><a href="#空闲状态的定义" class="headerlink" title="空闲状态的定义"></a>空闲状态的定义</h3><p>了解什么时候主线程被认为是空闲的至关重要。一般情况下,Android系统认为主线程在处理完所有消息后即处于空闲状态。IdleHandler通过这个定义,能够在保证不影响用户体验的前提下执行一些耗时的操作。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"> // 没有消息,判断是否有IdleHandler</span><br><span class="line"> if (pendingIdleHandlerCount < 0</span><br><span class="line"> && (mMessages == null || now < mMessages.when)) {</span><br><span class="line"> pendingIdleHandlerCount = mIdleHandlers.size();</span><br><span class="line"> }</span><br><span class="line"> if (pendingIdleHandlerCount <= 0) {</span><br><span class="line"> // No idle handlers to run. Loop and wait some more.</span><br><span class="line"> mBlocked = true;</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (mPendingIdleHandlers == null) {</span><br><span class="line"> mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];</span><br><span class="line"> }</span><br><span class="line"> mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);</span><br><span class="line"></span><br><span class="line"> ....</span><br><span class="line"></span><br><span class="line">// 执行IdleHandler</span><br><span class="line">for (int i = 0; i < pendingIdleHandlerCount; i++) {</span><br><span class="line"> final IdleHandler idler = mPendingIdleHandlers[i];</span><br><span class="line"> mPendingIdleHandlers[i] = null; // release the reference to the handler</span><br><span class="line"></span><br><span class="line"> boolean keep = false;</span><br><span class="line"> try {</span><br><span class="line"> keep = idler.queueIdle();</span><br><span class="line"> } catch (Throwable t) {</span><br><span class="line"> Log.wtf(TAG, "IdleHandler threw exception", t);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (!keep) {</span><br><span class="line"> synchronized (this) {</span><br><span class="line"> mIdleHandlers.remove(idler);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="如何使用IdleHandler"><a href="#如何使用IdleHandler" class="headerlink" title="如何使用IdleHandler"></a>如何使用IdleHandler</h2><p>使用<code>IdleHandler</code>可以执行一些轻量级的任务,例如加载数据、更新<code>UI</code>等。以下是使用<code>IdleHandler</code>的几个使用技巧:</p>
<ol>
<li>注册IdleHandler:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Looper.myQueue().addIdleHandler(MyIdleHandler())</span><br><span class="line"></span><br><span class="line">class MyIdleHandler : MessageQueue.IdleHandler {</span><br><span class="line"> override fun queueIdle(): Boolean {</span><br><span class="line"> // 在主线程空闲时执行的任务逻辑</span><br><span class="line"> performIdleTask()</span><br><span class="line"> // 返回 true,表示任务处理完毕,不再执行</span><br><span class="line"> return true</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> private fun performIdleTask() {</span><br><span class="line"> // 具体的任务逻辑</span><br><span class="line"> // ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ol start="2">
<li>取消注册</li>
</ol>
<p>当不需要继续执行任务时,可以通过<code>removeIdleHandler</code>方法取消注册</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Looper.myQueue().removeIdleHandler(idleHandler);</span><br></pre></td></tr></table></figure>
<h2 id="IdleHandler的适用场景"><a href="#IdleHandler的适用场景" class="headerlink" title="IdleHandler的适用场景"></a>IdleHandler的适用场景</h2><ul>
<li>轻量级任务:<code>IdleHandler</code>主要用于执行轻量级的任务。由于它是在主线程空闲时执行,所以不适合执行耗时的任务。</li>
<li>主线程空闲时执行:<code>IdleHandler</code>通过在主线程空闲时被调用,避免了主线程的阻塞。因此,适用于需要在主线程执行的任务,并且这些任务对于用户体验的影响较小。</li>
<li>优先级较低的任务:如果有多个任务注册了<code>IdleHandler</code>,系统会按照注册的顺序调用它们的<code>queueIdle</code>方法。因此,适用于需要在较低优先级下执行的任务。</li>
</ul>
<p>总的来说<code>IdleHandler</code>适用于需要在主线程空闲时执行的轻量级任务,以提升应用的性能和用户体验。</p>
<h2 id="高级应用"><a href="#高级应用" class="headerlink" title="高级应用"></a>高级应用</h2><ol>
<li><strong>性能监控与优化</strong><br>利用 <code>IdleHandler</code> 可以实现性能监控和优化,例如统计每次空闲时的内存占用情况,或者执行一些内存释放操作。</li>
<li><strong>预加载数据</strong><br>在用户操作前,通过 <code>IdleHandler</code> 提前加载一些可能会用到的数据,提高用户体验。</li>
<li><strong>动态资源加载</strong><br>利用空闲时间预加载和解析资源,减轻在用户操作时的资源加载压力。</li>
</ol>
<h2 id="性能优化技巧"><a href="#性能优化技巧" class="headerlink" title="性能优化技巧"></a>性能优化技巧</h2><p>虽然IdleHandler提供了一个方便的机制来在主线程空闲时执行任务,但在使用过程中仍需注意一些性能方面的问题。</p>
<ol>
<li><strong>任务的轻量级处理:</strong> 确保注册的IdleHandler中的任务是轻量级的,不要在空闲时执行过于复杂或耗时的操作,以免影响主线程的响应性能。</li>
<li><strong>避免频繁注册和取消IdleHandler: </strong>频繁注册和取消IdleHandler可能会引起性能问题,因此建议在应用的生命周期内尽量减少注册和取消的操作。可以在应用启动时注册IdleHandler,在应用退出时取消注册。</li>
<li><strong>合理设置任务执行频率: </strong>根据任务的性质和执行需求,合理设置任务的执行频率。不同的任务可能需要在不同的时间间隔内执行,这样可以更好地平衡性能和功能需求。</li>
</ol>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过深度解析 <code>IdleHandler</code> 的原理和高级应用,让我们更好地利用这一工具进行性能优化。在实际项目中,灵活运用 <code>IdleHandler</code> 可以有效提升应用的响应速度和用户体验。希望本文能够激发大家对于Android性能优化的更多思考和实践。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<p>在Android应用程序的开发过程中,性能优化&#
精通Android线程池的必备高级技巧
https://www.rousetime.com/2023/12/14/精通Android线程池的必备高级技巧/
2023-12-14T01:59:37.000Z
2023-12-14T02:00:16.005Z
<p>在Android开发中,我们经常会遇到需要执行耗时操作的情况,例如网络请求、数据库读写、图片加载等。为了避免这些操作阻塞主线程,我们通常会使用线程池来管理并发执行任务。而Android Executors是一个非常常用的线程池管理工具。本文将深入解析Android Executors的原理,帮助大家更好地理解和使用这个工具。</p>
<h2 id="Android线程池简介"><a href="#Android线程池简介" class="headerlink" title="Android线程池简介"></a>Android线程池简介</h2><p>在移动应用中,频繁地创建和销毁线程可能导致系统资源的浪费。线程池通过维护一组可重用的线程,降低了线程创建和销毁的开销。这种机制使得线程的使用更加高效,同时能够控制并发线程的数量,防止系统资源被过度占用。</p>
<h3 id="线程池的作用和优势"><a href="#线程池的作用和优势" class="headerlink" title="线程池的作用和优势"></a>线程池的作用和优势</h3><ul>
<li><p><strong>任务队列管理:</strong> 线程池通过任务队列管理待执行的任务,确保它们按照一定的顺序执行,提高了任务的执行效率。</p>
</li>
<li><p><strong>资源控制:</strong> 可以限制并发执行的线程数量,避免资源耗尽和竞争条件的发生。</p>
</li>
<li><p><strong>线程复用:</strong> 线程池维护一组可复用的线程,减少了线程的创建和销毁,提高了系统的性能。</p>
</li>
</ul>
<h2 id="Executors框架概述"><a href="#Executors框架概述" class="headerlink" title="Executors框架概述"></a>Executors框架概述</h2><p><code>java.util.concurrent.Executors</code>是Java中用于创建线程池的工厂类。它提供了一系列静态方法,可以方便地创建不同类型的线程池。这些线程池类型包括CachedThreadPool、FixedThreadPool、ScheduledThreadPool和SingleThreadExecutor等。</p>
<p>通过使用<code>Executors</code>,可以简化线程池的创建过程,专注于任务的实现和调度。</p>
<h3 id="Executors工厂方法"><a href="#Executors工厂方法" class="headerlink" title="Executors工厂方法"></a>Executors工厂方法</h3><p><code>Executors</code>类提供了几个常用的工厂方法:</p>
<ul>
<li><p><strong>newCachedThreadPool():</strong> 创建一个可根据需要创建新线程的线程池,但在可用时将重用先前构造的线程。</p>
</li>
<li><p><strong>newFixedThreadPool(int n):</strong> 创建一个具有固定线程数的线程池,超过的任务会在队列中等待。</p>
</li>
<li><p><strong>newScheduledThreadPool(int corePoolSize):</strong> 创建一个线程池,它可调度在给定的延迟之后运行命令,或者定期执行命令。</p>
</li>
<li><p><strong>newSingleThreadExecutor():</strong> 创建一个使用单个 worker 线程的线程池,以无界队列方式来运行该线程。</p>
</li>
</ul>
<h3 id="任务提交和执行"><a href="#任务提交和执行" class="headerlink" title="任务提交和执行"></a>任务提交和执行</h3><p>一旦线程池创建完成,可以通过将任务提交给线程池来执行。任务可以是实现了<code>Runnable</code>接口的普通线程,也可以是实现了<code>Callable</code>接口的带返回值的线程。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">executorService.submit(() -> {</span><br><span class="line"> <span class="comment">// 执行任务逻辑</span></span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 关闭线程池</span></span><br><span class="line">executorService.shutdown();</span><br></pre></td></tr></table></figure>
<p>线程池有一个生命周期,它包括创建、运行和关闭三个阶段。创建后线程池可以接受并执行任务,一旦不再需要,可以通过调用<code>shutdown()</code>或<code>shutdownNow()</code>方法来关闭线程池。</p>
<h2 id="ThreadPoolExecutor"><a href="#ThreadPoolExecutor" class="headerlink" title="ThreadPoolExecutor"></a>ThreadPoolExecutor</h2><p><code>ThreadPoolExecutor</code>是<code>java.util.concurrent</code>包中的核心类之一,用于实现自定义的线程池。理解<code>ThreadPoolExecutor</code>的工作原理对于深入掌握线程池的使用至关重要。</p>
<h3 id="核心参数解释"><a href="#核心参数解释" class="headerlink" title="核心参数解释"></a>核心参数解释</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">public ThreadPoolExecutor(int corePoolSize,</span><br><span class="line"> int maximumPoolSize,</span><br><span class="line"> long keepAliveTime,</span><br><span class="line"> TimeUnit unit,</span><br><span class="line"> BlockingQueue<Runnable> workQueue,</span><br><span class="line"> ThreadFactory threadFactory) {</span><br><span class="line"> this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,</span><br><span class="line"> threadFactory, defaultHandler);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>ThreadPoolExecutor</code>的构造方法包括多个参数,其中一些是线程池的核心参数:</p>
<ul>
<li><p><strong>corePoolSize:</strong> 线程池的核心线程数,即线程池维护的最小线程数。</p>
</li>
<li><p><strong>maximumPoolSize:</strong> 线程池的最大线程数,即线程池允许的最大线程数。</p>
</li>
<li><p><strong>keepAliveTime:</strong> 当线程池线程数量超过核心线程数时,多余的空闲线程在终止前等待新任务的最长时间。</p>
</li>
<li><p><strong>workQueue:</strong> 用于保存等待执行的任务的阻塞队列。</p>
</li>
</ul>
<h3 id="线程池的生命周期和状态转换"><a href="#线程池的生命周期和状态转换" class="headerlink" title="线程池的生命周期和状态转换"></a>线程池的生命周期和状态转换</h3><p>线程池具有不同的生命周期状态,包括<code>RUNNING</code>、<code>SHUTDOWN</code>、<code>STOP</code>和<code>TERMINATED</code>。</p>
<ul>
<li><p><strong>创建阶段:</strong> 线程池被创建时,初始状态为<code>RUNNING</code>。在这个阶段,线程池可以接受新的任务,并会创建核心线程来执行任务。</p>
</li>
<li><p><strong>运行阶段:</strong> 在运行阶段,线程池按照任务的到来创建和回收线程,同时任务会被放入工作队列中等待执行。</p>
</li>
<li><p><strong>关闭阶段:</strong> 当线程池不再接受新任务时,进入关闭阶段。在此阶段,线程池会停止接受新任务,但会继续执行已经在队列中的任务。线程池的状态将变为<code>SHUTDOWN</code>。</p>
</li>
<li><p><strong>终止阶段:</strong> 在所有任务执行完成后,线程池进入终止阶段。此时,线程池的状态变为<code>TERMINATED</code>。在这个状态下,线程池中的所有线程都已经被销毁。</p>
</li>
</ul>
<p>线程池的状态转换是受到任务提交、关闭和线程池终止等事件的影响的。</p>
<ul>
<li><p><strong>任务提交:</strong> 在任务提交时,线程池可能会创建新的线程或者将任务放入队列中等待执行。</p>
</li>
<li><p><strong>关闭:</strong> 调用线程池的<code>shutdown()</code>方法将线程池切换到关闭状态。在关闭状态下,线程池不再接受新任务,但会继续执行队列中的任务。</p>
</li>
<li><p><strong>终止:</strong> 当所有任务执行完成,并且调用了<code>shutdown()</code>后,线程池进入终止状态。</p>
</li>
<li><p><strong>异常情况:</strong> 如果发生异常,线程池可能进入<code>TERMINATED</code>状态,但并不是所有的线程都已经终止。这种情况下,线程池可能需要通过适当的手段来处理未终止的线程。</p>
</li>
</ul>
<h3 id="拒绝策略"><a href="#拒绝策略" class="headerlink" title="拒绝策略"></a>拒绝策略</h3><p>线程池的拒绝策略决定了当线程池无法执行提交的任务时的行为。常见的拒绝策略包括<code>AbortPolicy</code>、<code>CallerRunsPolicy</code>、<code>DiscardPolicy</code>和<code>DiscardOldestPolicy</code>等。</p>
<h4 id="AbortPolicy"><a href="#AbortPolicy" class="headerlink" title="AbortPolicy"></a>AbortPolicy</h4><p><code>AbortPolicy</code>是默认的拒绝策略,当线程池无法接受新任务时,会抛出<code>RejectedExecutionException</code>异常。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">AbortPolicy</span> <span class="keyword">implements</span> <span class="title">RejectedExecutionHandler</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AbortPolicy</span><span class="params">()</span> </span>{ }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">rejectedExecution</span><span class="params">(Runnable r, ThreadPoolExecutor e)</span> </span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RejectedExecutionException(<span class="string">"Task "</span> + r.toString() +</span><br><span class="line"> <span class="string">" rejected from "</span> +</span><br><span class="line"> e.toString());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="CallerRunsPolicy"><a href="#CallerRunsPolicy" class="headerlink" title="CallerRunsPolicy"></a>CallerRunsPolicy</h4><p><code>CallerRunsPolicy</code>拒绝策略会直接在提交任务的线程中执行被拒绝的任务。这种策略适用于任务的负载比较轻,而且执行时间短暂的情况。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">CallerRunsPolicy</span> <span class="keyword">implements</span> <span class="title">RejectedExecutionHandler</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">CallerRunsPolicy</span><span class="params">()</span> </span>{ }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">rejectedExecution</span><span class="params">(Runnable r, ThreadPoolExecutor e)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!e.isShutdown()) {</span><br><span class="line"> r.run();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="DiscardPolicy"><a href="#DiscardPolicy" class="headerlink" title="DiscardPolicy"></a>DiscardPolicy</h4><p><code>DiscardPolicy</code>拒绝策略会默默地丢弃无法处理的任务,不提供任何反馈。如果任务队列已满,新提交的任务会被直接丢弃。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">DiscardPolicy</span> <span class="keyword">implements</span> <span class="title">RejectedExecutionHandler</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DiscardPolicy</span><span class="params">()</span> </span>{ }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">rejectedExecution</span><span class="params">(Runnable r, ThreadPoolExecutor e)</span> </span>{</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="DiscardOldestPolicy"><a href="#DiscardOldestPolicy" class="headerlink" title="DiscardOldestPolicy"></a>DiscardOldestPolicy</h4><p><code>DiscardOldestPolicy</code>拒绝策略会丢弃队列中最早被提交但尚未被执行的任务,然后将新任务加入队列。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">DiscardOldestPolicy</span> <span class="keyword">implements</span> <span class="title">RejectedExecutionHandler</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DiscardOldestPolicy</span><span class="params">()</span> </span>{ }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">rejectedExecution</span><span class="params">(Runnable r, ThreadPoolExecutor e)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!e.isShutdown()) {</span><br><span class="line"> e.getQueue().poll();</span><br><span class="line"> e.execute(r);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>也可以使用<code>setRejectedExecutionHandler()</code>方法设置拒绝策略,处理线程池无法接受新任务时的行为。或者实现<code>RejectedExecutionHandler</code>接口定义自己的策略。</p>
<h2 id="四种内置线程池类型"><a href="#四种内置线程池类型" class="headerlink" title="四种内置线程池类型"></a>四种内置线程池类型</h2><p>在<code>Executors</code>类中,提供了四种内置的线程池类型,分别是<code>CachedThreadPool</code>、<code>FixedThreadPool</code>、<code>ScheduledThreadPool</code>和<code>SingleThreadExecutor</code>。每种线程池类型都适用于不同的场景,下面我们将详细介绍每一种类型的特点和适用情况。</p>
<h3 id="CachedThreadPool"><a href="#CachedThreadPool" class="headerlink" title="CachedThreadPool"></a>CachedThreadPool</h3><ul>
<li><p><strong>特点:</strong>动态增加线程数量,如果线程池的当前线程数超过了处理任务所需的线程数,多余的空闲线程会在60秒后被终止。</p>
</li>
<li><p><strong>适用场景:</strong>处理大量短时任务,任务执行时间较短,且任务量不确定。</p>
</li>
</ul>
<h3 id="FixedThreadPool"><a href="#FixedThreadPool" class="headerlink" title="FixedThreadPool"></a>FixedThreadPool</h3><ul>
<li><p><strong>特点:</strong>固定线程数量的线程池,当线程池中的线程达到核心线程数时,新任务将在队列中等待。</p>
</li>
<li><p><strong>适用场景:</strong>适用于并发线程数有限的情况,可以控制资源的最大并发数。</p>
</li>
</ul>
<h3 id="ScheduledThreadPool"><a href="#ScheduledThreadPool" class="headerlink" title="ScheduledThreadPool"></a>ScheduledThreadPool</h3><ul>
<li><p><strong>特点:</strong>类似于<code>FixedThreadPool</code>,但增加了定时执行任务的功能,可以在指定时间执行任务。</p>
</li>
<li><p><strong>适用场景:</strong>适用于需要定期执行任务的场景,例如定时任务、周期性数据同步等。</p>
</li>
</ul>
<h3 id="SingleThreadExecutor"><a href="#SingleThreadExecutor" class="headerlink" title="SingleThreadExecutor"></a>SingleThreadExecutor</h3><ul>
<li><p><strong>特点:</strong>只有一个核心线程的线程池,确保所有任务按照指定顺序执行。</p>
</li>
<li><p><strong>适用场景:</strong>适用于需要顺序执行任务的场景,例如任务之间有依赖关系的情况。</p>
</li>
</ul>
<h2 id="阻塞队列的选择与优化"><a href="#阻塞队列的选择与优化" class="headerlink" title="阻塞队列的选择与优化"></a>阻塞队列的选择与优化</h2><p>线程池中的阻塞队列对于任务的存储和调度起着重要作用。不同的阻塞队列实现对线程池的性能和行为产生直接影响。以下是一些常见的阻塞队列以及它们的选择与优化建议。</p>
<h3 id="LinkedBlockingQueue"><a href="#LinkedBlockingQueue" class="headerlink" title="LinkedBlockingQueue"></a>LinkedBlockingQueue</h3><p><code>LinkedBlockingQueue</code>是一个基于链表的阻塞队列,可以选择不设置容量(默认为<code>Integer.MAX_VALUE</code>)。</p>
<ul>
<li><p><strong>优势:</strong> 对于大多数场景来说,具有较高的吞吐量和性能。</p>
</li>
<li><p><strong>注意:</strong> 如果任务提交速度高于线程处理速度,队列可能会无限制增长,占用大量内存。</p>
</li>
</ul>
<h3 id="ArrayBlockingQueue"><a href="#ArrayBlockingQueue" class="headerlink" title="ArrayBlockingQueue"></a>ArrayBlockingQueue</h3><p><code>ArrayBlockingQueue</code>是一个基于数组的有界阻塞队列,必须设置容量。</p>
<ul>
<li><p><strong>优势:</strong> 能够限制队列的最大容量,避免无限制增长。</p>
</li>
<li><p><strong>注意:</strong> 需要根据应用场景合理设置队列容量,避免太小导致性能瓶颈,太大则可能占用过多内存。</p>
</li>
</ul>
<h3 id="SynchronousQueue"><a href="#SynchronousQueue" class="headerlink" title="SynchronousQueue"></a>SynchronousQueue</h3><p><code>SynchronousQueue</code>是一个没有容量的阻塞队列,每个插入操作必须等待另一个线程的移除操作。</p>
<ul>
<li><p><strong>优势:</strong> 高效地传递任务,适用于高并发场景。</p>
</li>
<li><p><strong>注意:</strong> 当线程池的最大线程数小于队列容量时,可能导致任务被直接拒绝。</p>
</li>
</ul>
<h3 id="PriorityBlockingQueue"><a href="#PriorityBlockingQueue" class="headerlink" title="PriorityBlockingQueue"></a>PriorityBlockingQueue</h3><p><code>PriorityBlockingQueue</code>是一个支持优先级的无界阻塞队列。</p>
<ul>
<li><p><strong>优势:</strong> 具备任务优先级特性,可以实现任务按照优先级顺序执行。</p>
</li>
<li><p><strong>注意:</strong> 需要确保任务实现了<code>Comparable</code>接口或提供自定义比较器。</p>
</li>
</ul>
<h2 id="自定义线程池"><a href="#自定义线程池" class="headerlink" title="自定义线程池"></a>自定义线程池</h2><p>除了使用内置的线程池类型外,<code>ThreadPoolExecutor</code>还允许开发者自定义线程池,以满足特定的业务需求。自定义线程池的步骤如下:</p>
<h3 id="创建ThreadPoolExecutor实例"><a href="#创建ThreadPoolExecutor实例" class="headerlink" title="创建ThreadPoolExecutor实例"></a>创建ThreadPoolExecutor实例</h3><p>通过<code>ThreadPoolExecutor</code>的构造方法,设置核心线程数、最大线程数、阻塞队列等参数来创建线程池实例。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个自定义的线程池</span></span><br><span class="line">ThreadPoolExecutor customThreadPool = <span class="keyword">new</span> ThreadPoolExecutor(</span><br><span class="line"> corePoolSize,</span><br><span class="line"> maximumPoolSize,</span><br><span class="line"> keepAliveTime,</span><br><span class="line"> timeUnit,</span><br><span class="line"> workQueue,</span><br><span class="line"> threadFactory,</span><br><span class="line"> handler</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<h3 id="配置ThreadFactory"><a href="#配置ThreadFactory" class="headerlink" title="配置ThreadFactory"></a>配置ThreadFactory</h3><p>通过<code>setThreadFactory()</code>方法配置线程工厂,用于创建新的线程。可以使用<code>Executors.defaultThreadFactory()</code>创建默认线程工厂,也可以实现<code>ThreadFactory</code>接口自定义线程创建过程。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 自定义线程工厂</span></span><br><span class="line">ThreadFactory customThreadFactory = <span class="keyword">new</span> ThreadFactory() {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> AtomicInteger threadNumber = <span class="keyword">new</span> AtomicInteger(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Thread <span class="title">newThread</span><span class="params">(Runnable r)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Thread(r, <span class="string">"CustomThread-"</span> + threadNumber.getAndIncrement());</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">customThreadPool.setThreadFactory(customThreadFactory);</span><br></pre></td></tr></table></figure>
<h3 id="配置Handler"><a href="#配置Handler" class="headerlink" title="配置Handler"></a>配置Handler</h3><p>通过<code>setThreadFactory()</code>方法配置<code>RejectedExecutionHandler</code>,用于处理被拒绝的任务。可以选择使用预定义的处理器,如<code>AbortPolicy</code>、<code>CallerRunsPolicy</code>等,也可以实现<code>RejectedExecutionHandler</code>接口定义自己的处理逻辑。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 自定义拒绝策略处理器</span></span><br><span class="line">RejectedExecutionHandler customHandler = (r, executor) -> {</span><br><span class="line"> <span class="comment">// 处理被拒绝的任务,例如记录日志或其他处理</span></span><br><span class="line"> System.out.println(<span class="string">"Task Rejected: "</span> + r.toString());</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">customThreadPool.setRejectedExecutionHandler(customHandler);</span><br></pre></td></tr></table></figure>
<h3 id="提交任务"><a href="#提交任务" class="headerlink" title="提交任务"></a>提交任务</h3><p>通过调用<code>execute()</code>或<code>submit()</code>方法将任务提交给自定义的线程池执行。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">customThreadPool.execute(() -> {</span><br><span class="line"> <span class="comment">// 执行任务逻辑</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<h2 id="线程池的注意事项"><a href="#线程池的注意事项" class="headerlink" title="线程池的注意事项"></a>线程池的注意事项</h2><p>在使用线程池时,以下注意事项可以充分发挥线程池的优势,提高应用性能和稳定性。</p>
<h3 id="合理设置线程池参数"><a href="#合理设置线程池参数" class="headerlink" title="合理设置线程池参数"></a>合理设置线程池参数</h3><ul>
<li><p><strong>核心线程数(corePoolSize):</strong> 根据应用的并发量和性能需求来设置,避免设置过高或过低。</p>
</li>
<li><p><strong>最大线程数(maximumPoolSize):</strong> 根据应用的并发峰值来设置,不要设置过多,以免占用过多系统资源。</p>
</li>
<li><p><strong>阻塞队列(workQueue):</strong> 选择合适的队列类型,如<code>LinkedBlockingQueue</code>或<code>ArrayBlockingQueue</code>,以及合适的队列容量,避免队列溢出。</p>
</li>
<li><p><strong>线程存活时间(keepAliveTime):</strong> 配合阻塞队列,避免线程池中的线程数量长时间维持在核心线程数之上。</p>
</li>
</ul>
<h3 id="检测和处理异常"><a href="#检测和处理异常" class="headerlink" title="检测和处理异常"></a>检测和处理异常</h3><p>在任务的<code>run</code>方法中要注意捕获异常,以避免异常抛出导致线程终止。可以在<code>Thread.UncaughtExceptionHandler</code>中处理未捕获的异常。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Thread.setDefaultUncaughtExceptionHandler((t, e) -> {</span><br><span class="line"> <span class="comment">// 处理未捕获的异常</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<h3 id="使用Callable获取任务执行结果"><a href="#使用Callable获取任务执行结果" class="headerlink" title="使用Callable获取任务执行结果"></a>使用Callable获取任务执行结果</h3><p>如果任务需要返回结果,可以使用<code>Callable</code>接口,通过<code>Future</code>对象获取任务的执行结果。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">5</span>);</span><br><span class="line">Future<Integer> future = executorService.submit(() -> {</span><br><span class="line"> <span class="comment">// 执行任务逻辑</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">42</span>;</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取任务执行结果</span></span><br><span class="line"><span class="keyword">int</span> result = future.get();</span><br></pre></td></tr></table></figure>
<h2 id="避免线程泄漏"><a href="#避免线程泄漏" class="headerlink" title="避免线程泄漏"></a>避免线程泄漏</h2><p>线程泄漏是在使用线程池时需要注意的一个问题。下面是一些避免线程泄漏的方法:</p>
<ul>
<li><p><strong>及时释放线程资源:</strong>在任务完成后,务必及时关闭线程,释放线程资源。可以使用<code>shutdown()</code>或<code>shutdownNow()</code>方法来关闭线程池。</p>
</li>
<li><p><strong>使用WeakReference:</strong>如果线程需要引用外部对象,可以使用WeakReference来持有引用。这样,在任务完成后,即使线程还在执行,外部对象也能被垃圾回收器回收。</p>
</li>
<li><p><strong>处理任务取消:</strong>如果任务被取消,需要确保线程能够正确地响应取消操作,并释放相关资源。</p>
</li>
<li><p><strong>使用适当的线程池大小:</strong>如果线程池的大小设置过大,可能会导致线程资源的浪费。因此,需要根据应用程序的需求和系统的性能来合理地设置线程池的大小。</p>
</li>
<li><p><strong>使用线程池监控工具:</strong>可以使用线程池监控工具来检测线程池的状态和资源利用情况,及时发现潜在的线程泄漏问题。</p>
</li>
</ul>
<h2 id="监控线程池状态"><a href="#监控线程池状态" class="headerlink" title="监控线程池状态"></a>监控线程池状态</h2><p>监控线程池的状态可以帮助我们及时发现线程池的异常和性能问题。下面是一些方法来监控线程池的状态:</p>
<ul>
<li><p><strong>使用ThreadPoolExecutor提供的方法:</strong><code>getActiveCount()</code>方法可以获取线程池中正在执行任务的线程数;<code>getTaskCount()</code>方法可以获取线程池中已经执行和正在执行的任务总数;<code>getCompletedTaskCount()</code>方法可以获取线程池中已经完成的任务总数。通过这些方法,可以了解线程池的活动情况。</p>
</li>
<li><p><strong>使用ThreadPoolExecutor提供的getQueue()方法:</strong>这个方法可以获取线程池中的任务队列。通过检查任务队列的长度,可以了解等待执行的任务数。</p>
</li>
<li><p><strong>使用定时任务:</strong>可以定时记录线程池的状态,比如每隔一段时间输出线程池的活动线程数、任务队列的长度等指标。这样可以及时发现线程池的异常情况。</p>
</li>
<li><p><strong>使用性能分析工具:</strong>可以使用性能分析工具,如<code>Android Profiler</code>,来监测线程池的CPU使用率、内存消耗和线程执行时间等指标。这些工具可以提供更详细的线程池性能信息。</p>
</li>
<li><p><strong>自定义监控工具:</strong>根据应用程序的需求,可以自定义监控工具来监测线程池的状态。这些工具可以根据具体情况收集和展示线程池的相关指标。</p>
</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过本文的学习,我们深入了解了Android中线程池Executors的重要性和灵活性。线程池作为一种多线程管理机制,在Android应用中发挥着关键作用,能够有效提高应用的性能、响应速度,同时避免过度占用系统资源。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<p>在Android开发中,我们经常会遇到需要执行&#
Android WorkManager: 轻松管理后台任务
https://www.rousetime.com/2023/12/13/Android-WorkManager-轻松管理后台任务/
2023-12-13T01:41:18.000Z
2023-12-13T01:41:55.184Z
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>在Android应用开发中,有效地管理后台任务是至关重要的。Android WorkManager是一个强大的库,旨在简化任务调度和后台工作管理。本文将深入探讨WorkManager的内部实现细节、原理和具体使用。</p>
<h2 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h2><h3 id="架构概述"><a href="#架构概述" class="headerlink" title="架构概述"></a>架构概述</h3><p>WorkManager的内部实现采用了现代化的任务调度架构,分为以下几个核心组件:</p>
<ul>
<li><p><strong>WorkManager:</strong> 提供任务调度和管理的主要接口,负责协调任务的执行。</p>
</li>
<li><p><strong>Worker:</strong> 开发者定义的执行实际任务的工作单元。每个Worker都运行在独立的后台线程中。</p>
</li>
<li><p><strong>WorkRequest:</strong> 定义了任务的相关参数,如约束条件、重试策略等。分为OneTimeWorkRequest和PeriodicWorkRequest两种。</p>
</li>
<li><p><strong>WorkDatabase:</strong> 用于持久化存储任务的状态、约束条件等信息。</p>
</li>
<li><p><strong>WorkPolicy:</strong> 定义了任务调度的策略,包括立即执行、保留最新、保留最旧等。</p>
</li>
</ul>
<h3 id="调度流程"><a href="#调度流程" class="headerlink" title="调度流程"></a>调度流程</h3><p>当开发者提交任务时,WorkManager首先会将任务信息存储到WorkDatabase中,包括任务的状态、约束条件等。然后,WorkManager会根据设备的API级别智能地选择合适的后台调度器,如JobScheduler、Firebase JobDispatcher和AlarmManager。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">@NonNull</span><br><span class="line">static Scheduler createBestAvailableBackgroundScheduler(</span><br><span class="line"> @NonNull Context context,</span><br><span class="line"> @NonNull WorkManagerImpl workManager) {</span><br><span class="line"></span><br><span class="line"> Scheduler scheduler;</span><br><span class="line"></span><br><span class="line"> if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {</span><br><span class="line"> // use JobScheduler</span><br><span class="line"> scheduler = new SystemJobScheduler(context, workManager);</span><br><span class="line"> setComponentEnabled(context, SystemJobService.class, true);</span><br><span class="line"> Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");</span><br><span class="line"> } else {</span><br><span class="line"> // may use JobDispatcher</span><br><span class="line"> scheduler = tryCreateGcmBasedScheduler(context);</span><br><span class="line"> if (scheduler == null) {</span><br><span class="line"> // use Alarmanager</span><br><span class="line"> scheduler = new SystemAlarmScheduler(context);</span><br><span class="line"> setComponentEnabled(context, SystemAlarmService.class, true);</span><br><span class="line"> Logger.get().debug(TAG, "Created SystemAlarmScheduler");</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return scheduler;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li><p><strong>JobScheduler (API 23+):</strong> 使用JobScheduler进行任务调度,允许系统最优化地执行任务,例如合并相邻的任务以减少设备唤醒次数。</p>
</li>
<li><p><strong>Firebase JobDispatcher (API 14+):</strong> 对于API级别较低的设备,WorkManager会利用Firebase JobDispatcher来实现类似的任务调度。</p>
</li>
<li><p><strong>AlarmManager:</strong> 在API级别更低的设备上,WorkManager会通过AlarmManager来实现任务的调度和唤醒。</p>
</li>
</ul>
<h3 id="智能约束处理"><a href="#智能约束处理" class="headerlink" title="智能约束处理"></a>智能约束处理</h3><p>WorkManager的强大之处在于其智能约束处理,确保任务在满足条件的情况下才会执行。<br>WorkManager的智能约束处理通过Constraints来实现。</p>
<p>智能约束处理基于两个核心概念:硬约束和软约束。</p>
<ul>
<li>硬约束: 这些是必须满足的条件,如网络连接、充电状态等。如果硬约束条件无法满足,WorkManager会等待直到满足条件再执行任务。</li>
<li>软约束: 这些是可选条件,例如设备空闲、存储空间充足等。如果软约束条件无法满足,WorkManager仍然会执行任务,但会尽量在条件合适时执行。</li>
</ul>
<p>这种智能的约束处理方式使得开发者能够更灵活地控制任务的执行时机,提高任务的执行效率和用户体验。</p>
<h2 id="具体使用"><a href="#具体使用" class="headerlink" title="具体使用"></a>具体使用</h2><h3 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h3><p>首先,在项目的<code>build.gradle</code>文件中添加WorkManager的依赖:</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">implementation <span class="string">"androidx.work:work-runtime:2.8.0"</span></span><br></pre></td></tr></table></figure>
<h3 id="创建任务"><a href="#创建任务" class="headerlink" title="创建任务"></a>创建任务</h3><p>创建一个继承自<code>Worker</code>的任务类,实现<code>doWork()</code>方法,定义具体的后台任务逻辑。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyWorker</span></span>(context: Context, params: WorkerParameters) : Worker(context, params) {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">doWork</span><span class="params">()</span></span>: Result {</span><br><span class="line"> <span class="comment">// 执行后台任务逻辑</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> Result.success()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="设置约束和触发条件"><a href="#设置约束和触发条件" class="headerlink" title="设置约束和触发条件"></a>设置约束和触发条件</h3><p>使用<code>Constraints</code>来定义任务的约束条件,例如网络连接、充电状态等。使用<code>OneTimeWorkRequest</code>或<code>PeriodicWorkRequest</code>来创建工作请求,并设置触发条件。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> constraints = Constraints.Builder()</span><br><span class="line"> .setRequiredNetworkType(NetworkType.CONNECTED)</span><br><span class="line"> .setRequiresCharging(<span class="literal">true</span>)</span><br><span class="line"> .build()</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> workRequest = OneTimeWorkRequest.Builder(MyWorker::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span></span><br><span class="line"> .setConstraints(constraints)</span><br><span class="line"> .build()</span><br></pre></td></tr></table></figure>
<h3 id="提交任务"><a href="#提交任务" class="headerlink" title="提交任务"></a>提交任务</h3><p>通过<code>WorkManager</code>的<code>enqueue</code>方法提交任务请求。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">WorkManager.getInstance(context).enqueue(workRequest)</span><br></pre></td></tr></table></figure>
<h2 id="高级使用方式"><a href="#高级使用方式" class="headerlink" title="高级使用方式"></a>高级使用方式</h2><h3 id="自定义重试策略"><a href="#自定义重试策略" class="headerlink" title="自定义重试策略"></a>自定义重试策略</h3><p>WorkManager允许开发者自定义任务的重试策略。通过在<code>OneTimeWorkRequestBuilder</code>中使用<code>setBackoffCriteria</code>方法,可以定义指数退避的重试策略。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> workRequest = OneTimeWorkRequestBuilder<MyWorker>()</span><br><span class="line"> .setBackoffCriteria(</span><br><span class="line"> BackoffPolicy.LINEAR,</span><br><span class="line"> OneTimeWorkRequest.MIN_BACKOFF_MILLIS,</span><br><span class="line"> TimeUnit.MILLISECONDS</span><br><span class="line"> )</span><br><span class="line"> .build()</span><br></pre></td></tr></table></figure>
<h3 id="链式任务"><a href="#链式任务" class="headerlink" title="链式任务"></a>链式任务</h3><p>使用<code>beginWith</code>、<code>then</code>等方法,可以构建任务链,确保它们按照预期的顺序执行。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> workRequest1 = OneTimeWorkRequest.Builder(Task1::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>).<span class="title">build</span></span>()</span><br><span class="line"><span class="keyword">val</span> workRequest2 = OneTimeWorkRequest.Builder(Task2::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>).<span class="title">build</span></span>()</span><br><span class="line"></span><br><span class="line">WorkManager.getInstance(context)</span><br><span class="line"> .beginWith(workRequest1)</span><br><span class="line"> .then(workRequest2)</span><br><span class="line"> .enqueue()</span><br></pre></td></tr></table></figure>
<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul>
<li>数据传递: 当使用WorkManager执行任务时,需要注意任务之间的数据传递。WorkManager提供了<code>Data</code>类来传递简单的键值对数据。确保传递的数据是序列化的,以避免因为进程间通信导致的问题。</li>
<li>任务唯一性: 保证每个任务有唯一的标识符是很重要的。在创建<code>OneTimeWorkRequest</code>时,可以使用<code>setInputData</code>方法设置输入数据,确保任务执行时有足够的信息。</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过本文的介绍,我们详细了解了Android WorkManager的内部实现细节、原理、具体使用。其灵活的任务调度架构和智能约束处理使得开发者能够轻松管理后台任务,提升应用的性能和用户体验。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>在Androi
Android IntentService的开发技巧
https://www.rousetime.com/2023/12/12/Android-IntentService的开发技巧/
2023-12-12T01:58:56.000Z
2023-12-12T01:59:25.870Z
<p>Android 应用开发中,执行后台任务是常见需求之一。其中,<code>IntentService</code> 是一种强大的工具,可以轻松管理异步任务,而无需担心线程管理和生命周期问题。本文将深入探讨 <code>IntentService</code> 的各个方面,包括基本用法、特点、生命周期、与其他服务的比较以及最佳实践等内容。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在移动应用开发中,经常需要在后台执行一些耗时任务,如下载文件、处理数据等。<code>IntentService</code> 是 Android 中的一个服务,专门用于简化这类任务的处理。它继承自 <code>Service</code> 类,并在单独的工作线程中执行任务,避免了多线程管理的复杂性。</p>
<h2 id="IntentService-的特点"><a href="#IntentService-的特点" class="headerlink" title="IntentService 的特点"></a>IntentService 的特点</h2><ol>
<li><p><strong>自动管理生命周期</strong></p>
<p><code>IntentService</code> 在完成所有任务后会自动停止,不需要手动调用 <code>stopService</code>。此外,它能够按顺序执行任务队列,确保任务的有序执行。</p>
</li>
<li><p><strong>单线程操作</strong></p>
<p><code>IntentService</code> 在单独的工作线程中执行任务,避免了多线程管理的复杂性。这使得它特别适用于需要按顺序执行的任务。</p>
</li>
</ol>
<h2 id="使用-IntentService"><a href="#使用-IntentService" class="headerlink" title="使用 IntentService"></a>使用 IntentService</h2><ol>
<li><p><strong>创建 IntentService</strong></p>
<p>创建一个 <code>IntentService</code> 需要继承该类,并实现构造函数和 <code>onHandleIntent</code> 方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyIntentService</span> <span class="keyword">extends</span> <span class="title">IntentService</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">MyIntentService</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(<span class="string">"MyIntentService"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onHandleIntent</span><span class="params">(@Nullable Intent intent)</span> </span>{</span><br><span class="line"> <span class="comment">// 在这里执行具体的后台任务</span></span><br><span class="line"> String data = intent.getStringExtra(<span class="string">"data"</span>);</span><br><span class="line"> <span class="comment">// 处理数据...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>启动 IntentService</strong></p>
<p>使用 <code>startService</code> 方法启动 <code>IntentService</code>,通过创建 <code>Intent</code> 对象来传递需要执行的任务。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 启动 IntentService 的示例代码</span></span><br><span class="line">Intent intent = <span class="keyword">new</span> Intent(context, MyIntentService<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">intent.putExtra(<span class="string">"data"</span>, <span class="string">"example_data"</span>);</span><br><span class="line">context.startService(intent);</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>任务处理</strong></p>
<p>在 <code>onHandleIntent</code> 方法中执行具体的耗时任务,通过 <code>Intent</code> 提取传递的数据。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onHandleIntent</span><span class="params">(@Nullable Intent intent)</span> </span>{</span><br><span class="line"> String data = intent.getStringExtra(<span class="string">"data"</span>);</span><br><span class="line"> <span class="comment">// 处理数据...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="IntentService-的生命周期"><a href="#IntentService-的生命周期" class="headerlink" title="IntentService 的生命周期"></a>IntentService 的生命周期</h2><ol>
<li><p><strong>创建和销毁</strong></p>
<p><code>IntentService</code> 在任务完成后自动停止,无需手动管理生命周期。在完成所有任务后,<code>IntentService</code> 会调用 <code>onDestroy</code> 方法。</p>
</li>
<li><p><strong>线程管理</strong></p>
<p>工作线程的创建和管理由 <code>IntentService</code> 自动处理,开发者无需担心多线程相关的细节。</p>
</li>
</ol>
<h2 id="IntentService-与其他服务的比较"><a href="#IntentService-与其他服务的比较" class="headerlink" title="IntentService 与其他服务的比较"></a>IntentService 与其他服务的比较</h2><ol>
<li><p><strong>与 Service 的比较</strong></p>
<p>相对于普通 <code>Service</code>,<code>IntentService</code> 更适用于一次性、有序执行的后台任务。普通 <code>Service</code> 需要手动管理线程和任务队列。</p>
</li>
<li><p><strong>与 AsyncTask 的比较</strong></p>
<p>与 <code>AsyncTask</code> 相比,<code>IntentService</code> 在执行异步任务时更为简便,且不容易导致内存泄漏。<code>AsyncTask</code> 在处理长时间运行的任务时需要额外的注意。</p>
</li>
</ol>
<h2 id="实例与示例代码"><a href="#实例与示例代码" class="headerlink" title="实例与示例代码"></a>实例与示例代码</h2><ol>
<li><p><strong>基本用法示例</strong></p>
<p>创建一个简单的 <code>IntentService</code> 示例,执行后台任务。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyIntentService</span> <span class="keyword">extends</span> <span class="title">IntentService</span> </span>{</span><br><span class="line"> <span class="comment">// 构造函数和onHandleIntent方法的实现...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>传递数据</strong></p>
<p>通过 <code>Intent</code> 传递数据给 <code>IntentService</code>。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Intent intent = <span class="keyword">new</span> Intent(context, MyIntentService<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">intent.putExtra(<span class="string">"data"</span>, <span class="string">"example_data"</span>);</span><br><span class="line">context.startService(intent);</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>通知界面更新</strong></p>
<p>使用广播或回调来通知界面任务的完成情况。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 示例代码:使用广播通知界面更新</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyIntentService</span> <span class="keyword">extends</span> <span class="title">IntentService</span> </span>{</span><br><span class="line"> <span class="comment">// onHandleIntent方法中任务完成后发送广播</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">notifyUI</span><span class="params">()</span> </span>{</span><br><span class="line"> Intent intent = <span class="keyword">new</span> Intent(<span class="string">"com.example.ACTION_TASK_COMPLETE"</span>);</span><br><span class="line"> LocalBroadcastManager.getInstance(<span class="keyword">this</span>).sendBroadcast(intent);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol>
<li><p><strong>长时间运行的任务</strong></p>
<p>长时间运行的任务可能导致 <code>IntentService</code> 被系统终止,需要注意处理这种情况。</p>
</li>
<li><p><strong>高版本替代品</strong></p>
<p>在Android8.0及以后<code>IntentService</code>不再推荐使用,高版本推荐使用<code>WorkManager</code>。</p>
</li>
</ol>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><code>IntentService</code>简化了后台任务的执行,提高了开发效率。其自动管理生命周期和线程,使得开发者能够更专注于业务逻辑的实现。通过本文的深入解析,相信读者能够更全面地了解并合理使用 <code>IntentService</code>。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<p>Android 应用开发中,执行后台任务是常见需&
深入Android多线程编程与性能优化
https://www.rousetime.com/2023/12/07/深入Android多线程编程与性能优化/
2023-12-07T02:29:27.000Z
2023-12-07T02:30:02.005Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在上一篇的入门篇中,我们对Android线程的基础概念和多线程编程模型有了初步了解。本篇将深入探讨多线程编程技术和性能优化策略,以提升应用的效率和响应性。</p>
<h2 id="高级多线程编程技术"><a href="#高级多线程编程技术" class="headerlink" title="高级多线程编程技术"></a>高级多线程编程技术</h2><h3 id="使用线程池管理线程"><a href="#使用线程池管理线程" class="headerlink" title="使用线程池管理线程"></a>使用线程池管理线程</h3><p>线程池是一组预先创建的线程,用于执行任务。通过使用线程池,可以避免不断创建和销毁线程的开销,提高线程的重用率,同时有效控制并发线程数量。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建固定大小的线程池</span></span><br><span class="line">ExecutorService executor = Executors.newFixedThreadPool(<span class="number">5</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 提交任务给线程池执行</span></span><br><span class="line">executor.submit(() -> {</span><br><span class="line"> <span class="comment">// 执行任务的代码</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<ul>
<li>通过<code>Executors.newFixedThreadPool</code>创建一个包含5个线程的固定大小线程池。</li>
<li><code>executor.submit</code>方法用于将任务提交给线程池执行。</li>
</ul>
<h3 id="使用Callable和Future获取任务结果"><a href="#使用Callable和Future获取任务结果" class="headerlink" title="使用Callable和Future获取任务结果"></a>使用Callable和Future获取任务结果</h3><p><code>Callable</code>接口允许线程返回结果,而<code>Future</code>接口允许主线程获取线程的执行结果。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Callable<String> callableTask = () -> {</span><br><span class="line"> <span class="comment">// 执行任务的代码</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Task completed"</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">Future<String> future = executor.submit(callableTask);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取任务结果</span></span><br><span class="line">String result = future.get();</span><br></pre></td></tr></table></figure>
<ul>
<li><code>Callable</code>接口相比<code>Runnable</code>接口,允许在执行完任务后返回一个结果。</li>
<li><code>executor.submit</code>返回一个<code>Future</code>对象,可以通过<code>get</code>方法获取任务执行的结果。</li>
</ul>
<h3 id="使用Lock和Condition进行更精细的同步控制"><a href="#使用Lock和Condition进行更精细的同步控制" class="headerlink" title="使用Lock和Condition进行更精细的同步控制"></a>使用Lock和Condition进行更精细的同步控制</h3><p>与<code>synchronized</code>关键字相比,<code>Lock</code>接口提供了更灵活的锁定机制,<code>Condition</code>接口允许线程等待特定条件满足后再继续执行。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">Lock lock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line">Condition condition = lock.newCondition();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在线程中使用锁</span></span><br><span class="line">lock.lock();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 一些需要同步的代码块</span></span><br><span class="line"> condition.await(); <span class="comment">// 等待条件满足</span></span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在另一个线程中发出信号</span></span><br><span class="line">lock.lock();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> condition.signal(); <span class="comment">// 发送信号</span></span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> lock.unlock();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li><code>ReentrantLock</code>是<code>Lock</code>接口的一种实现,提供了可重入的锁。</li>
<li><code>condition.await()</code>用于让线程等待,<code>condition.signal()</code>用于唤醒等待的线程。</li>
</ul>
<h2 id="并发数据结构"><a href="#并发数据结构" class="headerlink" title="并发数据结构"></a>并发数据结构</h2><p>并发数据结构是在多线程编程中至关重要的一部分。并发数据结构提供了在多线程环境下安全访问和修改数据的机制,以确保线程安全性和避免竞态条件。</p>
<p>以下是一些常见的并发数据结构及其应用</p>
<h3 id="ConcurrentHashMap"><a href="#ConcurrentHashMap" class="headerlink" title="ConcurrentHashMap"></a>ConcurrentHashMap</h3><p><code>ConcurrentHashMap</code> 是 <code>HashMap</code> 的线程安全版本,它允许在不同的部分上并发地读写数据,提高了并发性能。在多线程环境中,使用 <code>ConcurrentHashMap</code> 可以避免对整个数据结构的锁定,从而提高并发性。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">ConcurrentHashMap<String, Integer> concurrentMap = <span class="keyword">new</span> ConcurrentHashMap<>();</span><br><span class="line"></span><br><span class="line">concurrentMap.put(<span class="string">"key1"</span>, <span class="number">1</span>);</span><br><span class="line">concurrentMap.put(<span class="string">"key2"</span>, <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> value = concurrentMap.get(<span class="string">"key1"</span>);</span><br></pre></td></tr></table></figure>
<h3 id="CopyOnWriteArrayList"><a href="#CopyOnWriteArrayList" class="headerlink" title="CopyOnWriteArrayList"></a>CopyOnWriteArrayList</h3><p><code>CopyOnWriteArrayList</code> 是 <code>ArrayList</code> 的线程安全版本,它通过在修改操作时创建底层数组的副本来保证线程安全。它适用于读多写少的场景。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CopyOnWriteArrayList<String> copyOnWriteArrayList = <span class="keyword">new</span> CopyOnWriteArrayList<>();</span><br><span class="line"></span><br><span class="line">copyOnWriteArrayList.add(<span class="string">"Item1"</span>);</span><br><span class="line">copyOnWriteArrayList.add(<span class="string">"Item2"</span>);</span><br><span class="line"></span><br><span class="line">String item = copyOnWriteArrayList.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure>
<h3 id="BlockingQueue"><a href="#BlockingQueue" class="headerlink" title="BlockingQueue"></a>BlockingQueue</h3><p><code>BlockingQueue</code> 是一个阻塞队列,它提供了在队列为空或已满时阻塞线程的操作。这在生产者-消费者模型中特别有用。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">BlockingQueue<String> blockingQueue = <span class="keyword">new</span> LinkedBlockingQueue<>(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生产者线程</span></span><br><span class="line">blockingQueue.put(<span class="string">"Item"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 消费者线程</span></span><br><span class="line">String item = blockingQueue.take();</span><br></pre></td></tr></table></figure>
<h3 id="AtomicInteger"><a href="#AtomicInteger" class="headerlink" title="AtomicInteger"></a>AtomicInteger</h3><p><code>AtomicInteger</code> 是一个原子整数,它提供了一组原子操作,可确保在多线程环境中进行递增、递减等操作时的线程安全性。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">AtomicInteger atomicInteger = <span class="keyword">new</span> AtomicInteger(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> result = atomicInteger.incrementAndGet(); <span class="comment">// 原子递增操作</span></span><br></pre></td></tr></table></figure>
<h3 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch"></a>CountDownLatch</h3><p><code>CountDownLatch</code> 是一个同步工具类,它允许一个或多个线程等待其他线程完成操作。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程1</span></span><br><span class="line"><span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="comment">// 执行某些操作</span></span><br><span class="line"> latch.countDown();</span><br><span class="line">}).start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程2</span></span><br><span class="line"><span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="comment">// 执行某些操作</span></span><br><span class="line"> latch.countDown();</span><br><span class="line">}).start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 主线程等待两个线程完成</span></span><br><span class="line">latch.await();</span><br></pre></td></tr></table></figure>
<h3 id="CyclicBarrier"><a href="#CyclicBarrier" class="headerlink" title="CyclicBarrier"></a>CyclicBarrier</h3><p><code>CyclicBarrier</code> 是另一个同步工具类,它允许一组线程相互等待,直到所有线程都到达某个屏障点。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">CyclicBarrier cyclicBarrier = <span class="keyword">new</span> CyclicBarrier(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程1</span></span><br><span class="line"><span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="comment">// 执行某些操作</span></span><br><span class="line"> cyclicBarrier.await();</span><br><span class="line">}).start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程2</span></span><br><span class="line"><span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="comment">// 执行某些操作</span></span><br><span class="line"> cyclicBarrier.await();</span><br><span class="line">}).start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程3</span></span><br><span class="line"><span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="comment">// 执行某些操作</span></span><br><span class="line"> cyclicBarrier.await();</span><br><span class="line">}).start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 主线程等待所有线程到达屏障点</span></span><br><span class="line">cyclicBarrier.await();</span><br></pre></td></tr></table></figure>
<p>这些并发数据结构为多线程编程提供了有力的支持,但在使用它们时需要谨慎,以确保正确地处理并发访问和修改。在合适的场景下,合理使用这些数据结构可以提高程序的性能和并发度。</p>
<h2 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h2><p><code>UncaughtExceptionHandler</code> 接口可以用于捕获线程中未处理的异常。通过设置线程的 <code>UncaughtExceptionHandler</code>,可以在异常发生时执行自定义的处理逻辑。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UncaughtExceptionHandlerExample</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> <span class="comment">// 创建一个线程</span></span><br><span class="line"> Thread thread = <span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="comment">// 模拟发生异常</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"Simulated exception"</span>);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 设置线程的UncaughtExceptionHandler</span></span><br><span class="line"> thread.setUncaughtExceptionHandler((t, e) -></span><br><span class="line"> System.out.println(<span class="string">"Uncaught exception in thread "</span> + t.getName() + <span class="string">": "</span> + e.getMessage()));</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 启动线程</span></span><br><span class="line"> thread.start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个例子中,我们创建了一个线程,并设置了它的 <code>UncaughtExceptionHandler</code>。当线程中发生未捕获的异常时,<code>UncaughtExceptionHandler</code> 的 <code>uncaughtException</code> 方法将被调用,我们在这里简单地输出了异常信息。</p>
<p>请注意,这种机制对于捕获主线程的异常可能不太适用。对于主线程的异常处理,更好的方式是使用 <code>Thread.setDefaultUncaughtExceptionHandler</code> 来设置默认的异常处理器。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Thread.setDefaultUncaughtExceptionHandler((t, e) -></span><br><span class="line"> System.out.println(<span class="string">"Uncaught exception in thread "</span> + t.getName() + <span class="string">": "</span> + e.getMessage()));</span><br></pre></td></tr></table></figure>
<p>这将影响所有线程,除非它们显式地设置了自己的 <code>UncaughtExceptionHandler</code>。</p>
<h2 id="Android中的异步任务与HandlerThread"><a href="#Android中的异步任务与HandlerThread" class="headerlink" title="Android中的异步任务与HandlerThread"></a>Android中的异步任务与HandlerThread</h2><h3 id="AsyncTask的替代方案"><a href="#AsyncTask的替代方案" class="headerlink" title="AsyncTask的替代方案"></a>AsyncTask的替代方案</h3><p>由于<code>AsyncTask</code>已被废弃,推荐使用<code>Executor</code>和<code>Handler</code>的结合,或者使用Kotlin协程(在入门篇中已有介绍)来执行异步任务。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用Executor执行异步任务</span></span><br><span class="line">Executor executor = Executors.newSingleThreadExecutor();</span><br><span class="line"></span><br><span class="line">executor.execute(() -> {</span><br><span class="line"> <span class="comment">// 执行异步任务的代码</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<h3 id="HandlerThread的使用"><a href="#HandlerThread的使用" class="headerlink" title="HandlerThread的使用"></a>HandlerThread的使用</h3><p><code>HandlerThread</code>是Android中的一个辅助类,它封装了与Looper相关的操作,使得在后台线程中执行任务更为方便。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">HandlerThread handlerThread = <span class="keyword">new</span> HandlerThread(<span class="string">"MyHandlerThread"</span>);</span><br><span class="line">handlerThread.start();</span><br><span class="line"></span><br><span class="line">Handler handler = <span class="keyword">new</span> Handler(handlerThread.getLooper());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在后台线程执行任务</span></span><br><span class="line">handler.post(() -> {</span><br><span class="line"> <span class="comment">// 执行任务的代码</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<h2 id="性能优化策略"><a href="#性能优化策略" class="headerlink" title="性能优化策略"></a>性能优化策略</h2><h3 id="使用Systrace进行性能分析"><a href="#使用Systrace进行性能分析" class="headerlink" title="使用Systrace进行性能分析"></a>使用Systrace进行性能分析</h3><p>Systrace是Android系统提供的一种性能分析工具,可以用来检查应用中的性能瓶颈,找到耗时操作,优化线程执行时间。</p>
<ul>
<li>通过Android Studio中的Profiler工具,选择Systrace进行性能分析。</li>
</ul>
<h3 id="避免UI线程阻塞"><a href="#避免UI线程阻塞" class="headerlink" title="避免UI线程阻塞"></a>避免UI线程阻塞</h3><p>将耗时任务转移到后台线程执行,确保UI线程不会因为耗时操作而阻塞,提高应用的响应性。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用Handler将任务提交到后台线程执行</span></span><br><span class="line">Handler handler = <span class="keyword">new</span> Handler(Looper.getMainLooper());</span><br><span class="line"></span><br><span class="line">handler.post(() -> {</span><br><span class="line"> <span class="comment">// 执行耗时任务</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<h3 id="使用轻量级的同步机制"><a href="#使用轻量级的同步机制" class="headerlink" title="使用轻量级的同步机制"></a>使用轻量级的同步机制</h3><p>避免过多使用重量级的同步机制,如<code>synchronized</code>关键字,可以选择使用<code>java.util.concurrent</code>包中的更轻量级的机制,例如<code>ReentrantLock</code>。</p>
<h3 id="优化内存管理"><a href="#优化内存管理" class="headerlink" title="优化内存管理"></a>优化内存管理</h3><p>及时释放不再需要的对象,避免内存泄漏。可以使用工具如<code>LeakCanary</code>来帮助检测内存泄漏问题。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用WeakReference来持有对象的弱引用,有助于及时释放不再需要的对象</span></span><br><span class="line">WeakReference<MyObject> weakReference = <span class="keyword">new</span> WeakReference<>(myObject);</span><br><span class="line">myObject = <span class="keyword">null</span>; <span class="comment">// 释放强引用</span></span><br></pre></td></tr></table></figure>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>我们深入了解了Android中更高级的多线程编程技术,包括线程池的使用、锁与条件的灵活应用、并发数据结构以及一些性能优化的策略。希望读者能够在实际项目中灵活运用这些知识,构建高效稳定的Android应用。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Ӡ
Kotlin委托的深入解析与实践
https://www.rousetime.com/2023/11/16/Kotlin委托的深入解析与实践/
2023-11-16T01:58:19.000Z
2023-11-16T01:59:19.172Z
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Kotlin编程语言中,委托是一项强大的特性,它能够极大地简化代码,提高代码的可维护性。本文将深入探讨Kotlin中的委托机制,介绍其原理、具体使用方式以及实际应用场景。</p>
<h2 id="委托的原理"><a href="#委托的原理" class="headerlink" title="委托的原理"></a>委托的原理</h2><p>委托是一种通过将实际工作委托给其他对象来实现代码重用的机制。在Kotlin中,委托通过关键字 <code>by</code> 来实现。我们将首先了解委托的基本原理,为后续的实例打下基础。</p>
<h2 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h2><p>Kotlin中的类可以通过关键字 <code>by</code> 委托给其他类。考虑以下示例:</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">Printer</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">printMessage</span><span class="params">(message: <span class="type">String</span>)</span></span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ConsolePrinter</span> : <span class="type">Printer {</span></span></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">printMessage</span><span class="params">(message: <span class="type">String</span>)</span></span> {</span><br><span class="line"> println(message)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MessageProcessor</span></span>(printer: Printer) : Printer <span class="keyword">by</span> printer</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">val</span> consolePrinter = ConsolePrinter()</span><br><span class="line"> <span class="keyword">val</span> messageProcessor = MessageProcessor(consolePrinter)</span><br><span class="line"> </span><br><span class="line"> messageProcessor.printMessage(<span class="string">"Hello, AAA!"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上述例子中,<code>MessageProcessor</code> 类通过 <code>by</code> 关键字将实际的打印操作委托给了 <code>ConsolePrinter</code> 类。这种方式使得代码更具灵活性,我们可以轻松地切换不同的打印实现,而不需要修改 <code>MessageProcessor</code> 类的代码。</p>
<h2 id="具体使用示例"><a href="#具体使用示例" class="headerlink" title="具体使用示例"></a>具体使用示例</h2><p>接下来,我们将通过实际的示例来演示Kotlin委托的具体使用方式。我们将使用委托来实现属性的延迟初始化,这是委托的一个常见用法。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LazyProperty</span></span>(initializer: () -> <span class="built_in">Int</span>) {</span><br><span class="line"> <span class="keyword">val</span> lazyValue: <span class="built_in">Int</span> <span class="keyword">by</span> lazy(initializer)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">val</span> lazyProperty = LazyProperty {</span><br><span class="line"> println(<span class="string">"Initializing lazy property"</span>)</span><br><span class="line"> <span class="number">42</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> println(<span class="string">"Accessing lazy property for the first time: <span class="subst">${lazyProperty.lazyValue}</span>"</span>)</span><br><span class="line"> println(<span class="string">"Accessing lazy property for the second time: <span class="subst">${lazyProperty.lazyValue}</span>"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上述例子中,<code>LazyProperty</code> 类中的 <code>lazyValue</code> 属性通过 <code>lazy</code> 委托实现延迟初始化。只有在首次访问时,<code>initializer</code> 函数才会被调用,从而实现了懒加载的效果。</p>
<h2 id="委托的实际应用场景"><a href="#委托的实际应用场景" class="headerlink" title="委托的实际应用场景"></a>委托的实际应用场景</h2><p>Kotlin委托不仅可以用于简化代码结构,还可以在实际应用中发挥重要作用。考虑以下场景:我们需要为一个Android应用实现网络请求,我们可以使用委托来实现通用的网络请求逻辑,将业务代码与网络请求逻辑解耦。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">NetworkRequest</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">makeRequest</span><span class="params">(url: <span class="type">String</span>)</span></span>: String</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DefaultNetworkRequest</span> : <span class="type">NetworkRequest {</span></span></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">makeRequest</span><span class="params">(url: <span class="type">String</span>)</span></span>: String {</span><br><span class="line"> <span class="comment">// 实际的网络请求逻辑</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Response from <span class="variable">$url</span>"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DataManager</span></span>(networkRequest: NetworkRequest) : NetworkRequest <span class="keyword">by</span> networkRequest {</span><br><span class="line"> <span class="comment">// 业务逻辑代码</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">val</span> defaultNetworkRequest = DefaultNetworkRequest()</span><br><span class="line"> <span class="keyword">val</span> dataManager = DataManager(defaultNetworkRequest)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> response = dataManager.makeRequest(<span class="string">"https://example.com"</span>)</span><br><span class="line"> println(<span class="string">"Received response: <span class="variable">$response</span>"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个例子中,<code>DataManager</code> 类通过委托将网络请求的实际操作委托给了实现了 <code>NetworkRequest</code> 接口的类。这种结构使得我们可以轻松切换不同的网络请求实现,而不需要修改业务逻辑。</p>
<h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>通过本文的学习,我们深入了解了Kotlin中委托的原理和使用方式。委托不仅能够提高代码的可维护性,还能在实际应用中发挥重要作用。在实际开发中,合理利用委托将使我们的代码更加灵活、清晰,并提升整体的代码质量。</p>
<h2 id="推荐"><a href="#推荐" class="headerlink" title="推荐"></a>推荐</h2><p><a href="https://github.com/idisfkj/android-startup" target="_blank" rel="noopener">android_startup</a>: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。</p>
<p><a href="https://github.com/idisfkj/AwesomeGithub" target="_blank" rel="noopener">AwesomeGithub</a>: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。</p>
<p><a href="https://github.com/idisfkj/flutter_github" target="_blank" rel="noopener">flutter_github</a>: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。</p>
<p><a href="https://github.com/idisfkj/android-api-analysis" target="_blank" rel="noopener">android-api-analysis</a>: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。</p>
<p><a href="https://github.com/idisfkj/daily_algorithm" target="_blank" rel="noopener">daily_algorithm</a>: 每日一算法,由浅入深,欢迎加入一起共勉。</p>
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在Kotlin