你或许用过一些 Android APP 的小组件,比方:

  • 支付宝的小组件:之前疫情期间增加了对应小组件卡片在桌面,可点击小卡片上的检查健康码的按钮,可一键翻开健康码。
  • 音乐APP的小组件:增加对应对应小组件后, 可在 APP 的主屏幕中轻松看到当时播放歌曲的相关信息:歌曲封面、歌曲名、歌手名称、所属专辑名称等。
  • 时钟类 APP 的小组件:可增加各种款式的时钟小组件在屏幕,装饰你的主屏幕,在你喜爱的小组件上来检查时刻。
  • 气候类 APP 的小组件:可在主屏幕直接看到气候相关信息,不用再翻开气候的 APP

假如你所开发的项目的产品司理并没有相关开发需求,或许很多 Android 开发并没有触摸过相关桌面主屏幕的相关小组件开发,这篇文章首要介绍下相关开发的知识点。

AppWidgetProvider:
协助完成 AppWidget 供给程序的便利类。 你能够用 AppWidgetProvider 做的事情,你也能够用一个普通的 BroadcastReceiver 做。 AppWidgetProvider 仅仅从 onReceive(Context,Intent) 中接纳到的 Intent 中解分出相关字段,并运用接纳到的 extra 调用 hook 办法。
扩展此类并掩盖 onUpdateonDeletedonEnabledonDisabled 办法中的一个或多个以完成您自己的 AppWidget 功用。

