概述
RemoteViews顧名思義就是遠程View,它表示的是一個View結構,它可以在其他進程中顯示,為了跨進程更新它的界面,RemoteViews提供了一組基礎的操作來實現這個效果。RemoteViews在Android中的使用場景有兩種:通知欄和桌面小部件。
RemoteViews在通知欄上的應用
我們知道通知欄除了默認的效果外還支持自定義布局。
使用系統默認的樣式彈出一個通知的方式如下:(android3.0之后)
private void showDefaultNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// 設置通知的基本信息:icon、標題、內容
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle("My notification");
builder.setContentText("Hello World!");
builder.setAutoCancel(true);
// 設置通知的點擊行為:這里啟動一個 Activity
Intent intent = new Intent(this, SecondActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
// 發送通知 id 需要在應用內唯一
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(id, builder.build());
}
上述代碼會彈出一個系統默認樣式的通知,單擊通知后會打開SecondActivity同時會清除本身。效果如圖:
為了滿足個性化需求,我們還可能會用到自定義通知。實現自定義通知我們首先需要提供一個布局文件,然后通過RemoteViews來加載這個布局文件即可改變通知的樣式。
private void showCustomNotification() {
RemoteViews remoteView;
// 構建 remoteView
remoteView = new RemoteViews(getPackageName(), R.layout.layout_notification);
remoteView.setTextViewText(R.id.tvMsg, "哈shenhuniurou");
remoteView.setImageViewResource(R.id.ivIcon, R.mipmap.ic_launcher_round);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// 設置自定義 RemoteViews
builder.setContent(remoteView).setSmallIcon(R.mipmap.ic_launcher);
// 設置通知的優先級(懸浮通知)
builder.setPriority(NotificationCompat.PRIORITY_MAX);
Uri alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
// 設置通知的提示音
builder.setSound(alarmSound);
// 設置通知的點擊行為:這里啟動一個 Activity
Intent intent = new Intent(this, SecondActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
builder.setAutoCancel(true);
Notification notification = builder.build();
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(1001, notification);
}
效果如圖所示:
創建RemoteViews對象我們只需要知道當前應用包名和布局文件的資源id,比較簡單,但是要更新RemoteViews就不是那么容易了,因為我們無法直接訪問布局文件中的View,而必須通過RemoteViews提供的特定的方法來更新View。比如設置TextView文本內容需要用setTextViewText方法,設置ImageView圖片需要通過setImageViewResource方法。也可以給里面的View設置點擊事件,需要使用PendingIntent并通過setOnClickPendingIntent方法來實現。之所以更新RemoteViews如此復雜,直接原因是因為RemoteViews沒有提供跟View類似的findViewById這個方法,我們無法獲取到RemoteViews中的子View。
RemoteViews在桌面小部件上的應用
現在我要實現的效果是這樣一個小部件:
AppWidgetProvider是Android中提供用于實現桌面小部件的類,它的本質其實是一個廣播。開發桌面小部件的步驟:
定義小部件布局
在res/layout/下新建一個布局文件layout_widget.xml,內容命名根據需求自定。我在里面放了四個線程布局當做按鈕,外面再套一層線性布局橫向排列。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="4">
<LinearLayout
android:id="@+id/btn1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:text="按鈕1"
android:textColor="@android:color/white" />
</LinearLayout>
<LinearLayout
android:id="@+id/btn2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:text="按鈕2"
android:textColor="@android:color/white" />
</LinearLayout>
<LinearLayout
android:id="@+id/btn3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:text="按鈕3"
android:textColor="@android:color/white" />
</LinearLayout>
<LinearLayout
android:id="@+id/btn4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:text="按鈕4"
android:textColor="@android:color/white" />
</LinearLayout>
</LinearLayout>
定義小部件配置信息
在res/xml/下新建一個資源文件,命名自定:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget"
android:minHeight="56dp"
android:minWidth="272dp"
android:previewImage="@mipmap/ic_launcher"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="100000"
android:widgetCategory="home_screen">
</appwidget-provider>
解釋下各個屬性的含義
android:initialLayout:指定小部件的初始化布局
android:minHeight:小部件最小高度
android:minWidth:小部件最小寬度
android:previewImage:小部件列表顯示的圖標
android:updatePeriodMillis:小部件自動更新的周期
android:widgetCategory:小部件顯示的位置,home_screen表示只在桌面上顯示
定義小部件的實現類
package com.shenhuniurou.remoteviewsdemo;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
/**
* Created by Daniel on 2017/7/1.
*/
public class CustomAppWidgetProvider extends AppWidgetProvider {
public static final String CLICK_WEDGET_ONE = "com.shenhuniurou.appwidgetprovider.click.one";
public static final String CLICK_WEDGET_TWO = "com.shenhuniurou.appwidgetprovider.click.two";
public static final String CLICK_WEDGET_THREE = "com.shenhuniurou.appwidgetprovider.click.three";
public static final String CLICK_WEDGET_FOUR = "com.shenhuniurou.appwidgetprovider.click.four";
public CustomAppWidgetProvider() {
super();
}
@Override
public void onReceive(final Context context, Intent intent) {
super.onReceive(context, intent);
String action = intent.getAction();
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
// 判斷action是否是自己定義的action
if (action.equals(CLICK_WEDGET_ONE)) {
// 點擊的是第一個按鈕
Intent firstIntent = Intent.makeRestartActivityTask(new ComponentName(context, MainActivity.class));
Intent secondIntent = new Intent(context, SecondActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivities(context, 0, new Intent[] { firstIntent, secondIntent }, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);
} else if (action.equals(CLICK_WEDGET_TWO)) {
// 點擊的是第二個按鈕
Intent clickIntent = new Intent(context, ThirdActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, clickIntent, 0);
remoteViews.setOnClickPendingIntent(R.id.btn2, pendingIntent);
} else if (action.equals(CLICK_WEDGET_THREE)) {
// 點擊的是第三個按鈕
Intent clickIntent = new Intent(context, ForthActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, clickIntent, 0);
remoteViews.setOnClickPendingIntent(R.id.btn3, pendingIntent);
} else if (action.equals(CLICK_WEDGET_FOUR)) {
// 點擊的是第四個按鈕
Intent clickIntent = new Intent(context, FifthActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, clickIntent, 0);
remoteViews.setOnClickPendingIntent(R.id.btn4, pendingIntent);
}
appWidgetManager.updateAppWidget(new ComponentName(context, CustomAppWidgetProvider.class), remoteViews);
}
/**
* 桌面小部件每次更新時調用的方法
* @param context
* @param appWidgetManager
* @param appWidgetIds
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
int count = appWidgetIds.length;
for (int i = 0; i < count; i++) {
int appWidgetId = appWidgetIds[i];
onWidgetUpdate(context, appWidgetManager, appWidgetId);
}
}
/**
* 更新桌面小部件
* @param context
* @param appWidgetManager
* @param appWidgetId
*/
private void onWidgetUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
Intent intent1 = new Intent(CLICK_WEDGET_ONE);
PendingIntent pendingIntent1 = PendingIntent.getBroadcast(context, 0, intent1, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent1);
Intent intent2 = new Intent(CLICK_WEDGET_TWO);
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(context, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.btn2, pendingIntent2);
Intent intent3 = new Intent(CLICK_WEDGET_THREE);
PendingIntent pendingIntent3 = PendingIntent.getBroadcast(context, 0, intent3, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.btn3, pendingIntent3);
Intent intent4 = new Intent(CLICK_WEDGET_FOUR);
PendingIntent pendingIntent4 = PendingIntent.getBroadcast(context, 0, intent4, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.btn4, pendingIntent4);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
}
在清單文件上聲明小部件
<receiver android:name=".CustomAppWidgetProvider">
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_provider_info" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.shenhuniurou.appwidgetprovider.click.one" />
<action android:name="com.shenhuniurou.appwidgetprovider.click.two" />
<action android:name="com.shenhuniurou.appwidgetprovider.click.three" />
<action android:name="com.shenhuniurou.appwidgetprovider.click.four" />
</intent-filter>
</receiver>
這里面meta-data標簽中的name屬性是固定的android.appwidget.provider
,而resource屬性則是我們剛才新建的小部件的配置信息的xml,intent-filter中的android.appwidget.action.APPWIDGET_UPDATE是必須加的,它作為小部件的標識存在,這是系統的規范,否則這個receiver就不是一個桌面小部件,并且也無法出現在手機的小部件列表里。下面其他的action分別對應各個按鈕點擊的動作。
最后實現的效果圖:
總結下這個操作過程:當小部件一被添加到桌面時會調用Provider中的onUpdate方法,在這個方法中我們會通過AppWidgetManager去更新小部件的界面,但是這個更新我們是沒辦法直接更新的,而是通過RemoteViews來操作,setOnClickPendingIntent給每個按鈕設置了點擊時會發送的廣播動作,而在清單文件中我們聲明小部件時已經將這些廣播動作都加到intent-filter,所以當我們點擊桌面上該小部件中的某個按鈕時,就會發送對應的廣播,而小部件監聽了這個廣播,接收到廣播后再onReceive方法中根據動作來分別處理點擊事件。當然,對小部件的一些其他操作方法(比如onEnabled、onDisabled、onDeleted)的廣播也會在onReceive中接收到,然后分發給不同的方法。(我這里處理點擊事件用的也是RemoteViews的方式,其實不必,直接使用context.startActivity即可,但如果不是打開頁面,而是要更新小部件的界面,那么就需要繼續使用RemoteViews來更新了。)
PendingIntent
在上面實現小部件時我們多次使用到了PendingIntent,這個東西顧名思義我們可以理解為將要發生的意圖,就是在某個待定的時刻會發生。所以它和Intent的區別就在于一個是立即執行的一個是在未來某個時候執行。PendingIntent典型的使用場景是通知中點擊通知時跳轉頁面,因為我們不知道用戶什么時候點擊,另外就是給RemoteViews添加單擊事件,因為RemoteViews運行在遠程進程中,所以它不同于普通的View,不能想View那樣通過setOnClickListener方法來給設置單擊事件,想要給RemoteViews設置點擊事件,就必須使用PendingIntent,通過setOnClickPendingIntent方法來設置。PendingIntent是通過send和cancel方法來發送和取消待執行的Intent。
PendingIntent支持三種待定意圖,啟動activity(常見的通知)、啟動Service和發送廣播。它的主要方法有下面這些:
啟動Activity它有兩種,啟動單個和啟動多個,當使用getActivities時,實際上啟動的是Intent數組中最后一個activity,如果要讓最后一個activity返回時不退出app而是退回到上一個activity,實現方式可參照我上面第一個按鈕的點擊處理。
getActivity、getService、getBroadcast這三個方法的參數意義都是相同的,第一個上下文,第三個待定的意圖,第二個requestCode表示PendingIntent發送方的請求碼,多數情況下設置為0即可,另外requestCode會影響到第四個參數flags的效果。flags這個標志位表示執行效果。
常見的flags類型有FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT。要理解這四個標志位的含義和區別,我們首先要弄明白PendingIntent的匹配規則,也就是什么情況下PendingIntent是相同的。
匹配規則:
- 如果兩個PendingIntent它們內部的Intent相同,且requestCode也相同,那么這兩個PendingIntent就是相同的;
Intent相同的情況:
- 如果兩個Intent的ComponentName和intent-filter都相同,那么這兩個Intent就是相同的。(Extras不參與Intent的匹配過程,就是它不同,只要ComponentName和intent-filter相同,Intent都算相同的。)
FLAG_ONE_SHOT:表示當前描述的PendingIntent只能被使用一次,然后它就會自動cancel,如果后續還有相同的PendingIntent,那么它們的send方法就會調用失敗。如果通知欄消息使用這種標記位,同類型的通知就只會被打開一次,后續的通知將無法點開。
FLAG_NO_CREATE:表示當前描述的PendingIntent不會主動創建,如果當前PendingIntent之前不存在,那么getActivities等這些方法會直接返回null,獲取PendingIntent失敗。它無法單獨使用。
FLAG_CANCEL_CURRENT:表示當前描述的PendingIntent如果已經存在,就cancel它,然后系統會創建一個新的。
FLAG_UPDATE_CURRENT:表示當前描述的PendingIntent如果已經存在,那么它會被更新,內部的Intent中的Extras也會被更新。
RemoteViews的內部機制
RemoteViews的構造方法很多,我們最常見的一個是
public RemoteViews(String packageName, int layoutId) {
this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId);
}
只需要包名和待加載的資源文件id,它并不能支持所有類型的View,也不支持自定義的View,它能支持的類型如下:
Layout:FrameLyout、LinearLayout、RelativeLayout、GridLayout
View:Button、ImageView、ImageButton、ProgressBar、TextView、ListView、GridView、StackView、ViewStub、AdapterViewFlipper、ViewFlipper、AnalogClock、Chronometer。
如果我們在RemoteViews中使用了它不支持的View不如EditText,那么就會發生異常。
我們看看RemoteViews的set方法
從這些方法中看出,原本可以直接調用的View的方法,現在要通過RemoteViews的一系列set方法來完成。
我們知道,通知欄和桌面小部件分別由NotificationManager和AppWidgetManager來管理的,而NotificationManager和AppWidgetManager是通過Binder分別和SystemServer進程中的NotificationManagerService以及AppWidgetService進行通信,因此,通知欄和桌面小部件中的布局文件實際上是在NotificationManagerService和AppWidgetService中被加載的,而他們運行在SystemServer中,這其實已經和我們自己的app進程構成了跨進程通信。
理論分析
首先RemoteViews會通過Binder傳遞到SystemServer進程,因為RemoteViews實現了Parcelable接口,可以跨進程傳輸,系統會根據RemoteViews中的包名等信息去獲取到該app的資源,然后通過LayoutInflater去加載RemoteViews中的布局文件。在SystemServer進程中加載后的布局文件是一個普通的View,只不過對于我們的app進程來說,它是一個遠程View也就是RemoteViews。接著系統會對View執行一系列界面更新任務,這些任務就是之前我們通過set方法提交的,set方法對View的更新操作并不是立刻執行的,在RemoteViews內部會記錄所有的更新操作,具體的執行要等到RemoteViews被完全加載以后,這樣RemoteViews就可以在SystemServer中進程中顯示了,這就是我們所看到的通知欄消息和桌面小部件。當需要更新RemoteViews時,我們又需要調用一系列set方法通過NotificationManager和AppWidgetManager來提交更新任務,具體更新操作也是在SystemServer進程中完成的。
理論上講系統完全可以通過Binder去支持所有的View和View操作,但是這樣做代價太大,View的方法太多了,另外大量的IPC操作會影響效率。為了解決這個問題,系統并沒有通過Binder去直接支持View的跨進程訪問,而是提供了一個Action的概念,Action代表一個View操作,Action同樣實現了Parcelable接口。系統首先將View操作封裝到Action對象并將這些對象跨進程傳輸到遠程進程,接著在遠程進程中執行Action對象中的具體操作。在我們的app中每調用一次set方法,RemoteViews中就會添加一個對應的Action對象,當我們通過NotificationManager和AppWidgetManager來提交我們的更新時,這些Action對象就會傳輸到遠程進程并在遠程進程中依次執行。遠程進程通過RemoteViews的apply方法來進行View的更新操作,apply方法內部是去遍歷所有的Action對象并調用它們的apply方法,具體的View更新操作是由Action對象的apply方法來完成。
上述做法的好處,首先是不需要定義大量的Binder接口,其次通過在遠程進程中批量執行RemoteViews的更新操作從而避免了大量的IPC操作,這就提高了程序的性能。
源碼分析
首先我們從RemoteViews的set方法入手,比如設置圖片的方法setImageViewResource它內部實現是這樣的:
/**
* Equivalent to calling ImageView.setImageResource
*
* @param viewId The id of the view whose drawable should change
* @param srcId The new resource id for the drawable
*/
public void setImageViewResource(int viewId, int srcId) {
setInt(viewId, "setImageResource", srcId);
}
上面的代碼中viewId是被操作的View的id,setImageResource是方法名,srcId是要給這個ImageView設置的圖片資源id。這里的方法名和ImageView的setImageResource是一致的。我們再看看setInt方法的具體實現:
/**
* Call a method taking one int on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setInt(int viewId, String methodName, int value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
}
可以看到它內部并沒有對View進行直接操作,而是添加了一個ReflectionAction對象,字面上理解應該是一個反射類型的動作,再看addAction的實現:
/**
* Add an action to be executed on the remote side when apply is called.
*
* @param a The action to add
*/
private void addAction(Action a) {
if (hasLandscapeAndPortraitLayouts()) {
throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
" layouts cannot be modified. Instead, fully configure the landscape and" +
" portrait layouts individually before constructing the combined layout.");
}
if (mActions == null) {
mActions = new ArrayList<Action>();
}
mActions.add(a);
// update the memory usage stats
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
上述代碼可以看到,在RemoteViews內部維護了一個名為mActions的ArrrayList,所有的對View更新的操作動作都被添加到這個集合中,注意,僅僅是添加進來保存,并沒有去執行這些Action。到這里setImageViewResource方法的源碼已經結束了,下面我們要弄清楚這些Action的執行。我們再看看RemoteViews的apply方法:
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result = inflateView(context, rvToApply, parent);
loadTransitionOverride(context, handler);
rvToApply.performApply(result, parent, handler);
return result;
}
首先RemoteViews會通過LayoutInflater去加載它的布局文件,加載完之后通過performApply方法去執行一些更新操作。
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions != null) {
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
a.apply(v, parent, handler);
}
}
}
這里遍歷了mActions集合且執行每個Action的apply方法,應該可以看出,Action的apply方法才是真正操作View更新的地方。
當我們調用RemoteViews的set方法時,并不會立刻更新它們的界面,而必須要通過NotificationManager的notify方法以及AppWidgetManager的updateAppWidget方法才能更新它們的界面。實際上在AppWidgetManager的updateAppWidget內部實現中,的確是通過RemoteViews的apply方法和reapply方法來加載或更新界面的,apply和reapply的區別在于:apply會加載布局并更新界面,而reapply則只會更新界面,初始化時調用apply方法,后面的更新則調用reapply方法。
ReflectionAction是Action的子類,我們看下它的源碼:
/**
* Base class for the reflection actions.
*/
private final class ReflectionAction extends Action {
String methodName;
int type;
Object value;
ReflectionAction(int viewId, String methodName, int type, Object value) {
this.viewId = viewId;
this.methodName = methodName;
this.type = type;
this.value = value;
}
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final View view = root.findViewById(viewId);
if (view == null) return;
Class<?> param = getParameterType();
if (param == null) {
throw new ActionException("bad type: " + this.type);
}
try {
getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
} catch (ActionException e) {
throw e;
} catch (Exception ex) {
throw new ActionException(ex);
}
}
}
它的內部實現有點長,我們主要看它的apply方法。
getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
這句代碼就是它以反射的方式來對View進行操作,getMethod根據方法名得到反射所需的Method對象,然后執行該方法。
RemoteViews中的單擊事件,只支持發起PendingIntent,不支持onClickListener這種方法。我們需要注意setOnClickPendingIntent、setPendingIntentTemplate和setOnClickFillInIntent這幾個方法之間的區別和聯系。setOnClickPendingIntent是用于給普通的View設置點擊事件,但是它不能給ListView或者GridView、StackView中的item設置點擊事件,因為開銷比較大,系統禁止了這種方式。而setPendingIntentTemplate方法就能給item設置單擊事件,具體使用請參照這篇文章Android 之窗口小部件高級篇--App Widget 之 RemoteViews。
RemoteViews的優缺點
實際開發中,跨進程通信我們可以選擇AIDL去實現,但是如果對界面的更新比較頻繁,這時會有效率問題,而且AIDL接口可能會變得很復雜,但如果采用RemoteViews來實現就沒有這個問題了,RemoteViews的缺點就是它僅支持一些常見的View,而對于自定義View是不支持的。