差点就倒了 發表於 2025-10-30 14:55:00

Launcher 卡片框架多模块集成

<p>方案一、aar架包集成</p>
<p>最简单直接的方案,卡片侧实现,打成aar包提供到launcher显示</p>
<p>方案二、<span data-font-family="default">AppWidget</span></p>
<p><span data-font-family="default">原生的桌面小组件方案,被限制无法自定义view</span></p>
<p><span data-font-family="default">底层通过BroadcastReceiver实现</span></p>
<h4><span data-font-family="default">方案三、插件方案</span></h4>
<p><span data-font-family="default">插件方案有好几种,实现原理都是通过配置实现,其中有Service,BroadcastReceiver,</span>Plugin</p>
<p>在SystemUI模块中,状态栏等模块很多使用的都是Plugin方案跟Service方案</p>
<p>这里详细讲通过<strong>Service配置跟Plugin配置</strong>实现</p>
<p>插件方案可以实现卡片跟launcher解耦,并且可以自定义view,还支持跨进程交互</p>
<p>首先定义一个插件,用于配置卡片信息,exported 属性标识可以给其它应用读取</p>
<div class="cnblogs_code"><img id="code_img_closed_d2782106-80a0-40bb-9376-47b519a73a6f" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_d2782106-80a0-40bb-9376-47b519a73a6f" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_d2782106-80a0-40bb-9376-47b519a73a6f" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">service
            </span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">=".TestWidgetService"</span><span style="color: rgba(255, 0, 0, 1)">
            android:exported</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(255, 0, 0, 1)">
            android:label</span><span style="color: rgba(0, 0, 255, 1)">="测试卡片1"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
            <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">intent-filter</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
                <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">action </span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">="com.appwidget.action.rear.APPWIDGET_PLUGIN"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
            <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">intent-filter</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

            <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">meta-data
                </span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">="com.appwidget.provider"</span><span style="color: rgba(255, 0, 0, 1)">
                android:resource</span><span style="color: rgba(0, 0, 255, 1)">="@xml/remote_control_widget_info"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">service</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

      <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">service
            </span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">=".PagerWidgetPlugin"</span><span style="color: rgba(255, 0, 0, 1)">
            android:exported</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(255, 0, 0, 1)">
            android:label</span><span style="color: rgba(0, 0, 255, 1)">="测试卡片2"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
            <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">intent-filter</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
                <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">action </span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">="com.appwidget.action.rear.APPWIDGET_PLUGIN"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
            <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">intent-filter</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

            <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">meta-data
                </span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">="com.appwidget.provider"</span><span style="color: rgba(255, 0, 0, 1)">
                android:resource</span><span style="color: rgba(0, 0, 255, 1)">="@xml/pager_widget_info"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">service</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<div class="cnblogs_code"><img id="code_img_closed_ab157e25-0682-47bd-9210-f3e949ca9917" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_ab157e25-0682-47bd-9210-f3e949ca9917" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_ab157e25-0682-47bd-9210-f3e949ca9917" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.example.page

</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context