public class WidgetDemoProvider extends AppWidgetProvider {
    /**
     * 当 Widget 第一次被增加时调用,例如用户增加了两个你的 Widget,那么只要在增加第一个 Widget 时该  
     * 办法会被调用。所以该办法比较合适履行你一切 Widgets 只需进行一次的操作。对用播送的 Action 为 
     * ACTION_APPWIDGET_ENABLE。
     */
    @Override
    public void onEnabled(Context context) {
        super.onEnabled(context);
    }
    /**
     * 与 onEnabled 恰好相反,当你的最终一个 Widget 被删除时调用该办法,所以这儿用来清理之前在 onEnabled() 中进行的操作。
     * 当最终一个该类型的小部件从桌面移除时调用,对应的播送的 Action 为 ACTION_APPWIDGET_DISABLED。
     *
     */
    public void onDisabled(Context context) {
        super.onDisabled(context);
    }
    /**
     * 当 Widget 第一次被增加或许巨细产生改变时调用该办法,能够在此操控 Widget 元素的显现和躲藏。
     * 当小部件布局产生更改的时分调用。对应播送的 Action 为 ACTION_APPWIDGET_OPTIONS_CHANGED。
     *
     * @param appWidgetManager 您能够调用 AppWidgetManager.updateAppWidget 的 AppWidgetManager 目标。
     * @param appWidgetId 巨细改动的widget的appWidgetId。
     * @param newOptions
     */
    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    }
    /**
     * 当小部件从备份中复原,或许康复设置的时分,会调用,实际用的比较少。对应播送的 Action 为 ACTION_APPWIDGET_RESTORED。
     * @param oldWidgetIds
     * @param newWidgetIds
     */
    @Override
    public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
        super.onRestored(context, oldWidgetIds, newWidgetIds);
    }
    /**
     * AppWidget 更新事情
     *
     * 小部件被增加时或许每次小部件更新时都会调用一次该办法,装备文件中装备小部件的更新周期 updatePeriodMillis,每次更新都会调用。对应播送 Action 为:ACTION_APPWIDGET_UPDATE 和 ACTION_APPWIDGET_RESTORED
     *
     * @param appWidgetManager 更新 AppWidget 状况; 获取有关已安装 AppWidget 供给程序和其他 AppWidget 相关状况的信息。
     * @param appWidgetIds 需求更新的 appWidgetIds。 请留意,这或许是此供给程序的一切 AppWidget 实例,也或许仅仅其间的一个子集。
     */
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
    }
    /**
     * 当 Widget 被删除时调用该办法。
     *
     * 每删除一个小部件就调用一次。对应的播送的 Action 为: ACTION_APPWIDGET_DELETED 。
     *
     * @param appWidgetIds 已从其组件集群中删除的 appWidgetIds。
     */
    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        super.onDeleted(context, appWidgetIds);
    }
    /**
     *  接纳播送的回调函数
     * onReceive() 中处理的是 Widget 相关的播送事情,然后分发到各个回调函数中onUpdate(), onDeleted(), onEnabled(), onDisabled, onAppWidgetOptionsChanged()。
     *
     * @param context
     * @param intent
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
            this.onEnabled(context);
        }
        else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
            this.onDisabled(context);
        }
        ...省掉
        super.onReceive(context, intent);
    }
}

依据自己的事务,写好 WidgetDemoProvider 的自界说代码后,需求在 AndroidManifest.xml 里注册一下:

      <receiver android:name=".widget.WidgetDemoProvide"
            android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="xxx..."/>  <!-- 此处能够增加自己需求的 -->
            </intent-filter>
            <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/appwidget_demo" /> <!-- 此处能够增加自己需求的给用户提前预览的自界说小组件布局 -->
        </receiver>

<receiver> 元素需求 android:name 特点,该特点指定小部件运用的 AppWidgetProviderAppWidgetProvider 的父类便是 BroadcastReceiver)。
<intent-filter> 中的 <action> 元素指定小部件接受 ACTION_APPWIDGET_UPDATE 播送。这是有必要明确声明的唯一一项播送,用以接纳小部件的增删改等信息。
<meta-data> 元素指定小部件的资源,而且需求以下特点:
android:name – 指定元数据名称。有必要运用 android.appwidget.provider 将数据标识为 AppWidgetProviderInfo 描述符。
android:resource – 指定 AppWidgetProviderInfo 资源方位。

appwidget_demo.xml 的示例代码:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="180dp"
    android:minHeight="180dp"
    android:updatePeriodMillis="1800000"
    android:previewImage="@mipmap/app_widget_preview_3x3" 
    android:initialLayout="@layout/app_widget_preview_layout_3_3"
    android:resizeMode="horizontal|vertical"> 
</appwidget-provider>

minWidthminHeight :指定小部件默许情况下占用的最小空间。
留意:为使小部件能够在设备间移植,小部件的最小巨细不得超过 4 x 4 单元格。
minResizeWidthminResizeHeight:指定小部件的肯定最小巨细。
updatePeriodMillis:界说小部件结构经过调用 onUpdate() 回调办法来从 AppWidgetProvider 恳求更新的频率应该是多大。
initialLayout:指向用于界说小部件布局的布局资源。
configure:界说要在用户增加小部件时发动以便用户装备小部件特点的 Activity。
previewImage:指定预览来描绘小部件经过装备后是什么样子的,用户在挑选小部件时会看到该预览。
autoAdvanceViewId :指定应由小部件的保管应用自动跳转的小部件子视图的视图 ID。
resizeMode :指定能够按什么规则来调整微件的巨细,可选值为“horizontal|vertical”,一般默许设置横竖都能够进行调整。
minResizeHeight :指定可将微件巨细调整到的最小高度。
minResizeWidth:指定可将微件巨细调整到的最小宽度。
widgetCategory:声明小部件是否能够显现在主屏幕 (home_screen) 或确定屏幕 (keyguard) 上。只要低于 5.0 的 Android 版别才支撑确定屏幕微件。关于 Android 5.0 及更高版别,只要 home_screen 有用,所以现在将这个值写为 home_screen 即可。

假如在自己的相关事务代码里,比方 activity 里如何触发 WidgetDemoProvider 相关数据以及页面更新。能够看以下示例代码:

  private void sendNotify(){
    try {
      Class javaClass = Class.forName("xxx.WidgetDemoProvider");
      final Intent intent = new Intent(this, javaClass);
      intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
      int[] ids = AppWidgetManager.getInstance(GlobeContext.context).getAppWidgetIds(new ComponentName(GlobeContext.context, javaClass));
      intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
      sendBroadcast(intent);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }

一些开发留意事项:

  • 小组件的宽高是能够支撑用户自行调整的,只需简略的设置最低宽高,可是可调整的最小粒度是依据手机icon 为规范。小组件数量无约束,用户对小组件的巨细和详细功用喜爱都不太一样,所以解决方案就简略粗犷一点,你能想到的适配尺度,每种尺度搞一个,用户自己挑选合适的尺度就好。大、中、小、大中、中小、细小、超大等尺度,能够全部做一遍。
android:resizeMode="horizontal|vertical"
widget 能够被拉伸的方向。horizontal表明能够水平拉伸,vertical表明能够竖直拉伸
  • 更新时刻分为自动更新和守时更新。
    自动更新:即在 APP 中能够动态更新这个桌面小组件,这种情况更新没有时刻约束。
    守时更新:小组件需求展现的数据或许现已产生了改变,可是 APP 现已被体系杀死了,无法自动更新数据,就会导致小组件展现的数据或许是已过期的或许是旧的,这时分就能够用到小组件的守时更新功用,可是这个守时更新有一个约束,根据省电逻辑,最快的更新周期为 30 分钟。(假如是在 onUpdate 办法中写个守时器守时更新,这样是不可的,会被体系给杀死,杀死之后小组件不会消失,而是一向显现最终一次更新时分的状况,直到下一次更新数据,类似于电子水墨屏的逻辑。)
  • 一般点击整个小组件,我们直接调起 APP。点击跳转页面需求用到 PendingIntent,这玩意的 Flag 有很多种形式,详细能够检查文章底部的参考文档,坑就坑在这个 Flag,31 之后的体系有改动,会报错,所以 31 的体系需求用 PendingIntent.FLAG_IMMUTABLE,详细看代码。
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
            this.onEnabled(context);
        }
        else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
            this.onDisabled(context);
        }
        if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
            Uri data = intent.getData();
            int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
            switch (buttonId) {
                case R.id.widget_layout:
                   Intent intent = new Intent(context, RemotePlayerActivity.class);
                   intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                   context.startActivity(goIntent);
                   RemoteViews remoteView = new RemoteViews(context.getPackageName(),R.layout.app_widget_layout);
                   //将按钮与点击事情绑定
                   remoteView.setOnClickPendingIntent(R.id.widget_layout,getPendingIntent(context, R.id.widget_layout));
                   break;
            }
        }
        super.onReceive(context, intent);
    }
    private PendingIntent getPendingIntent(Context context, int buttonId) {
        Intent intent = new Intent();
        intent.setClass(context, WidgetDemoProvider.class);
        intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
        intent.setData(Uri.parse("harvic:" + buttonId));
        PendingIntent pendingIntent;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
        } else {
            pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
        }
        return pendingIntent;
    }
  • 经过 PendingIntent 就能够直接调起 APP 的相关页面,不过这儿也有坑,假定你 APP 的发动页面是 MainActivity 页面,点击小组件你就让它跳转到 MainActivity 页面走正常的 APP 发动流程,就等同于是点击小组件就能发动 APP,哪怕 APP 被杀死了。坑就坑在于,经过这种方式翻开的 APP,它不会走 Application 类,也便是你假如是在 Application初始化了某些东西,可是 APP 现已被体系杀死了,这时分你再点击小组件发动 APP,这时就会发现好多组件用不了,由于没初始化。
    所以相对省事的做法便是把 Application 的一切需求初始化的东西都放 MainActivity 里面初始化了,现在为了工信部隐私相关合规,应该很多 APP 的初始化代码应该现已从 Application 放到用户点同意隐私协议弹框后再去初始化了。

RemoteViews,从字面意思理解为它是一个远程视图。是一种远程的 View,它在其它进程中显现,却能够在另一个进程中更新。RemoteViewsAndroid 中的运用场景首要有:自界说通知栏和桌面小部件。

RemoteViewsService,是办理 RemoteViews 的服务。一般,当 AppWidget 中包含 GridViewListViewStackView 等调集视图时,才需求运用 RemoteViewsService 来进行更新、办理。RemoteViewsService 更新调集视图的一般过程是:经过 setRemoteAdapter() 办法来设置 RemoteViews对应 RemoteViewsService
之后在 RemoteViewsService 中,完成 RemoteViewsFactory 接口。然后,在 RemoteViewsFactory 接口中对调集视图的各个子项进行设置,例如 ListView 中的每一 Item

RemoteViewsFactory 经过 RemoteViewsService 中的介绍,我们知道 RemoteViewsService 是经过
RemoteViewsFactory 来详细办理 layout 中调集视图的,RemoteViewsFactoryRemoteViewsService 中的一个内部接口。RemoteViewsFactory 供给了一系列的办法办理调集视图中的每一项。
例如:
public RemoteViews getViewAt(int position):
经过getViewAt()来获取“调集视图”中的第position项的视图,视图是以RemoteViews的目标返回的。
public int getCount() :
经过getCount()来获取“调集视图”中一切子项的总数。

  • **用户可从头设置原有 widget。**在 Android 12 之前,从头设置 widget 意味着用户有必要删除现有 widget,然后运用新装备从头增加。Android 12 在多个方面改进了 widget 的装备方式,然后协助用户采用更简略的方式对 widget 进行个性化装备。可重组的 widget 答应用户对 widget 进行自界说设置。在 Android 12 中,用户将无需经过删除和从头增加 widget 来调整这些原有设定。
    要运用这一功用,您需在 appwidget-provider 中把 widgetFeatures 特点设置为 reconfigurable
    当用户装备该 widget 时,新的装备会被记录在 ListWidgetConfigureActivity 中。
    假如您的 widget 依靠默许设置,在 Android 12 中您可跳过初始化操作,经过默许装备来设置 widget
<appwidget-provider
   android:configure="com.example.android.appwidget.ListWidgetConfigureActivity"
   android:widgetFeatures="reconfigurable|configuration_optional"
   ... />
  • Android 12 中 Widget 的尺度约束改进。除了现有的 minWidthminHeighminResizeWidth 以及 minResizeHeight 以外,Android 12 还增加了新的 appwidget-provider 特点。您能够运用新的 maxResizeWidthmaxResizeHeight 特点,来界说用户所能够调整的 widget 尺度的最大高度和宽度。新的 targetCellWidthtargetCellHeight 特点能够界说设备主屏幕上的 widget 默许尺度。假如之前有 targetCellWidthtargetCellHeight 特点的话,小部件也不至于像现在这么乱而导致用户不想运用。
<appwidget-provider
   android:maxResizeWidth="240dp"
   android:maxResizeHeight="180dp"
   android:minWidth="180dp"
   android:minHeight="110dp"
   android:minResizeWidth="180dp"
   android:minResizeHeight="110dp"
   android:targetCellWidth="3"
   android:targetCellHeight="2"
   ... />
<!-- maxResizeWidth:界说用户所能够调整的小部件尺度的最大宽度
maxResizeHeight:界说用户所能够调整的小部件尺度的最大高度
targetCellWidth:界说设备主屏幕上的小部件默许宽度所占格数(即使不同型号的手机中也会占界说好的格数,但手机体系版别有必要在 Android 12 及以上)
targetCellHeight:界说设备主屏幕上的小部件默许高度所占格数 -->
  • 新的小部件控件。Android 12 运用以下现有控件新增了对有状况行为的支撑:CheckBoxSwitch
    RadioButton,上面这几个控件大家应该非常熟悉了,但在 Android 12 之前在小部件中想要运用的话也是不或许的。

  • Android 12 以上能够经过 system_app_widget_background_radiussystem_app_widget_inner_radius 体系参数来设置微件圆角的半径。

重视大众号:Android老皮!!!欢迎大家来找我探讨交流