本文是 Android Widget(小部件) 系列的第一篇拇砰,主要是對 Android widget (小部件)基本原理、開發(fā)流程、以及常見問題做了簡單的介紹。
本系列的目的是通過對Android 小部件的梳理,了解小部件刷新流程、恢復流程、以及系統(tǒng)發(fā)生變化時,小部件是如何適配的常挚,解決在開發(fā)小部件過程中遇到的問題。系列文章大部份來自源碼的解讀稽物,內(nèi)容非常多奄毡,也非常容易遺忘,因此記錄分享贝或。
系列文章
Android Widget 基礎介紹以及常見問題
安卓小部件刷新源碼解析一非列表
安卓小部件(APPWidget)刷新源碼解析一列表
一吼过、Android Widget 原理常見問題
1、小部件是什么咪奖?
App widgets are miniature application views that can be embedded in other applications (such as the home screen) and receive periodic updates盗忱。
通俗解釋:一個能夠定期刷新并且加到其他應用上的微型視圖。
官網(wǎng)
2羊赵、小部件的運行機制是什么趟佃?
- 通過 AppWidgetProvider 定義小部件的行為
- 通過 RemoteView 和布局文件定義小部件的UI
- 通過AppWidgetManager 更新視圖
- 在manifeset 里注冊 AppWidgetProvider(繼承于廣播)扇谣,設置監(jiān)聽的action以及配置文件
3、RemoteView如何工作闲昭?
RemoteView 繼承于Parcelable罐寨,可在進程間傳遞。RemoteView 會將每一個設置的行為轉換成相應的Action序矩。在Host 測時再將Action 翻譯成對應的行為鸯绿。
4、小部件運行在什么進程簸淀?
小部件的運行邏輯需要分為三部分:AppWidgetProvider 中的邏輯運行在小部件所在應用進程瓶蝴。小部件查找以及權限校驗的邏輯運行在system_process中。小部件渲染邏輯在host 進程中租幕。
二舷手、開發(fā)中常見問題
1、開發(fā)一個小部件有哪必要流程令蛉?
- 新建一個類繼承AppWidgetProvider用于定義主要的邏輯和行為
public class ExampleAppWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 更新邏輯
}
}
- 新建一個配置文件描述AppWidgetProviderInfo 信息
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp" // 最小寬聚霜,用于計算橫向網(wǎng)格數(shù)
android:minHeight="40dp" // 最小高狡恬,用于計算縱向網(wǎng)格數(shù)
android:updatePeriodMillis="86400000" // 刷新時間間隔珠叔,最小為30min
android:previewImage="@drawable/preview" //定義預覽圖片
android:initialLayout="@layout/example_appwidget" 定義初始化布局,remoteView 布局未加載結束前視圖
android:configure="com.example.android.ExampleAppWidgetConfigure" //定義設置頁
android:resizeMode="horizontal|vertical" //定義尺寸模式
android:widgetCategory="home_screen"> //定義種類弟劲,有桌面祷安、鎖屏、輸入法
</appwidget-provider>
- 在AndroidManifest.xml 中注冊
<receiver android:name="ExampleAppWidgetProvider" >
// 監(jiān)聽更新的acion
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver>
2兔乞、如何設置minWidth 和 minHeight
minWidth 和 minHeight 主要用于計算橫向和縱向所占格子數(shù)汇鞭,不通廠商計算方式不同,但大概率都會符合谷歌規(guī)范規(guī)范
- 4*2 橫向范圍 250~320 縱向是110~180
- 2*2 橫向范圍110~180 縱向是110~180
3庸追、如何AppWidgetProvider 如何更新小部件霍骄?
// appWidgetManager和widgetId 從 onUpdate 方法中獲取
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.xxx);
appWidgetManager.updateAppWidget(widgetId, remoteViews);
4、應用里如何更新小部件淡溯?
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.xxx);
AppWidgetManager appWidgetManager = AppWidgetManager.*getInstance*(context);
// NormalExampleWidgetProvider 為小部件組件名字读整,這里僅示例
ComponentName componentName = new ComponentName(context, NormalExampleWidgetProvider.class);
appWidgetManager.updateAppWidget(componentName, remoteViews);
5、如何設置點擊事件咱娶?
// 生成PendingIntent
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
//生成 RemoteViews 關聯(lián) PendingIntent
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
// 關聯(lián) widget 和 RemoteViews
appWidgetManager.updateAppWidget(appWidgetId, views);
6米间、Widget 中 List 設置了setRemoteAdapter,第二次添加該小部件時膘侮,為什么沒有調(diào)用onGetViewFactory 屈糊?
原因可能是RemoteViewsAdapter 復用,系統(tǒng)認為沒有數(shù)據(jù)改變琼了,導致沒有回調(diào)onGetViewFactory逻锐,這個在google demo 也有說明。
- 原因分析
class AbsListView {
public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
// Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
// service handling the specified intent.
if (mRemoteAdapter != null) {
Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
Intent.FilterComparison fcOld = new Intent.FilterComparison(
mRemoteAdapter.getRemoteViewsServiceIntent());
// 比較兩個是否
if (fcNew.equals(fcOld)) {
return;
}
}
mDeferNotifyDataSetChanged = false;
// Otherwise, create a new RemoteViewsAdapter for binding
mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
if (mRemoteAdapter.isDataReady()) {
setAdapter(mRemoteAdapter);
}
}
}
class Intent {
public boolean equals(@Nullable Object obj) {
if (obj instanceof FilterComparison) {
Intent other = ((FilterComparison)obj).mIntent;
return mIntent.filterEquals(other);
}
return false;
}
public boolean filterEquals(Intent other) {
if (other == null) {
return false;
}
if (!Objects.equals(this.mAction, other.mAction)) return false;
if (!Objects.equals(this.mData, other.mData)) return false;
if (!Objects.equals(this.mType, other.mType)) return false;
if (!Objects.equals(this.mIdentifier, other.mIdentifier)) return false;
if (!(this.hasPackageEquivalentComponent() && other.hasPackageEquivalentComponent())
&& !Objects.equals(this.mPackage, other.mPackage)) {
return false;
}
if (!Objects.equals(this.mComponent, other.mComponent)) return false;
if (!Objects.equals(this.mCategories, other.mCategories)) return false;
return true;
}
}
- 解決方案
// Here we setup the intent which points to the StackViewService which will
// provide the views for this collection.
Intent intent = new Intent(context, StackWidgetService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
// When intents are compared, the extras are ignored, so we need to embed the extras
// into the data so that the extras will not be ignored.
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);
https://android.googlesource.com/platform/development/+/master/samples/StackWidget/src/com/example/android/stackwidget/StackWidgetProvider.java
到這里,Android Widget 基本使用以及常見問題就已經(jīng)說完了昧诱。但使用中你可能會遇到各種各樣的問題慷丽,而要解決問題,就需要你對相應的流程熟悉鳄哭。因此才會有這一些列的文章要糊。