</span><span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> Plugin {

    fun onCreate(hostContext: Context, pluginContext: Context) {
    }

    fun onDestroy() {
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">class</span> PagerWidgetPlugin : Plugin</pre>
</div>
<span class="cnblogs_code_collapse">Plugin</span></div>
<div class="cnblogs_code"><img id="code_img_closed_a79b9b59-c536-414d-b260-045d9c00766c" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_a79b9b59-c536-414d-b260-045d9c00766c" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_a79b9b59-c536-414d-b260-045d9c00766c" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.example.page

</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.app.Service
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Intent
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.os.IBinder

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TestWidgetService : Service() {
    override fun onBind(intent: Intent</span>?): IBinder?<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">Service</span></div>
<p>上面插件是直接定义在卡片里,其实应该在launcher中,然后对所有的卡片提供基础aar,统一接口</p>
<p>然后在res/xml下面新建 widget_info.xml</p>
<div class="cnblogs_code"><img id="code_img_closed_b2afd8fb-7d28-4db6-8658-846107abccea" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_b2afd8fb-7d28-4db6-8658-846107abccea" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_b2afd8fb-7d28-4db6-8658-846107abccea" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com-appwidget-provider
    </span><span style="color: rgba(255, 0, 0, 1)">cardType</span><span style="color: rgba(0, 0, 255, 1)">="0"</span><span style="color: rgba(255, 0, 0, 1)">
    mediumLayout</span><span style="color: rgba(0, 0, 255, 1)">="@layout/pager_control_layout"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">pager_widget_info</span></div>
<div class="cnblogs_code"><img id="code_img_closed_75b1c9f9-bb87-47ad-9494-fa11a2c22b15" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_75b1c9f9-bb87-47ad-9494-fa11a2c22b15" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_75b1c9f9-bb87-47ad-9494-fa11a2c22b15" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com-appwidget-provider
    </span><span style="color: rgba(255, 0, 0, 1)">cardType</span><span style="color: rgba(0, 0, 255, 1)">="0"</span><span style="color: rgba(255, 0, 0, 1)">
    smallLayout</span><span style="color: rgba(0, 0, 255, 1)">="@layout/cards_remote_control_layout"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">remote_control_widget_info</span></div>
<p>编写卡片布局</p>
<div class="cnblogs_code"><img id="code_img_closed_b30b0499-abb1-4762-951f-d7ce30ba2fef" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_b30b0499-abb1-4762-951f-d7ce30ba2fef" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_b30b0499-abb1-4762-951f-d7ce30ba2fef" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">androidx.constraintlayout.widget.ConstraintLayout </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
    xmlns:app</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res-auto"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/white"</span><span style="color: rgba(255, 0, 0, 1)">
    android:focusable</span><span style="color: rgba(0, 0, 255, 1)">="false"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">ImageView
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_image"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">TextView
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_title"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_margin</span><span style="color: rgba(0, 0, 255, 1)">="32dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:drawableLeft</span><span style="color: rgba(0, 0, 255, 1)">="@mipmap/ic_launcher_round"</span><span style="color: rgba(255, 0, 0, 1)">
      android:drawablePadding</span><span style="color: rgba(0, 0, 255, 1)">="8dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:text</span><span style="color: rgba(0, 0, 255, 1)">="title"</span><span style="color: rgba(255, 0, 0, 1)">
      android:textColor</span><span style="color: rgba(0, 0, 255, 1)">="@android:color/holo_blue_dark"</span><span style="color: rgba(255, 0, 0, 1)">
      app:layout_constraintStart_toStartOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span><span style="color: rgba(255, 0, 0, 1)">
      app:layout_constraintTop_toTopOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">TextView
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_tips"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginBottom</span><span style="color: rgba(0, 0, 255, 1)">="4dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:ellipsize</span><span style="color: rgba(0, 0, 255, 1)">="end"</span><span style="color: rgba(255, 0, 0, 1)">
      android:maxWidth</span><span style="color: rgba(0, 0, 255, 1)">="390dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:singleLine</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(255, 0, 0, 1)">
      android:text</span><span style="color: rgba(0, 0, 255, 1)">="tips"</span><span style="color: rgba(255, 0, 0, 1)">
      android:textColor</span><span style="color: rgba(0, 0, 255, 1)">="@android:color/holo_orange_dark"</span><span style="color: rgba(255, 0, 0, 1)">
      app:layout_constraintBottom_toTopOf</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_summary"</span><span style="color: rgba(255, 0, 0, 1)">
      app:layout_constraintStart_toStartOf</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_summary"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">TextView
      </span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_summary"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginStart</span><span style="color: rgba(0, 0, 255, 1)">="32dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:layout_marginBottom</span><span style="color: rgba(0, 0, 255, 1)">="35dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:ellipsize</span><span style="color: rgba(0, 0, 255, 1)">="end"</span><span style="color: rgba(255, 0, 0, 1)">
      android:maxWidth</span><span style="color: rgba(0, 0, 255, 1)">="405dp"</span><span style="color: rgba(255, 0, 0, 1)">
      android:singleLine</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(255, 0, 0, 1)">
      android:text</span><span style="color: rgba(0, 0, 255, 1)">="content"</span><span style="color: rgba(255, 0, 0, 1)">
      android:textColor</span><span style="color: rgba(0, 0, 255, 1)">="@android:color/holo_blue_bright"</span><span style="color: rgba(255, 0, 0, 1)">
      app:layout_constraintBottom_toBottomOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span><span style="color: rgba(255, 0, 0, 1)">
      app:layout_constraintStart_toStartOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">androidx.constraintlayout.widget.ConstraintLayout</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">cards_remote_control_layout</span></div>
<div class="cnblogs_code"><img id="code_img_closed_15213ad1-52a8-4016-8850-a2fd4548673e" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_15213ad1-52a8-4016-8850-a2fd4548673e" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_15213ad1-52a8-4016-8850-a2fd4548673e" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">com.example.page.loop.CustomViewPager </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
    android:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/white"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span></pre>
</div>
<span class="cnblogs_code_collapse">pager_control_layout</span></div>
<p>然后在launcher中,使用 AppWidgetManager 来读取配置信息</p>
<div class="cnblogs_code"><img id="code_img_closed_5e7433a4-4778-4d50-aae9-90aa75c4e923" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_5e7433a4-4778-4d50-aae9-90aa75c4e923" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_5e7433a4-4778-4d50-aae9-90aa75c4e923" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.test.launcher.rear.card.appwidget

</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.annotation.SuppressLint
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.ComponentName
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Intent
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.pm.PackageManager
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.pm.ResolveInfo
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.util.Log
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.blankj.utilcode.util.GsonUtils
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.kunminx.architecture.ui.callback.UnPeekLiveData
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> org.xmlpull.v1.XmlPullParser
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> org.xmlpull.v1.XmlPullParserException
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.IOException

@SuppressLint(</span>"StaticFieldLeak"<span style="color: rgba(0, 0, 0, 1)">)
object AppWidgetManager {

    val context: Context </span>=<span style="color: rgba(0, 0, 0, 1)"> android.app.AppGlobals.getInitialApplication()

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">const</span> val ACTION = "com.appwidget.action.rear.APPWIDGET_PLUGIN"

    <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">const</span> val META_DATA_APPWIDGET_PROVIDER: String = "com.appwidget.provider"

    <span style="color: rgba(0, 0, 255, 1)">private</span> val list = mutableListOf&lt;CardModel&gt;<span style="color: rgba(0, 0, 0, 1)">()
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> var mAppWidgetChangeListener: ((MutableList&lt;CardModel&gt;) -&gt; Unit)? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
    val showOnCards </span>= UnPeekLiveData(mutableListOf&lt;CardModel&gt;<span style="color: rgba(0, 0, 0, 1)">())

    init {
      val intent </span>=<span style="color: rgba(0, 0, 0, 1)"> Intent(ACTION)
      val resolveInfoList </span>=<span style="color: rgba(0, 0, 0, 1)"> context.packageManager.queryIntentServices(
            intent,
            PackageManager.GET_META_DATA or PackageManager.GET_SHARED_LIBRARY_FILES
      )
      Logger.d(</span>"resolveInfoList size ${resolveInfoList.size}"<span style="color: rgba(0, 0, 0, 1)">)
      resolveInfoList.forEach { ri </span>-&gt;<span style="color: rgba(0, 0, 0, 1)">
            parseAppWidgetProviderInfo(ri)
      }
    }

    var id </span>= 0<span style="color: rgba(0, 0, 0, 1)">

    fun allocateAppWidgetId(): Int {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> ++<span style="color: rgba(0, 0, 0, 1)">id
    }

    fun setAppWidgetChangeListener(listener: ((MutableList</span>&lt;CardModel&gt;) -&gt; Unit)?<span style="color: rgba(0, 0, 0, 1)">) {
      mAppWidgetChangeListener </span>=<span style="color: rgba(0, 0, 0, 1)"> listener
    }


    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun parseAppWidgetProviderInfo(resolveInfo: ResolveInfo) {
      val componentName </span>=<span style="color: rgba(0, 0, 0, 1)">
            ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name)
      val serviceInfo </span>=<span style="color: rgba(0, 0, 0, 1)"> resolveInfo.serviceInfo

      val hasXmlDefinition </span>= serviceInfo.metaData?.getInt(META_DATA_APPWIDGET_PROVIDER) != 0

      <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (hasXmlDefinition) {
            val info </span>=<span style="color: rgba(0, 0, 0, 1)"> CardInfo()
            info.serviceInfo </span>=<span style="color: rgba(0, 0, 0, 1)"> serviceInfo
            info.componentName </span>=<span style="color: rgba(0, 0, 0, 1)"> componentName
            val pm </span>=<span style="color: rgba(0, 0, 0, 1)"> context.packageManager
            </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                serviceInfo.loadXmlMetaData(pm, META_DATA_APPWIDGET_PROVIDER).use { parser </span>-&gt;
                  <span style="color: rgba(0, 0, 255, 1)">if</span> (parser == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                        Logger.w(</span>"$componentName parser is null"<span style="color: rgba(0, 0, 0, 1)">)
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
                  }

                  val nodeName: String </span>=<span style="color: rgba(0, 0, 0, 1)"> parser.name
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> ("com-appwidget-provider" !=<span style="color: rgba(0, 0, 0, 1)"> nodeName) {
                        Logger.w(</span>"$componentName provider is null"<span style="color: rgba(0, 0, 0, 1)">)
                        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
                  }

                  info.descriptionRes </span>=<span style="color: rgba(0, 0, 0, 1)">
                        parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "description", 0<span style="color: rgba(0, 0, 0, 1)">)

                  info.mediumLayout </span>=<span style="color: rgba(0, 0, 0, 1)">
                        parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "mediumLayout", 0<span style="color: rgba(0, 0, 0, 1)">)
                  info.mediumPreviewImage </span>=<span style="color: rgba(0, 0, 0, 1)">
                        parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "mediumPreviewImage", 0<span style="color: rgba(0, 0, 0, 1)">)

                  info.smallLayout </span>=<span style="color: rgba(0, 0, 0, 1)">
                        parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "smallLayout", 0<span style="color: rgba(0, 0, 0, 1)">)
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (info.smallLayout != 0<span style="color: rgba(0, 0, 0, 1)">) {
                        info.sizeStyle </span>= 1<span style="color: rgba(0, 0, 0, 1)">
                  }
                  info.smallPreviewImage </span>=<span style="color: rgba(0, 0, 0, 1)">
                        parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "smallPreviewImage", 0<span style="color: rgba(0, 0, 0, 1)">)

                  info.bigLayout </span>=<span style="color: rgba(0, 0, 0, 1)">
                        parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "bigLayout", 0<span style="color: rgba(0, 0, 0, 1)">)
                  info.bigPreviewImage </span>=<span style="color: rgba(0, 0, 0, 1)">
                        parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "bigPreviewImage", 0<span style="color: rgba(0, 0, 0, 1)">)
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> (info.bigLayout != 0<span style="color: rgba(0, 0, 0, 1)">) {
                        info.sizeStyle </span>= 2<span style="color: rgba(0, 0, 0, 1)">
                  }
                  Logger.d(</span>"parseAppWidgetProviderInfo $componentName hasLayout=${info.hasLayout()}"<span style="color: rgba(0, 0, 0, 1)">)
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (info.hasLayout()) {
                        list.add(CardModel(allocateAppWidgetId(), info, </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">))
                  }
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
                }
            } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (e: IOException) {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Ok to catch Exception here, because anything going wrong because
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> of what a client process passes to us should not be fatal for the
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> system process.</span>
                Logger.e("XML parsing failed for AppWidget provider $componentName"<span style="color: rgba(0, 0, 0, 1)">, e)
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
            } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (e: PackageManager.NameNotFoundException) {
                Logger.e(</span>"XML parsing failed for AppWidget provider $componentName"<span style="color: rgba(0, 0, 0, 1)">, e)
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
            } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (e: XmlPullParserException) {
                Logger.e(</span>"XML parsing failed for AppWidget provider $componentName"<span style="color: rgba(0, 0, 0, 1)">, e)
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
            }
      }
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>也可以通过加载器获取</p>
<div class="cnblogs_code"><img id="code_img_closed_60906fc8-6cee-470c-9008-f13067af4f0f" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_60906fc8-6cee-470c-9008-f13067af4f0f" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_60906fc8-6cee-470c-9008-f13067af4f0f" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun parseAppWidgetProviderInfo(resolveInfo: ResolveInfo) {
      val componentName </span>=<span style="color: rgba(0, 0, 0, 1)">
            ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name)

      val serviceInfo </span>=<span style="color: rgba(0, 0, 0, 1)"> resolveInfo.serviceInfo
      val pluginContext </span>=<span style="color: rgba(0, 0, 0, 1)"> PluginContextWrapper.createFromPackage(serviceInfo.packageName)

      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            val cardPlugin </span>=<span style="color: rgba(0, 0, 0, 1)"> Class.forName(
                serviceInfo.name, </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">, pluginContext.classLoader
            ).newInstance() as CardPlugin

            cardPlugin.onCreate(context, pluginContext)
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (e: Exception) {
            Log.w(TAG, </span>"parseAppWidgetProviderInfo failed for AppWidget provider $componentName"<span style="color: rgba(0, 0, 0, 1)">, e)
      }
    }</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>因为处于不用apk,所以加载卡片类,需要加载其他路径的类文件,需要把这个类文件路径加到自己的classloader</p>
