小米MIUI 小部件系统能力说明
1.调起 MIUI Widget 商店里的详情页,添加应用的 MIUI Widget
1.1.描述
启动 Widget 详情页,详情页会包含该 App 通过审核的Widget。用户可以左右滑动预览,并选择其中一个添加到桌面。
1.2.调用方式
关键方法
appWidgetManager.requestPinAppWidget(myProvider, extras, null)
使用 extras 携带参数
addType: appWidgetDetail
widgetName: 小部件name,可选参数,用来指定打开详情页后定位到的组件。如果不填,默认定位到第一个。
widgetExtraData: 用于携带自定义参数,携带自定义参数类型只能是String,最多携带5个,超过5个所有携带的自定义参数都将被抛弃。
注意:该方法仅支持 Android 8.0 及以上系统。不支持 MIUI Widget 的手机调用requestPinAppWidget 方法不会调起 Widget 商店里的详情页。
1.3.示例
public void startAppWidgetPage(Context context){
AppWidgetManager appWidgetManager =
AppWidgetManager.getInstance(context);
ComponentName myProvider = new ComponentName(this,
ExampleWidgetProvider.class);
if (appWidgetManager.isRequestPinAppWidgetSupported()) {
Bundle extras = new Bundle();
extras.putString("addType","appWidgetDetail");
// packageName 为应用真实包名
extras.putString("widgetName",
"packageName/com.miui.ExampleWidgetProvider");
Bundle dataBundle = new Bundle();
dataBundle.putString("dataKey1","data1xxx");
dataBundle.putString("dataKey2","data2xxx");
dataBundle.putString("dataKey3","data3xxx");
dataBundle.putString("dataKey4","data4xxx");
dataBundle.putString("dataKey5","data5xxx");
extras.putBundle("widgetExtraData", dataBundle);
appWidgetManager.requestPinAppWidget(myProvider, extras, null);
}
}
// 获取自定义参数
public class ExampleWidgetProvider extends AppWidgetProvider {
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager
appWidgetManager, int appWidgetId, Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager,
appWidgetId, newOptions);
if(extras != null){
Bundle dataBundle = extras.getBundle("widgetExtraData");
dataBundle.getString("xxxkey");
}
}
}
2.设置 MIUI Widget卡片状态
2.1.描述
MIUI Widget 也可以在智能助理(负一屏)进行展示。应用可以选择向系统发送卡片的优先级状态,MIUI智能助理(负一屏)会根据卡片状态进行动态的排序。
2.2.调用方式
key: miuiWidgetEventCode
类型: String
描述: 事件code命名规则请参考code事件表
key: miuiWidgetTimestamp
类型: String
描述: 状态变化时的时间戳
appWidgetManager.updateAppWidgetOptions(widgetId, options);
appWidgetManager.updateAppWidget(widgetId, views);
2.3.code 事件表
事件code命名规则请参考下表,具体事件与code对应关系以双方沟通结果为准 *新增或修改事件需与MIUI商务同学联系,上报未确认的事件code不会生效,恶意错报被系统识别后会降低推荐权重 |
|||
核心场景 | 事件 | 事件code | 备注 |
金融 股票证券 |
小部件内有股票/基金在交易 | opening | |
小部件内无股票/基金在交易 | closing | ||
出行 | 通勤时间 | commuting | |
非通勤时间 | other | ||
直播/电台 | 小部件内展示内容“直播中” | live1 | 不同的事件可用live1、live2…区分 |
小部件内展示内容“重播中” | replay | ||
小部件内展示内容“未开播” | other | ||
连接状态 | 已连接 | connected | |
未连接 | disconnect | ||
通知提醒 | 小部件内展示有通知提醒提醒 | notice1 | 不同的事件可用notice1、notice2…区分 |
进度状态 | 如“打车进度”“下单进度”等 | progress1 | 不同的事件可用progress1、progress2…区分 |
内容资讯 | 如“今日热门”“话题榜”“热搜榜”等 | info1 | 不同的事件可用info1、info2…区分 |
功能跳转 | 小部件为纯工具类的功能跳转 | state1 | 不同的事件可用state1、state2…区分 |
其他 | 小部件的兜底展示方案或其他状态 | other |
2.4.示例
- 普通 Widget :在 AppWidgetProvider 的 onUpdate 方法,调用 updateAppWidgetOptions 方法。
public class ExampleWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// todo 准备待更新数据
....
// 更新数据
for (int appWidgetId : appWidgetIds) {
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.widget_ui);
Bundle options = new Bundle();
options.putString("miuiWidgetEventCode", "notice1");
long currentTimeMillis = System.currentTimeMillis();
options.putString("miuiWidgetTimestamp",
String.valueOf(currentTimeMillis));
appWidgetManager.updateAppWidgetOptions(widgetId, options);
appWidgetManager.updateAppWidget(widgetId, views);
}
}
}
- 列表 Widget : notifyAppWidgetViewDataChanged 更新后调用 updateAppWidget 和updateAppWidgetOptions 方法
public class ExampleWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
// todo 准备待更新数据
...
appWidgetManager.notifyAppWidgetViewDataChanged (mAppWidgetId,R.id.content);
// 更新数据后进行状态更新
Bundle options = new Bundle();
options.putString("miuiWidgetEventCode", "notice1");
long currentTimeMillis = System.currentTimeMillis();
options.putString("miuiWidgetTimestamp",
String.valueOf(currentTimeMillis));
appWidgetManager.updateAppWidgetOptions(mAppWidgetId, options);
RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.widget_list_example);
appWidgetManager.updateAppWidget(mAppWidgetId, remoteViews);
}
}
}
注意:updateAppWidgetOptions 需要在 updateAppWidget ()方法前,并且两者需要同一线程。
3.设置编辑页
3.1.描述
MIUI Widget 额外提供进入编辑页的入口。用户点击编辑按钮,可以进入编辑页进行相应的设置。
3.2.调用方法与参数
Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
String uriPath = "widgetdemo://com.miui.widgetdemo/widget/WidgetEditActivity";
options.putString("miuiEditUri", uriPath);
appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
appWidgetManager.updateAppWidget(widgetId, views);
3.3.示例
public class ExampleWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager
appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
Bundle options =
appWidgetManager.getAppWidgetOptions(appWidgetId);
String uriPath = "widgetdemo://com.miui.widgetdemo/widget/WidgetEditActivity";
options.putString("miuiEditUri", uriPath);
appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
RemoteViews views =
new RemoteViews(context.getPackageName(),
R.layout.widget_ui);
appWidgetManager.updateAppWidget(widgetId, views);
}
....
}
}
// 通过 intent 可以获取点击的appWidgetId
public class WidgetEditActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Intent intent = getIntent();
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
}
}
4.同功能 MIUI Widget 聚合
4.1.描述
MIUI Widget 可以把不同尺寸相同功能的Widget 在详情页中聚合在一块显示,用户可以根据需求添加相应尺寸的Widget。
4.2.调用方式
在AndroidManifest.xml 文件中 AppWidgetProvider 对应的receiver 如果label 的名称相同的话将会被认为是同一功能的不同尺寸。
4.3.示例
<receiver
android:name="com.miui.widgetdemo.provider.ExampleWidgetProvider1"
android:label="@string/app_widget_group">
...
</receiver>
<receiver
android:name="com.miui.widgetdemo.provider.ExampleWidgetProvider2"
android:label="@string/app_widget_group">
...
</receiver>
5.点击 MIUI Widget 跳转应用页面
5.1.描述
在 AppWidgetProvider 的onUpdate方法中,使用 RemoteViews 的 setOnClickPendingIntent 设置启动的 Intent 。使用 AppWidgetManager 的 updateAppWidget方法关联RemoteViews 和Widget。
5.2.参数
// viewId Widget 布局 id
// PendingIntent Intent的封装
// appWidgetId Widget 的 id,可在 onUpdate 方法参数中获取
// appWidgetManager widget 的管理对象,可在 onUpdate 方法参数获取
remoteViews.setOnClickPendingIntent(int viewId, PendingIntent pendingIntent)
appWidgetManager.updateAppWidget(int appWidgetId, RemoteViews views)
5.3.示例
// step1: 构建跳转页面的 PendingIntent
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// step2: 生成 RemoteViews 关联 PendingIntent
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
// step3: 关联 widget 和 RemoteViews
appWidgetManager.updateAppWidget(appWidgetId, views);
6.判断 MIUI Widget 是否已经添加
6.1.描述
使用系统 AppWidgetManager 的 getAppWidgetIds方法判断某个Widget是否已添加。
6.2.示例
ComponentName componentName = new ComponentName(getPackageName(), "com.miui.ExampleAppWidgetProvider");
int[] appWidgetIds = AppWidgetManager.getInstance(getApplicationContext()).getAppWidgetIds(componentName);
if(appWidgetIds.length > 0){
// 已添加
} else {
// 未添加
}
7.判断是否支持MIUI Widget
7.1.描述
开发者可以通过示例方式判断当前手机是否支持MIUI Widget。
7.2.示例
@WorkerThread
public boolean isMiuiWidgetSupported() {
Uri uri =
Uri.parse("content://com.miui.personalassistant.widget.external");
boolean isMiuiWidgetSupported = false;
try {
Bundle bundle = getContentResolver().call(uri,
"isMiuiWidgetSupported", null, null);
if (bundle != null) {
isMiuiWidgetSupported = bundle.getBoolean("isMiuiWidgetSupported");
}
} catch (Exception e) {
e.printStackTrace();
}
return isMiuiWidgetSupported;
}
8.判断是否支持MIUI Widget 小部件详情页
8.1.描述
开发者可以通过示例方式判断当前手机是否支持MIUI Widget 详情页。为了提升用户体验,部分机型支持 MIUI Widget(包含MIUI Widget 特性,例如曝光刷新等), 但不支持调起MIUI Widget 详情页。这部分机型添加小部件的方式与旧版系统一致。
8.2.示例
@WorkerThread
public boolean isMiuiWidgetDetailPageSupported() {
Uri uri =
Uri.parse("content://com.miui.personalassistant.widget.external");
boolean isMiuiWidgetDetailPageSupported = false;
try {
Bundle bundle = getContentResolver().call(uri,
"isMiuiWidgetDetailPageSupported", null, null);
if (bundle != null) {
isMiuiWidgetDetailPageSupported = bundle.getBoolean("isMiuiWidgetDetailPageSupported");
}
} catch (Exception e) {
e.printStackTrace();
}
return isMiuiWidgetDetailPageSupported;
}
9.MIUI Widget 与 Activity 切换动画
9.1.描述
MIUI 系统为MIUI Widget 和 Activity 切换时增加了过渡动画。开发者可根据自身业务场景决定是否使用过渡动画。动画默认开启,如果不使用,设置”miuiWidgetTransitionAnimation”为false。
9.2.示例
<receiver android:name="com.miui.ExampleWidgetProvider" >
<meta-data
android:name="miuiWidget"
android:value="true" />
// 关闭动画
<meta-data
android:name="miuiWidgetTransitionAnimation"
android:value="false" />
...
</receiver>
10.App主动刷新小部件
10.1.描述
当应用在前台使用或在后台存活时,可调用AppWidgetManager. updateAppWidget()方法,主动刷新小部件。这样可以提高小部件内容的实时性和准确性。
10.2.示例
// 通过组件名更新
public void updateWidget(Context context) {
// R.layout.demo_appwidget_normal_example 为小部件布局文件,这里仅示例
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.demo_appwidget_normal_example);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
// NormalExampleWidgetProvider 为小部件组件名字,这里仅示例
ComponentName componentName = new ComponentName(context, NormalExampleWidgetProvider.class);
appWidgetManager.updateAppWidget(componentName, remoteViews);
}
// 通过widgetId更新
public void updateWidgetByWidgetId(Context context, int widgetId) {
// R.layout.demo_appwidget_normal_example 为小部件布局文件,这里仅示例
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.demo_appwidget_normal_example);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(widgetId, remoteViews);
}
11.Push透传刷新服务
11.1.描述
当MIUI Widget状态发生改变且Widget不可见、主应用未启动时,开发者可调用小部件服务端提供的更新API,经由MI PUSH发送透传消息给负一屏/桌面客户端,由负一屏/桌面拉起Widget独立进程(不唤醒应用主进程),以实现MIUI Widget实时刷新的目的。
流程图:
11.2.接口规范
域名: https://developer.assistant.miui.com
路径: /openapi/widget/{cpCode}/refresh
cpCode值由小部件开发人员提供
参数:需包含Header以及Body
Header参数表 | ||||
头域名称 | 描述 | 是否必选 | 类型 | 取值范围 |
Content-Type | 固定值,填application/json | 是 | String | application/json |
app-id | 在小米开放平台注册的程序编号 | 是 | String | |
access-token | 应用级token(校验请求权限,通过帐号服务获取) | 是 | String | 最大长度:259 |
sign | 签名(验证请求完整性,下方有签名生成方法) | 是 | String | |
timestamp | 当前时间戳(防止请求重放,会根据该字段判断请求有效期) | 是 | long | 13位 毫秒时间戳 |
trace-id | 请求的唯一标识(用来定位每次请求) | 是 | String | 只能包含数字和大小写字母,最大长度64 |
Body参数表 | |||
字段名 | 描述 | 必须 | 类型 |
oaid | 设备oaid | 是 | String |
widgetId | 设备 widgetId (安卓生成的id) | 是 | String |
widgetProviderName | 小部件providerName | 是 | String |
extra | 额外透传到小部件内容 | 否 | String |
注意:
- 获取access-token:access-token的获取方式请联系相关技术支持。
- 处理签名
签名生成:
假设: appId = "111111111" , 当前时间戳是 1606206667013 ,秘钥是 "testSecret"
step 1: 先将body进行md5,再转化为16进制小写字符串
公式:md5Body= Lowercase(hexEncode(md5(body)))
step 2: 将appId、毫秒时间戳和body的md5Body值,按照参数名称进行字典排序,用‘&’连接获得strToDigest
如:strToDigest="appId=111111111&body=md5Body×tamp=1606206667013"
step 3: 将获得到的摘要字符串(strToDigest)进行HMAC_SHA_256,再转化为16进制小写字符串得到签名
公式:Lowercase(hexEncode(HMAC_SHA_256("testSecret", strToDigest)))
// 签名java demo:
// demo引用了apache的工具类进行处理,若不想引用可以参考其逻辑自行实现
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
@Slf4j
public class SignatureUtils {
/**
* 生成签名
*
* @param appId 应用id
* @param secret 秘钥
* @param timestamp 请求的时间戳
* @param body 请求的body内容
* @return 签名信息
*/
public static String generateSignature(String appId, String secret, String timestamp, String body) {
String sign = null;
try {
String md5Body = DigestUtils.md5Hex(body);
Map<String, String> paramMap = new TreeMap<>();
paramMap.put("appId", appId);
paramMap.put("timestamp", timestamp);
paramMap.put("body", md5Body);
String strToDigest = paramMap.entrySet().stream().map(e ->手机app生成器; e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
sign = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, secret).hmacHex(strToDigest);
} catch (Exception e) {
log.error("generate signature exception !", e);
}
return sign;
}
/**
* 验证签名
*
* @param appId 应用id
* @param secret 秘钥
* @param timestamp 请求的时间戳
* @param body 请求的body内容
* @param sign 请求的签名
* @return 签名信息
*/
public static boolean verifySignature(String appId, String secret, String timestamp, String body, String sign) {
return sign.equals(generateSignature(appId, secret, timestamp, body));
}
}
- 返回状态码定义
状态码 | 描述 |
成功 | 0 |
服务异常 | 1000 |
无效参数 | 1001 |
缺失参数 | 1002 |
token验证失败 | 3000 |
签名验证失败 | 4000 |
流量限制 | 5000 |
- 开发者MIUI Widget可从刷新广播的Intent中得到extra信息。
11.3.示例
// Curl示例:
curl --location --request POST 'http://developer.assistant.miui.com/openapi/widget/testCp/refresh'
--header 'app-id: 111111111'
--header 'access-token: A1_oKs19iiWArgij6qFYaEAooAMqG3bhXUgS0MZQf63KQTgWju-oj9YuccqR1EhbugrqnFmooNr6mKdkfEdKN740fUYpxk0o9ZHE5QpFvR1fhaoJ7xELYD1byNnYZb-tB-y5DPXRLIp8ikod5lUZGnayuLVPePa7uB1LlVzw-qPS-U'
--header 'sign: 664d528f398af20f23b6e4ec43d4e9662476ee8fc9c5cee42dd897b1af0152e7'
--h生成自己的appeader 'timestamp: 1636341480000'
--header 'trace-id: 123123'
--header 'Content-Type: application/json'
--data-raw '{
"oaid": "f29eb7d12222fb6b",
"widgetId": "666",
"widgetProviderName": "com.miui.test",
"extra": "{"test":123}"
}'
编辑:yimen,如若转载,请注明出处:https://www.yimenapp.com/kb-yimen/12415/
部分内容来自网络投稿,如有侵权联系立删