一、刷新流程
1撒汉、system_process 發(fā)送廣播
2己单、應(yīng)用widget收到廣播,執(zhí)行一系列的業(yè)務(wù)邏輯后塞淹,調(diào)用AppWidgetManager的updateAppWidget()方法
3、AppWidgetManager 通過(guò)AIDL 通知 system_process更新栖茉,system_process收到回調(diào)后做一些列操作篮绿,回調(diào)host 進(jìn)程
4、host 進(jìn)程綁定service,回調(diào)應(yīng)用進(jìn)程onDataSetChanged,應(yīng)用進(jìn)程修改數(shù)據(jù)
5吕漂、host 進(jìn)程根據(jù)remoteView 更新視圖
二亲配、詳情刷新流程
1、system_process 發(fā)送廣播
更新廣播的action 為android.appwidget.action.APPWIDGET_UPDATE
2惶凝、調(diào)用 notifyAppWidgetViewDataChanged更新
調(diào)用AppWidgetManager.notifyAppWidgetViewDataChanged(),在此之前一般都會(huì)設(shè)置setRemoteAdapter
3吼虎、notifyAppWidgetViewDataChanged()
這里通過(guò)AIDL跨進(jìn)程技術(shù)調(diào)用system_progress進(jìn)程的AppWidgetServiceImpl對(duì)象。
3.1梨睁、enforceCallFromPackage
安全性校驗(yàn)鲸睛,確定請(qǐng)求的包命和uid 是一致的
3.2、ensureGroupStateLoadedLocked
若已經(jīng)加載過(guò)了則return,若沒有加載過(guò)則根據(jù)uid 獲取widgetProvider(過(guò)濾帶刷新action 的廣播)坡贺,根據(jù)uid 獲取相應(yīng)的配置文件官辈,根據(jù)配置文件設(shè)置widget,并綁定相應(yīng)的host遍坟。放入mWidgets中拳亿。
3.3、lookupWidgetLocked
根據(jù)widgetId在mWidgets 找到對(duì)應(yīng)的widget,通過(guò)uid驗(yàn)證權(quán)限
3.4愿伴、scheduleNotifyAppWidgetViewDataChanged 發(fā)送DataChange 的message
4肺魁、Looper 執(zhí)行DataChange Message 調(diào)用 handleNotifyAppWidgetViewDataChanged
- 通過(guò)AIDL 回調(diào) AppWidgetHost 的 viewDataChanged 方法
- 若viewDataChanged回調(diào)異常,則重新bindService,連接成功后回調(diào) RemoteViewService 的 onDataSetChangedAsync方法隔节。
5鹅经、AppWidgetHost 收到 viewDataChanged 回調(diào),發(fā)DataChange Message
6怎诫、Looper 執(zhí)行DataChange Message 調(diào)用viewDataChanged找到對(duì)應(yīng)的 AppWidgetHostView 并執(zhí)行刷新
6.1瘾晃、獲取AppWidgetHostView 的adapter 并執(zhí)行notifyDataSetChanged
6.2、RemoteViewAdapter.notifyDataSetChanged 取消解綁message 發(fā)送dataChangeMessage
7幻妓、處理dataChangeMessage
7.1蹦误、enqueueDeferredUnbindServiceMessage 移除解綁message 并設(shè)置5s 后解綁
7.2、sendNotifyDataSetChange 回調(diào)RemoteViewFactory的onDataSetChanged ,這塊通常用來(lái)獲取新的數(shù)據(jù)
7.3、adapter.updateRemoteViews更新item
7.3.1强胰、回調(diào)RemoteViewFactory的getViewAt 獲取item 的RemoteView
7.3.2舱沧、發(fā)送remoteView load Message
8、調(diào)用notifyOnRemoteViewsLoaded 通過(guò)position找到對(duì)應(yīng)的item 進(jìn)行加載
8.1偶洋、調(diào)用applyRemoteViews 將RemoteView 的action 進(jìn)行應(yīng)用
三熟吏、詳細(xì)流程
1、收到廣播涡真,更新廣播的action 為android.appwidget.action.APPWIDGET_UPDATE分俯。
// AppWidgetProvider 繼承于廣播肾筐,system_process發(fā)送廣播是會(huì)回調(diào)onReceive方法
// 如果是更新廣播的話會(huì)回調(diào)onUpdate()方法
public class AppWidgetProvider extends BroadcastReceiver {
...
public void onReceive(Context context, Intent intent) {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
}
...
}
2哆料、應(yīng)用widget收到廣播,準(zhǔn)備數(shù)據(jù)構(gòu)建RemoteView,并調(diào)用AppWidgetManager的notifyAppWidgetViewDataChanged()方法
public abstract class TestWidgetProvider extends AppWidgetProvider {
...
/**
*onReceive會(huì)回調(diào)該方法
*
**/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds{
// AppWidgetProvider 這里通常會(huì)設(shè)置new RemoteView,并設(shè)置吗铐,可設(shè)置點(diǎn)擊時(shí)間东亦、文字、圖片等最后調(diào)用
// appWidgetManager.updateAppWidget()
super.onUpdate(context, appWidgetManager, appWidgetIds);
for (int widgetId : appWidgetIds) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.widget_test);
...
appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.lv_list_test);
}
}
...
}
3唬渗、notifyAppWidgetViewDataChanged()
這里通過(guò)AIDL跨進(jìn)程技術(shù)調(diào)用system_progress進(jìn)程的AppWidgetServiceImpl對(duì)象典阵。
public class AppWidgetManager {
...
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
if (mService == null) {
return;
}
try {
mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
...
}
/***************************************************************/
class AppWidgetServiceImpl {
…
public void notifyAppWidgetViewDataChanged(String callingPackage, int[] appWidgetIds,
int viewId) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG) {
Slog.i(TAG, "notifyAppWidgetViewDataChanged() " + userId);
}
// Make sure the package runs under the caller uid.
// AppWidgetServiceImpl 運(yùn)行在system_process ,包名為字符串傳入镊逝,
// 安全性校驗(yàn)壮啊,確定請(qǐng)求的包命和uid 是一致的
mSecurityPolicy.enforceCallFromPackage(callingPackage);
if (appWidgetIds == null || appWidgetIds.length == 0) {
return;
}
synchronized (mLock) {
// 是否解鎖狀態(tài),處于解鎖狀態(tài)撑蒜,若第一次加載則構(gòu)建widget歹啼,后面會(huì)詳細(xì)解析
ensureGroupStateLoadedLocked(userId);
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
final int appWidgetId = appWidgetIds[i];
// NOTE: The lookup is enforcing security across users by making
// sure the caller can only access widgets it hosts or provides.
//根據(jù)widgetId在mWidgets 找到對(duì)應(yīng)的widget,通過(guò)uid驗(yàn)證權(quán)限
Widget widget = lookupWidgetLocked(appWidgetId,
Binder.getCallingUid(), callingPackage);
if (widget != null) {
scheduleNotifyAppWidgetViewDataChanged(widget, viewId);
}
}
}
...
}
3.1、enforceCallFromPackage()
安全性校驗(yàn)座菠,確定請(qǐng)求的包命和uid 是一致的
public void enforceCallFromPackage(String packageName) {
mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
}
@Deprecated
public void checkPackage(int uid, @NonNull String packageName) {
try {
// 檢查請(qǐng)求的 uid 和 packageName 是否一致
if (mService.checkPackage(uid, packageName) != MODE_ALLOWED) {
throw new SecurityException(
"Package " + packageName + " does not belong to " + uid);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
3.2狸眼、ensureGroupStateLoadedLocked
若已經(jīng)加載過(guò)了則return,若沒有加載過(guò)則根據(jù)uid 獲取widgetProvider(過(guò)濾帶刷新action 的廣播),根據(jù)uid 獲取相應(yīng)的配置文件浴滴,根據(jù)配置文件設(shè)置widget拓萌,并綁定相應(yīng)的host。放入mWidgets中
class AppWidgetServiceImpl{
private void ensureGroupStateLoadedLocked(int userId, boolean enforceUserUnlockingOrUnlocked) {
// 判斷該應(yīng)用是否處在解鎖狀態(tài)升略,設(shè)備鎖
if (enforceUserUnlockingOrUnlocked && !isUserRunningAndUnlocked(userId)) {
throw new IllegalStateException(
"User " + userId + " must be unlocked for widgets to be available");
}
// 判斷該應(yīng)用文件配置是否處在解鎖狀態(tài)
if (enforceUserUnlockingOrUnlocked && isProfileWithLockedParent(userId)) {
throw new IllegalStateException(
"Profile " + userId + " must have unlocked parent");
}
// 獲取能用的配置配置id
final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
// 查看是否有未加載的user
// Careful lad, we may have already loaded the state for some
// group members, so check before loading and read only the
// state for the new member(s).
int newMemberCount = 0;
final int profileIdCount = profileIds.length;
for (int i = 0; i < profileIdCount; i++) {
final int profileId = profileIds[i];
// >=0代表已經(jīng)加載過(guò),標(biāo)記數(shù)組
if (mLoadedUserIds.indexOfKey(profileId) >= 0) {
profileIds[i] = LOADED_PROFILE_ID;
} else {
newMemberCount++;
}
}
// 沒有新加的 便會(huì)return
if (newMemberCount <= 0) {
return;
}
// 構(gòu)建新增加的ProfileId 數(shù)組,后續(xù)通常在第一次加載的時(shí)候執(zhí)行
int newMemberIndex = 0;
final int[] newProfileIds = new int[newMemberCount];
for (int i = 0; i < profileIdCount; i++) {
final int profileId = profileIds[i];
if (profileId != LOADED_PROFILE_ID) {
mLoadedUserIds.put(profileId, profileId);
newProfileIds[newMemberIndex] = profileId;
newMemberIndex++;
}
}
// 清除provider 和 host 的tag 設(shè)置為 TAG_UNDEFINED
clearProvidersAndHostsTagsLocked();
// 根據(jù)加載ProfileId 獲取系統(tǒng) ResolveInfo 列表微王, 根據(jù)ResolveInfo 構(gòu)建
provider;
loadGroupWidgetProvidersLocked(newProfileIds);
// 從系統(tǒng)配置文件/data/system/users/0/appwidgets.xml 加載狀態(tài)、
loadGroupStateLocked(newProfileIds);
}
}
3.3品嚣、lookupWidgetLocked
根據(jù)widgetId在mWidgets 找到對(duì)應(yīng)的widget,通過(guò)uid驗(yàn)證權(quán)限
class AppWidgetServiceImpl{
private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
final int N = mWidgets.size();
for (int i = 0; i < N; i++) {
Widget widget = mWidgets.get(i);
if (widget.appWidgetId == appWidgetId
&& mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {
return widget;
}
}
return null;
}
}
3.4炕倘、scheduleNotifyAppWidgetViewDataChanged 發(fā)送DataChange 的message
class AppWidgetServiceImpl{
...
private void scheduleNotifyAppWidgetViewDataChanged(Widget widget, int viewId) {
if (viewId == ID_VIEWS_UPDATE || viewId == ID_PROVIDER_CHANGED) {
// A view id should never collide with these constants but a developer can call this
// method with a wrong id. In that case, ignore the call.
return;
}
long requestId = UPDATE_COUNTER.incrementAndGet();
if (widget != null) {
widget.updateSequenceNos.put(viewId, requestId);
}
if (widget == null || widget.host == null || widget.host.zombie
|| widget.host.callbacks == null || widget.provider == null
|| widget.provider.zombie) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.arg3 = requestId;
args.argi1 = widget.appWidgetId;
args.argi2 = viewId;
mCallbackHandler.obtainMessage(
CallbackHandler.MSG_NOTIFY_VIEW_DATA_CHANGED,
args).sendToTarget();
}
...
}
4、Looper 執(zhí)行DataChange Message 調(diào)用 handleNotifyAppWidgetViewDataChanged
class AppWidgetServiceImpl{
private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
int appWidgetId, int viewId, long requestId) {
try {
//通過(guò)AIDL 回調(diào) AppWidgetHost 的 viewDataChanged 方法
callbacks.viewDataChanged(appWidgetId, viewId);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this instance.
callbacks = null;
}
// If the host is unavailable, then we call the associated
// RemoteViewsFactory.onDataSetChanged() directly
// 回調(diào)失敗了會(huì)重新綁定service ,連接成功后RemoteViewsFactory.onDataSetChanged()進(jìn)行刷新
synchronized (mLock) {
if (callbacks == null) {
host.callbacks = null;
Set<Pair<Integer, FilterComparison>> keys = mRemoteViewsServicesAppWidgets.keySet();
for (Pair<Integer, FilterComparison> key : keys) {
if (mRemoteViewsServicesAppWidgets.get(key).contains(appWidgetId)) {
final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IRemoteViewsFactory cb = IRemoteViewsFactory.Stub
.asInterface(service);
try {
cb.onDataSetChangedAsync();
} catch (RemoteException e) {
Slog.e(TAG, "Error calling onDataSetChangedAsync()", e);
}
mContext.unbindService(this);
}
@Override
public void onServiceDisconnected(android.content.ComponentName name) {
// Do nothing
}
};
final int userId = UserHandle.getUserId(key.first);
Intent intent = key.second.getIntent();
// Bind to the service and call onDataSetChanged()
bindService(intent, connection, new UserHandle(userId));
}
}
}
}
}
5腰根、AppWidgetHost 收到 viewDataChanged 回調(diào)激才,發(fā)DataChange Message
class AppWidgetHost {
...
static clase Callbacks{
public void viewDataChanged(int appWidgetId, int viewId) {
Handler handler = mWeakHandler.get();
if (handler == null) {
return;
}
Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
appWidgetId, viewId);
msg.sendToTarget();
}
}
...
}
6、Looper 執(zhí)行DataChange Message 調(diào)用viewDataChanged找到對(duì)應(yīng)的 AppWidgetHostView 并執(zhí)行刷新
public class AppWidgetHost {
...
void viewDataChanged(int appWidgetId, int viewId) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
}
if (v != null) {
v.viewDataChanged(viewId);
}
...
}
6.1、獲取AppWidgetHostView 的adapter 并執(zhí)行notifyDataSetChanged
public class AppWidgetHostView{
...
void viewDataChanged(int viewId) {
View v = findViewById(viewId);
if ((v != null) && (v instanceof AdapterView<?>)) {
AdapterView<?> adapterView = (AdapterView<?>) v;
Adapter adapter = adapterView.getAdapter();
if (adapter instanceof BaseAdapter) {
BaseAdapter baseAdapter = (BaseAdapter) adapter;
baseAdapter.notifyDataSetChanged();
} else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
// If the adapter is null, it may mean that the RemoteViewsAapter has not yet
// connected to its associated service, and hence the adapter hasn't been set.
// In this case, we need to defer the notify call until it has been set.
adapter 為空瘸恼,說(shuō)明還沒有連接成功劣挫,阻擋數(shù)據(jù)過(guò)濾
((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged();
}
}
...
}
6.2、RemoteViewAdapter.notifyDataSetChanged 取消解綁message 發(fā)送dataChangeMessage
public class RemoteViewsAdapter {
...
public void notifyDataSetChanged() {
mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED);
...
}
}
7东帅、處理dataChangeMessage
private static class RemoteServiceHandler{
…
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case MSG_NOTIFY_DATA_SET_CHANGED: {
//移除解綁message 并設(shè)置5s 后解綁压固,后面詳講
enqueueDeferredUnbindServiceMessage();
if (adapter == null) {
return;
}
if (mRemoteViewsFactory == null) {
//設(shè)置延遲通知
mNotifyDataSetChangedPending = true;
adapter.requestBindService();
return;
}
// 回調(diào)RemoteViewFactory的onDataSetChanged ,這塊通常用來(lái)獲取新的數(shù)據(jù),后面詳將
if (!sendNotifyDataSetChange(true)) {
return;
}
// Flush the cache so that we can reload new items from the service
synchronized (adapter.mCache) {
adapter.mCache.reset();
}
// Re-request the new metadata (only after the notification to the factory)
// 更新緩存
adapter.updateTemporaryMetaData(mRemoteViewsFactory);
int newCount;
int[] visibleWindow;
synchronized (adapter.mCache.getTemporaryMetaData()) {
newCount = adapter.mCache.getTemporaryMetaData().count;
visibleWindow = adapter.getVisibleWindow(newCount);
}
// Pre-load (our best guess of) the views which are currently visible in the
// AdapterView. This mitigates flashing and flickering of loading views when a
// widget notifies that its data has changed.
//更新每一個(gè)item,后面詳講
for (int position : visibleWindow) {
// Because temporary meta data is only ever modified from this thread
// (ie. mWorkerThread), it is safe to assume that count is a valid
// representation.
if (position < newCount) {
adapter.updateRemoteViews(mRemoteViewsFactory, position, false);
}
}
// Propagate the notification back to the base adapter
adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
adapter.mMainHandler.sendEmptyMessage(
MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
return;
}
}
}
}
7.1靠闭、enqueueDeferredUnbindServiceMessage 移除解綁message 并設(shè)置5s 后解綁
private static class RemoteServiceHandler{
…
private void enqueueDeferredUnbindServiceMessage() {
removeMessages(MSG_UNBIND_SERVICE);
sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY);
}
@Override
public void handleMessage(Message msg) {
RemoteViewsAdapter adapter = mAdapter.get();
switch (msg.what) {
...
switch (msg.what) {
case MSG_UNBIND_SERVICE: {
unbindNow();
return;
}
}
protected void unbindNow() {
if (mBindRequested) {
mBindRequested = false;
mContext.unbindService(this);
}
mRemoteViewsFactory = null;
}
...
}
7.2帐我、sendNotifyDataSetChange 回調(diào)RemoteViewFactory的onDataSetChanged ,這塊通常用來(lái)獲取新的數(shù)據(jù)
private boolean sendNotifyDataSetChange(boolean always) {
try {
if (always || !mRemoteViewsFactory.isCreated()) {
mRemoteViewsFactory.onDataSetChanged();
}
return true;
} catch (RemoteException | RuntimeException e) {
Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
return false;
}
}
7.3、adapter.updateRemoteViews更新item
public class RemoteViewsAdapter {
...
@WorkerThread
private void updateRemoteViews(IRemoteViewsFactory factory, int position,
boolean notifyWhenLoaded) {
// Load the item information from the remote service
final RemoteViews remoteViews;
final long itemId;
try {
// 獲取item 對(duì)應(yīng)的RemoteView
remoteViews = factory.getViewAt(position);
itemId = factory.getItemId(position);
if (remoteViews == null) {
throw new RuntimeException("Null remoteViews");
}
} catch (RemoteException | RuntimeException e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
// Return early to prevent additional work in re-centering the view cache, and
// swapping from the loading view
return;
}
if (remoteViews.mApplication != null) {
// We keep track of last application info. This helps when all the remoteViews have
// same applicationInfo, which should be the case for a typical adapter. But if every
// view has different application info, there will not be any optimization.
if (mLastRemoteViewAppInfo != null
&& remoteViews.hasSameAppInfo(mLastRemoteViewAppInfo)) {
// We should probably also update the remoteViews for nested ViewActions.
// Hopefully, RemoteViews in an adapter would be less complicated.
remoteViews.mApplication = mLastRemoteViewAppInfo;
} else {
mLastRemoteViewAppInfo = remoteViews.mApplication;
}
}
int layoutId = remoteViews.getLayoutId();
RemoteViewsMetaData metaData = mCache.getMetaData();
boolean viewTypeInRange;
int cacheCount;
synchronized (metaData) {
viewTypeInRange = metaData.isViewTypeInRange(layoutId);
cacheCount = mCache.mMetaData.count;
}
synchronized (mCache) {
if (viewTypeInRange) {
int[] visibleWindow = getVisibleWindow(cacheCount);
// Cache the RemoteViews we loaded
mCache.insert(position, remoteViews, itemId, visibleWindow);
if (notifyWhenLoaded) {
// Notify all the views that we have previously returned for this index that
// there is new data for it.
Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0,
remoteViews).sendToTarget();
}
} else {
// We need to log an error here, as the the view type count specified by the
// factory is less than the number of view types returned. We don't return this
// view to the AdapterView, as this will cause an exception in the hosting process,
// which contains the associated AdapterView.
Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " +
" indicated by getViewTypeCount() ");
}
}
...
}
8愧膀、調(diào)用notifyOnRemoteViewsLoaded 通過(guò)position找到對(duì)應(yīng)的item 進(jìn)行加載
private class RemoteViewsFrameLayoutRefSet{
/**
* Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
* the associated RemoteViews has loaded.
*/
public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
if (view == null) return;
// Remove this set from the original mapping
final LinkedList<RemoteViewsFrameLayout> refs = removeReturnOld(position);
if (refs != null) {
// Notify all the references for that position of the newly loaded RemoteViews
for (final RemoteViewsFrameLayout ref : refs) {
ref.onRemoteViewsLoaded(view, mRemoteViewsInteractionHandler, true);
}
}
}
}
8.1拦键、調(diào)用onRemoteViewsLoaded 將RemoteView 的action 進(jìn)行應(yīng)用
static class RemoteViewsFrameLayout extends AppWidgetHostView {
/**
* Updates this RemoteViewsFrameLayout depending on the view that was loaded.
* @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
* successfully.
* @param forceApplyAsync when true, the host will always try to inflate the view
* asynchronously (for eg, when we are already showing the loading
* view)
*/
public void onRemoteViewsLoaded(RemoteViews view, InteractionHandler handler,
boolean forceApplyAsync) {
setInteractionHandler(handler);
applyRemoteViews(view, forceApplyAsync || ((view != null) && view.prefersAsyncApply()));
}
}