<div class="cnblogs_code"><img id="code_img_closed_a8b76f1d-b2c3-479d-b558-46cc2f0411f0" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_a8b76f1d-b2c3-479d-b558-46cc2f0411f0" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_a8b76f1d-b2c3-479d-b558-46cc2f0411f0" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.test.carlauncher.cards.plugin

</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.app.Application
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.ContextWrapper
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.text.TextUtils
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.LayoutInflater
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> dalvik.system.PathClassLoader
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.File

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> PluginContextWrapper(
    base: Context,
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val classLoader: ClassLoader =<span style="color: rgba(0, 0, 0, 1)"> ClassLoaderFilter(base.classLoader)
) : ContextWrapper(base) {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> val application: Application by lazy {
      PluginApplication(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> val mInflater: LayoutInflater by lazy {
      LayoutInflater.from(baseContext).cloneInContext(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
    }

    override fun getClassLoader(): ClassLoader {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> classLoader
    }

    override fun getApplicationContext(): Context {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> application
    }

    override fun getSystemService(name: String): Any {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (LAYOUT_INFLATER_SERVICE ==<span style="color: rgba(0, 0, 0, 1)"> name) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mInflater
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> baseContext.getSystemService(name)
    }


    override fun toString(): String {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> "${javaClass.name}@${Integer.toHexString(hashCode())}_$packageName"<span style="color: rgba(0, 0, 0, 1)">
    }


    companion object {
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> val contextMap = mutableMapOf&lt;String, Context&gt;<span style="color: rgba(0, 0, 0, 1)">()

      </span><span style="color: rgba(0, 0, 255, 1)">private</span> val methodSetOuterContext = Class.forName("android.app.ContextImpl"<span style="color: rgba(0, 0, 0, 1)">)
            .getDeclaredMethod(</span>"setOuterContext", Context::<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">.java).apply {
                isAccessible </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
            }

      </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun Context.setOuterContext(outContext: Context) {
            methodSetOuterContext.invoke(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">, outContext)
      }

      fun createFromPackage(packageName: String): Context {
            val contextCache </span>=<span style="color: rgba(0, 0, 0, 1)"> contextMap.get(packageName)
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (contextCache != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> contextCache
            }
            val hostContext: Context </span>=<span style="color: rgba(0, 0, 0, 1)"> android.app.AppGlobals.getInitialApplication()
            val appInfo </span>= hostContext.packageManager.getApplicationInfo(packageName, 0<span style="color: rgba(0, 0, 0, 1)">)
            val appContext: Context </span>=<span style="color: rgba(0, 0, 0, 1)"> hostContext.createApplicationContext(
                appInfo,
                CONTEXT_INCLUDE_CODE or CONTEXT_IGNORE_SECURITY
            )

            val zipPaths </span>= mutableListOf&lt;String&gt;<span style="color: rgba(0, 0, 0, 1)">()
            val libPaths </span>= mutableListOf&lt;String&gt;<span style="color: rgba(0, 0, 0, 1)">()
            android.app.LoadedApk.makePaths(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">, appInfo, zipPaths, libPaths);
            val classLoader </span>=<span style="color: rgba(0, 0, 0, 1)"> PathClassLoader(
                TextUtils.join(File.pathSeparator, zipPaths),
                TextUtils.join(File.pathSeparator, libPaths),
                ClassLoaderFilter(hostContext.classLoader)
            )

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册广播、绑定服务、startActivity会使用OuterContext
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> (appContext as android.app.ContextImpl).setOuterContext(context)</span>
<span style="color: rgba(0, 0, 0, 1)">            appContext.setOuterContext(hostContext)

            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> PluginContextWrapper(appContext, classLoader).also {
                contextMap.put(packageName, it)
            }
      }
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<div class="cnblogs_code"><img id="code_img_closed_c36bc6ac-6318-47eb-8d5d-38533a1f62e9" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_c36bc6ac-6318-47eb-8d5d-38533a1f62e9" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_c36bc6ac-6318-47eb-8d5d-38533a1f62e9" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ClassLoaderFilter(
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> val mBase: ClassLoader,
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val mPackages: Array&lt;String&gt;<span style="color: rgba(0, 0, 0, 1)">
) : ClassLoader(getSystemClassLoader()) {


    @Throws(ClassNotFoundException::</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">)
    override fun loadClass(name: String, resolve: Boolean): Class</span>&lt;*&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (pkg in mPackages) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (name.startsWith(pkg)) {
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mBase.loadClass(name)
            }
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.loadClass(name, resolve)
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<div class="cnblogs_code"><img id="code_img_closed_347f800a-187c-4e07-85ab-d7a5ebb1e0ee" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_347f800a-187c-4e07-85ab-d7a5ebb1e0ee" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_347f800a-187c-4e07-85ab-d7a5ebb1e0ee" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> PluginApplication(context: Context) : Application() {

    init {
      attachBaseContext(context)
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>获取到卡片的context跟classloader后,传入到&nbsp;PluginContextWrapper 中,用于后续卡片内加载布局</p>
<p>通过PathClassLoader构建的类加载器包含了插件APK的路径,当调用LayoutInflater.inflate()时,系统会通过getClassLoader()获取这个自定义加载器来实例化插件中的自定义View类</p>
<p>类中重写了&nbsp;getSystemService(),返回自定义的LayoutInflater,这个inflater绑定了插件的Context,确保资源解析的正确性</p>
<p>setOuterContext()将宿主Context设置为OuterContext,这样在插件中启动Activity、注册广播等操作时,系统会使用宿主环境来执行这些跨进程操作</p>
<p>上面操作确保插件中的类加载、资源访问和组件交互都能在正确的环境中执行</p>
<div><span style="color: rgba(51, 51, 51, 1); font-family: &quot;PingFang SC&quot;, Arial, sans-serif; font-size: 16px">接下来将卡片布局加载到统一的容器中,在容器内加载布局启动activity等操作都使用的卡片context</span></div>
<div>
<div class="cnblogs_code"><img id="code_img_closed_0dc12b15-80b5-4358-a4c0-467c7a591606" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_0dc12b15-80b5-4358-a4c0-467c7a591606" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_0dc12b15-80b5-4358-a4c0-467c7a591606" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.test.launcher.rear.card.appwidget

</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Color
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.util.AttributeSet
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.Display
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.Gravity
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.KeyEvent
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.LayoutInflater
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.MotionEvent
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.View
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.ViewGroup
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.widget.FrameLayout
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.widget.TextView
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.view.children

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CardHostView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
) : FrameLayout(context, attrs) {
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> lateinit var contentView: View
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> var decoratorView: View? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
    var cardInfo: CardInfo</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">

    var initialLayout </span>= 0<span style="color: rgba(0, 0, 0, 1)">
      set(value) {
            field </span>=<span style="color: rgba(0, 0, 0, 1)"> value
            apply()
      }

    fun apply() {
      contentView </span>=<span style="color: rgba(0, 0, 0, 1)"> getDefaultView()
      removeAllViews()
      contentView.setCorner(getDimen(baseDimen.baseapp_auto_dp_32).toFloat())
      addView(contentView, LayoutParams(</span>-1, -1<span style="color: rgba(0, 0, 0, 1)">))
    }

    fun getDefaultView(): View {
      var defaultView: View</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span>
      <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            val layoutId: Int </span>=<span style="color: rgba(0, 0, 0, 1)"> initialLayout
            defaultView </span>= LayoutInflater.from(context).inflate(layoutId, <span style="color: rgba(0, 0, 255, 1)">this</span>, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
            setOnClickListener {
                defaultView</span>?<span style="color: rgba(0, 0, 0, 1)">.callOnClick()
            }
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (exception: RuntimeException) {
            Logger.e(</span>"Error inflating AppWidget $cardInfo"<span style="color: rgba(0, 0, 0, 1)">, exception)
      }

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (defaultView == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            Logger.w(</span>"getDefaultView couldn't find any view, so inflating error"<span style="color: rgba(0, 0, 0, 1)">)
            defaultView </span>=<span style="color: rgba(0, 0, 0, 1)"> getErrorView()
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> defaultView
    }

    override fun dispatchKeyEvent(event: KeyEvent</span>?<span style="color: rgba(0, 0, 0, 1)">): Boolean {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> !(parentView()?.inEditeMode ?: <span style="color: rgba(0, 0, 255, 1)">false</span>) &amp;&amp; <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.dispatchKeyEvent(event)
    }

    override fun dispatchTouchEvent(ev: MotionEvent</span>?<span style="color: rgba(0, 0, 0, 1)">): Boolean {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> !(parentView()?.inEditeMode ?: <span style="color: rgba(0, 0, 255, 1)">false</span>) &amp;&amp; <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.dispatchTouchEvent(ev)
    }


    fun exitEditeMode() {
      decoratorView</span>?<span style="color: rgba(0, 0, 0, 1)">.let {
            removeView(it)
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun getErrorView(): View {
      val tv </span>=<span style="color: rgba(0, 0, 0, 1)"> TextView(context)
      tv.gravity </span>=<span style="color: rgba(0, 0, 0, 1)"> Gravity.CENTER
      tv.setText(com.android.internal.R.string.gadget_host_error_inflating)
      tv.setBackgroundColor(Color.argb(</span>127, 0, 0, 0<span style="color: rgba(0, 0, 0, 1)">))
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> tv
    }

    fun getContentView(): View {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> contentView
    }

    override fun onAttachedToWindow() {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onAttachedToWindow()
      Logger.d(</span>"${contentView::class.java.name}#${contentView.hashCode()} onAttachedToWindow"<span style="color: rgba(0, 0, 0, 1)">)
    }

    override fun onDetachedFromWindow() {
      </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onDetachedFromWindow()
      Logger.d(</span>"${contentView::class.java.name}#${contentView.hashCode()} onDetachedFromWindow"<span style="color: rgba(0, 0, 0, 1)">)
    }


    fun View.parentView() </span>= parent?.parent as?<span style="color: rgba(0, 0, 0, 1)"> FocusLimitRecycleView

    companion object {
      fun obtain(context: Context, card: CardModel): CardHostView {
            val packageName </span>=<span style="color: rgba(0, 0, 0, 1)"> card.info.componentName.packageName
            val pluginContext </span>=
                <span style="color: rgba(0, 0, 255, 1)">if</span> (packageName == context.packageName) context <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
                  PluginContextWrapper.createFromPackage(packageName, context.display)
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> CardHostView(pluginContext).also {
                it.id </span>=<span style="color: rgba(0, 0, 0, 1)"> View.generateViewId()
                it.isFocusable </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                it.cardInfo </span>=<span style="color: rgba(0, 0, 0, 1)"> card.info
                it.initialLayout </span>=<span style="color: rgba(0, 0, 0, 1)"> when (card.info.sizeStyle) {
                  </span>1 -&gt;<span style="color: rgba(0, 0, 0, 1)"> card.info.smallLayout
                  </span>3 -&gt;<span style="color: rgba(0, 0, 0, 1)"> card.info.bigLayout
                  </span><span style="color: rgba(0, 0, 255, 1)">else</span> -&gt;<span style="color: rgba(0, 0, 0, 1)"> card.info.mediumLayout
                }
            }
      }
    }

    open fun updateChildState(it: Boolean, recyclerView: FocusLimitRecycleView) {
      val inTouchMode </span>=<span style="color: rgba(0, 0, 0, 1)"> recyclerView.isInTouchMode
      val hasFocus </span>=<span style="color: rgba(0, 0, 0, 1)"> recyclerView.hasFocus()
      val parent </span>= parent as?<span style="color: rgba(0, 0, 0, 1)"> ViewGroup
      Logger.d(</span>"parent isInTouchMode $inTouchMode $hasFocus"<span style="color: rgba(0, 0, 0, 1)">)
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (it) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (hasFocus &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">inTouchMode) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (recyclerView.getEditeChild() == parent?<span style="color: rgba(0, 0, 0, 1)">.tag) {
                  parent</span>?.descendantFocusability =<span style="color: rgba(0, 0, 0, 1)"> FOCUS_BLOCK_DESCENDANTS
                  getContentView().alpha </span>=<span style="color: rgba(0, 0, 0, 1)"> 1f
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                  parent</span>?.descendantFocusability =<span style="color: rgba(0, 0, 0, 1)"> FOCUS_AFTER_DESCENDANTS
                  getContentView().alpha </span>= 0.4f<span style="color: rgba(0, 0, 0, 1)">
                }
            }
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            getContentView().alpha </span>=<span style="color: rgba(0, 0, 0, 1)"> 1f
            parent</span>?<span style="color: rgba(0, 0, 0, 1)">.visible()
      }
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>在launcher中直接&nbsp;CardHostView.obtain(mBinding.root.context,it) 创建卡片显示在桌面&nbsp;</p>
</div><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/19174303
頁: [1]
查看完整版本: Launcher 卡片框架多模块集成