前言
Launcher在Android的AppWidget整個(gè)體系中扮演AppWidgetHost的角色,本文分析Launcher對(duì)于AppWidget的處理 部分源碼分析。
一、AppWidget加載流程
1. Android系統(tǒng)啟動(dòng),SystemServer創(chuàng)建AppWidgetService,并調(diào)用systemReady()方法,在systemReady()方法中做以下三項(xiàng)準(zhǔn)備工作:
- 通過PackageManager從Android系統(tǒng)中查找所有已經(jīng)被安裝的AppWidget(包含"android.appwidget.action.APPWIDGET_UPDATE“ 的Action和meta-data標(biāo)簽),解析AppWidget的配置信息,封閉成對(duì)象,保存到List集合。
- 從/data/system/users/0/appwidgets.xml文件讀取已經(jīng)被添加到Launcher的AppWidget信息,封閉成對(duì)象,保存到List集合中。
- 注冊(cè)四個(gè)廣播接收器:第一. Android系統(tǒng)啟動(dòng)完成,第二. Android配置信息改變,第三. 添加刪除應(yīng)用,第四. sdcard的安裝與缷載。
2. Android系統(tǒng)啟動(dòng)Launcher應(yīng)用程序,會(huì)做以下準(zhǔn)備工作:
- 從Launcher應(yīng)用的數(shù)據(jù)庫查找已經(jīng)被添加到Launcher的AppWidget信息。
- 根據(jù)查找到的appWidgetId值(整型值)創(chuàng)建LauncherAppWidgetHostView布局對(duì)象。
- 根據(jù)查找到的appWidgetId值(整型值)從AppWidgetService中獲取RemoteViews對(duì)象(因?yàn)槭堑谝淮螁?dòng)所以RemoteViews對(duì)象為空)。
- 將獲取到的RemoteViews對(duì)象的布局解析并設(shè)置到第(2)步中創(chuàng)建的LauncherAppWidgetHostView布局對(duì)象中。
- 將LauncherAppWidgetHostView布局對(duì)象添加到Launcher的WorkSpace中(因?yàn)镽emoteViews對(duì)象為空,所以只在Launcher的 WorkSpace中占了一個(gè)位置)。
3. Android系統(tǒng)啟動(dòng)完成,發(fā)出BOOT_COMPLETED廣播,AppWidgetService接收到廣播后,會(huì)做以下事情:
- 獲取已經(jīng)添加到Launcher的AppWidget列表,依次向這個(gè)Widget發(fā)出APPWIDGET_ENABLED和 APPWIDGET_UPDATE更新廣播,根據(jù)配置的更新間隔定時(shí)發(fā)出更新廣播。
- 每個(gè)AppWidget接收到廣播后都會(huì)調(diào)用onEnabled()方法和onUpdate()方法,在onEnabled()方法中進(jìn)行一些初始化操作,在onUpdate()方法中創(chuàng)建RemoteViews布局對(duì)象并通過AppWidgetManager的updateAppWidget(int appWidgetId, RemoteViews remoteViews)方法通知AppWidgetService對(duì)象用RemoteViews對(duì)象更新appWidgetId所對(duì)應(yīng)的AppWidget。
- AppWidgetService接收到了appWidgetId和RemoteViews后,通過appWidgetId查找已經(jīng)被添加到Launcher的LauncherAppWidgetHostView布局對(duì)象,并RemoteViews中的布局更新到LauncherAppWidgetHostView布局對(duì)象中。AppWidget顯示在Launcher中。
二、AppWidget 預(yù)覽界面的加載流程
1. 操作流程

如圖,長(zhǎng)按空白區(qū)域出現(xiàn)彈框 ,點(diǎn)擊 widgets選選項(xiàng) 進(jìn)入下圖 widget預(yù)覽頁面

2. 顯示流程源碼分析
- Workspace 的構(gòu)造方法中 設(shè)置了 setOnTouchListener 交給 WorkspaceTouchListener類處理
public Workspace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mLauncher = Launcher.getLauncher(context);
....
//設(shè)置 觸摸事件
setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
}
public class WorkspaceTouchListener implements OnTouchListener, Runnable {
...
}
- WorkspaceTouchListener 中的 onTouch 中的 mWorkspace.postDelayed(this, getLongPressTimeout()); 自己實(shí)現(xiàn)的 Runnable接口再觀其 run() 方法。
public boolean onTouch(View view, MotionEvent ev) {
int action = ev.getActionMasked();
if (action == ACTION_DOWN) {
// Check if we can handle long press.
boolean handleLongPress = canHandleLongPress();
...
cancelLongPress();
if (handleLongPress) {
mLongPressState = STATE_REQUESTED;
mTouchDownPoint.set(ev.getX(), ev.getY());
// 長(zhǎng)按點(diǎn)擊事件
mWorkspace.postDelayed(this, getLongPressTimeout());
}
mWorkspace.onTouchEvent(ev);
// Return true to keep receiving touch events
return true;
}
......
}
public void run() {
if (mLongPressState == STATE_REQUESTED) {
if (canHandleLongPress()) {
...
OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
} else {
cancelLongPress();
}
}
}
- OptionsPopupView 中添加選項(xiàng),根據(jù)觸摸坐標(biāo)設(shè)置顯示的區(qū)域 RectF, 最后顯示彈框
public static void showDefaultOptions(Launcher launcher, float x, float y) {
float halfSize = launcher.getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
if (x < 0 || y < 0) {
x = launcher.getDragLayer().getWidth() / 2;
y = launcher.getDragLayer().getHeight() / 2;
}
RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
ArrayList<OptionItem> options = new ArrayList<>();
options.add(new OptionItem(R.string.wallpaper_button_text, R.drawable.ic_wallpaper,
ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
ControlType.WIDGETS_BUTTON, OptionsPopupView::onWidgetsClicked));
options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting,
ControlType.SETTINGS_BUTTON, OptionsPopupView::startSettings));
show(launcher, target, options);
}
public static void show(Launcher launcher, RectF targetRect, List<OptionItem> items) {
OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
.inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
popup.mTargetRect = targetRect;
for (OptionItem item : items) {
DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
view.getIconView().setBackgroundResource(item.mIconRes);
view.getBubbleText().setText(item.mLabelRes);
view.setDividerVisibility(View.INVISIBLE);
view.setOnClickListener(popup);
view.setOnLongClickListener(popup);
popup.mItemMap.put(view, item);
}
popup.reorderAndShow(popup.getChildCount());
}
- 其中注意 OptionsPopupView::onWidgetsClicked 這個(gè)是 點(diǎn)擊事件 顯示 WidgetsFullSheet(widget的預(yù)覽界面)
public static boolean onWidgetsClicked(View view) {
Launcher launcher = Launcher.getLauncher(view.getContext());
if (launcher.getPackageManager().isSafeMode()) {
Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
return false;
} else {
WidgetsFullSheet.show(launcher, true /* animated */);
return true;
}
}
public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
.inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
sheet.mIsOpen = true;
launcher.getDragLayer().addView(sheet);
sheet.open(animate);
return sheet;
}
二、AppWidget 綁定流程
- Launcher 的 onCreate 方法中 mModel.startLoader(currentScreen)
protected void onCreate(Bundle savedInstanceState) {
...
if (!mModel.startLoader(currentScreen)) {
if (!internalStateHandled) {
// If we are not binding synchronously, show a fade in animation when
// the first page bind completes.
mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
}
} else {
// Pages bound synchronously.
mWorkspace.setCurrentPage(currentScreen);
setWorkspaceLoading(true);
}
...
}
- LauncherModel類中 startLoader 方法中 主要關(guān)注 loaderResults.bindWidgets() ,其通過
callbacks.bindAllWidgets(widgets) ,回調(diào)到 Launcher的bindAllWidgets 方法處理。
public boolean startLoader(int synchronousBindPage) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
synchronized (mLock) {
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
final Callbacks oldCallbacks = mCallbacks.get();
// Clear any pending bind-runnables from the synchronized load process.
mUiExecutor.execute(oldCallbacks::clearPendingBinds);
// If there is already one running, tell it to stop.
stopLoader();
LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
mBgAllAppsList, synchronousBindPage, mCallbacks);
if (mModelLoaded && !mIsLoaderTaskRunning) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
loaderResults.bindWorkspace();
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
// 其中方法,最后也是走到了 loaderResults.bindWidgets();
startLoaderForResults(loaderResults);
}
}
}
return false;
}
獲取 widget數(shù)據(jù) 回調(diào)到 launcher中
public void bindWidgets() {
final ArrayList<WidgetListRowEntry> widgets =
mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.bindAllWidgets(widgets);
}
}
};
mUiExecutor.execute(r);
}
- 向 PopupDataProvider類中設(shè)置 widget 數(shù)據(jù),調(diào)用 AbstractFloatingView.onWidgetsBound();
Launcher.java
@Override
public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) {
mPopupDataProvider.setAllWidgets(allWidgets);
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
if (topView != null) {
topView.onWidgetsBound();
}
}
前面也講到過 widget的預(yù)覽視圖由 WidgetsFullSheet 顯示 而WidgetsFullSheet是 AbstractFloatingView的子類,所以最終調(diào)用了WidgetsFullSheet.onWidgetsBound()。

WidgetsFullSheet.java
@Override
protected void onWidgetsBound() {
// 從PopupDataProvider類中取出之前設(shè)置進(jìn)去的數(shù)據(jù),填充adapter
mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets());
}
- 總結(jié)上面,widget數(shù)據(jù)最終 交由 WidgetsFullSheet 類處理,下面我們看下
/**
* Popup for showing the full list of available widgets
*/
public class WidgetsFullSheet extends BaseWidgetSheet
implements Insettable, ProviderChangedListener {
private static final long DEFAULT_OPEN_DURATION = 267;
private static final long FADE_IN_DURATION = 150;
private static final float VERTICAL_START_POSITION = 0.3f;
private final Rect mInsets = new Rect();
private final WidgetsListAdapter mAdapter;
private WidgetsRecyclerView mRecyclerView;
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LauncherAppState apps = LauncherAppState.getInstance(context);
mAdapter = new WidgetsListAdapter(context,
LayoutInflater.from(context), apps.getWidgetCache(), apps.getIconCache(),
this, this);
}
public WidgetsFullSheet(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContent = findViewById(R.id.container);
mRecyclerView = findViewById(R.id.widgets_list_view);
mRecyclerView.setAdapter(mAdapter);
mAdapter.setApplyBitmapDeferred(true, mRecyclerView);
TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
springLayout.addSpringView(R.id.widgets_list_view);
mRecyclerView.setEdgeEffectFactory(springLayout.createEdgeEffectFactory());
onWidgetsBound();
}
@Override
protected Pair<View, String> getAccessibilityTarget() {
return Pair.create(mRecyclerView, getContext().getString(
mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mLauncher.getAppWidgetHost().addProviderChangeListener(this);
notifyWidgetProvidersChanged();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
}
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
mRecyclerView.setPadding(
mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
mRecyclerView.getPaddingRight(), insets.bottom);
if (insets.bottom > 0) {
setupNavBarColor();
} else {
clearNavBarColor();
}
((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthUsed;
if (mInsets.bottom > 0) {
widthUsed = 0;
} else {
Rect padding = mLauncher.getDeviceProfile().workspacePadding;
widthUsed = Math.max(padding.left + padding.right,
2 * (mInsets.left + mInsets.right));
}
int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx;
measureChildWithMargins(mContent, widthMeasureSpec,
widthUsed, heightMeasureSpec, heightUsed);
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
// Content is laid out as center bottom aligned
int contentWidth = mContent.getMeasuredWidth();
int contentLeft = (width - contentWidth) / 2;
mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
contentLeft + contentWidth, height);
setTranslationShift(mTranslationShift);
}
@Override
public void notifyWidgetProvidersChanged() {
mLauncher.refreshAndBindWidgetsForPackageUser(null);
}
@Override
protected void onWidgetsBound() {
mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets());
}
private void open(boolean animate) {
if (animate) {
if (mLauncher.getDragLayer().getInsets().bottom > 0) {
mContent.setAlpha(0);
setTranslationShift(VERTICAL_START_POSITION);
}
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator
.setDuration(DEFAULT_OPEN_DURATION)
.setInterpolator(AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.linear_out_slow_in));
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRecyclerView.setLayoutFrozen(false);
mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
mOpenCloseAnimator.removeListener(this);
}
});
post(() -> {
mRecyclerView.setLayoutFrozen(true);
mOpenCloseAnimator.start();
mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
});
} else {
setTranslationShift(TRANSLATION_SHIFT_OPENED);
mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
post(this::announceAccessibilityChanges);
}
}
@Override
protected void handleClose(boolean animate) {
handleClose(animate, DEFAULT_OPEN_DURATION);
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
// Disable swipe down when recycler view is scrolling
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = false;
RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
if (scroller.getThumbOffsetY() >= 0 &&
mLauncher.getDragLayer().isEventOverView(scroller, ev)) {
mNoIntercept = true;
} else if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
}
}
return super.onControllerInterceptTouchEvent(ev);
}
public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
.inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
sheet.mIsOpen = true;
launcher.getDragLayer().addView(sheet);
sheet.open(animate);
return sheet;
}
@Override
protected int getElementsRowCount() {
return mAdapter.getItemCount();
}
}
由以上代碼可以看出 WidgetsFullSheet 是一個(gè)自定義的view ,構(gòu)造方法中 mAdapter = new WidgetsListAdapter(); 在show(Launcher launcher, boolean animate) 方法中 inflate(R.layout.widgets_full_sheet,,) 布局文件;在 onFinishInflate() 方法中 mRecyclerView = findViewById(R.id.widgets_list_view); mRecyclerView.setAdapter(mAdapter); 最后由之前所說的
onWidgetsBound() 方法中 adapter 設(shè)置數(shù)據(jù)。
R.layout.widgets_full_sheet 文件
<com.android.launcher3.widget.WidgetsFullSheet
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:theme="?attr/widgetsTheme" >
<com.android.launcher3.views.TopRoundedCornerView
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorPrimary"
android:elevation="4dp">
<com.android.launcher3.widget.WidgetsRecyclerView
android:id="@+id/widgets_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
<!-- Fast scroller popup -->
<TextView
android:id="@+id/fast_scroller_popup"
style="@style/FastScrollerPopup"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
<com.android.launcher3.views.RecyclerViewFastScroller
android:id="@+id/fast_scroller"
android:layout_width="@dimen/fastscroll_width"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="@dimen/fastscroll_end_margin" />
</com.android.launcher3.views.TopRoundedCornerView>
</com.android.launcher3.widget.WidgetsFullSheet>
- WidgetsListAdapter 就是一個(gè)recycleView的adapter ,onCreateViewHolder()中
inflate(R.layout.widgets_list_row_view, parent, false) 布局文件如下
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:id="@+id/widgets_cell_list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:focusable="true"
android:descendantFocusability="afterDescendants">
<!-- Section info -->
<com.android.launcher3.BubbleTextView
android:id="@+id/section"
android:layout_width="match_parent"
android:layout_height="@dimen/widget_section_height"
android:background="?android:attr/colorPrimary"
android:drawablePadding="@dimen/widget_section_horizontal_padding"
android:focusable="true"
android:gravity="start|center_vertical"
android:paddingBottom="@dimen/widget_section_vertical_padding"
android:paddingLeft="@dimen/widget_section_horizontal_padding"
android:paddingRight="@dimen/widget_section_horizontal_padding"
android:paddingTop="@dimen/widget_section_vertical_padding"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:textAlignment="viewStart"
launcher:iconDisplay="widget_section"
launcher:iconSizeOverride="@dimen/widget_section_icon_size"
launcher:layoutHorizontal="true" />
<include layout="@layout/widgets_scroll_container" />
</LinearLayout>
widgets_scroll_container.xml 文件
<HorizontalScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widgets_scroll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/colorPrimaryDark"
android:scrollbars="none">
<LinearLayout
android:id="@+id/widgets_cell_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:orientation="horizontal"
android:showDividers="none"/>
</HorizontalScrollView>
然后 看 onBindViewHolder 方法
@Override
public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
WidgetListRowEntry entry = mEntries.get(pos);
List<WidgetItem> infoList = entry.widgets;
ViewGroup row = holder.cellContainer;
if (DEBUG) {
Log.d(TAG, String.format(
"onBindViewHolder [pos=%d, widget#=%d, row.getChildCount=%d]",
pos, infoList.size(), row.getChildCount()));
}
// Add more views.
// if there are too many, hide them.
int expectedChildCount = infoList.size() + Math.max(0, infoList.size() - 1);
int childCount = row.getChildCount();
if (expectedChildCount > childCount) {
for (int i = childCount ; i < expectedChildCount; i++) {
if ((i & 1) == 1) {
// Add a divider for odd index
mLayoutInflater.inflate(R.layout.widget_list_divider, row);
} else {
// Add cell for even index
WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
R.layout.widget_cell, row, false);
// set up touch.
widget.setOnClickListener(mIconClickListener);
widget.setOnLongClickListener(mIconLongClickListener);
row.addView(widget);
}
}
} else if (expectedChildCount < childCount) {
for (int i = expectedChildCount ; i < childCount; i++) {
row.getChildAt(i).setVisibility(View.GONE);
}
}
// Bind the views in the application info section.
holder.title.applyFromPackageItemInfo(entry.pkgItem);
// Bind the view in the widget horizontal tray region.
for (int i=0; i < infoList.size(); i++) {
WidgetCell widget = (WidgetCell) row.getChildAt(2*i);
widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
widget.ensurePreview();
widget.setVisibility(View.VISIBLE);
if (i > 0) {
row.getChildAt(2*i - 1).setVisibility(View.VISIBLE);
}
}
}
以單個(gè)應(yīng)用進(jìn)行分組 作為一級(jí)菜單,一個(gè)應(yīng)用可能多個(gè) wideget 作為二級(jí)菜單,用WidgetCell作為容器放置于HorizontalScrollView中 橫向滾動(dòng);for 循環(huán)中 獲取每一個(gè)WidgetCell調(diào)用applyFromCellItem() 和 ensurePreview() 。
-
WidgetCell 作為單個(gè)widget顯示的容器,applyFromCellItem()設(shè)置數(shù)據(jù),ensurePreview()中加載預(yù)覽圖
image.png
public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
mItem = item;
mWidgetName.setText(mItem.label);
mWidgetDims.setText(getContext().getString(R.string.widget_dims_format,
mItem.spanX, mItem.spanY));
mWidgetDims.setContentDescription(getContext().getString(
R.string.widget_accessible_dims_format, mItem.spanX, mItem.spanY));
mWidgetPreviewLoader = loader;
if (item.activityInfo != null) {
setTag(new PendingAddShortcutInfo(item.activityInfo));
} else {
setTag(new PendingAddWidgetInfo(item.widgetInfo));
}
}
public void ensurePreview() {
if (mActiveRequest != null) {
return;
}
mActiveRequest = mWidgetPreviewLoader.getPreview(
mItem, mPresetPreviewSize, mPresetPreviewSize, this);
}
- 其中 預(yù)覽圖 交由WidgetPreviewLoader類中處理 ,其中PreviewLoadTask 是一個(gè) AsyncTask ,從緩存池中獲取 空白的bitmap ,從數(shù)據(jù)庫中獲取預(yù)覽圖數(shù)據(jù) 賦值給 空白的bitmap ,最后調(diào)用 mCaller.applyPreview(preview);
public CancellationSignal getPreview(WidgetItem item, int previewWidth,
int previewHeight, WidgetCell caller) {
String size = previewWidth + "x" + previewHeight;
WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
CancellationSignal signal = new CancellationSignal();
signal.setOnCancelListener(task);
return signal;
}
public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap>
implements CancellationSignal.OnCancelListener {
@Thunk final WidgetCacheKey mKey;
private final WidgetItem mInfo;
private final int mPreviewHeight;
private final int mPreviewWidth;
private final WidgetCell mCaller;
private final BaseActivity mActivity;
@Thunk long[] mVersions;
@Thunk Bitmap mBitmapToRecycle;
PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth,
int previewHeight, WidgetCell caller) {
mKey = key;
mInfo = info;
mPreviewHeight = previewHeight;
mPreviewWidth = previewWidth;
mCaller = caller;
mActivity = BaseActivity.fromContext(mCaller.getContext());
if (DEBUG) {
Log.d(TAG, String.format("%s, %s, %d, %d",
mKey, mInfo, mPreviewHeight, mPreviewWidth));
}
}
@Override
protected Bitmap doInBackground(Void... params) {
Bitmap unusedBitmap = null;
// If already cancelled before this gets to run in the background, then return early
if (isCancelled()) {
return null;
}
synchronized (mUnusedBitmaps) {
// Check if we can re-use a bitmap
for (Bitmap candidate : mUnusedBitmaps) {
if (candidate != null && candidate.isMutable() &&
candidate.getWidth() == mPreviewWidth &&
candidate.getHeight() == mPreviewHeight) {
unusedBitmap = candidate;
mUnusedBitmaps.remove(unusedBitmap);
break;
}
}
}
// creating a bitmap is expensive. Do not do this inside synchronized block.
if (unusedBitmap == null) {
unusedBitmap = Bitmap.createBitmap(mPreviewWidth, mPreviewHeight, Config.ARGB_8888);
}
// If cancelled now, don't bother reading the preview from the DB
if (isCancelled()) {
return unusedBitmap;
}
Bitmap preview = readFromDb(mKey, unusedBitmap, this);
// Only consider generating the preview if we have not cancelled the task already
if (!isCancelled() && preview == null) {
// Fetch the version info before we generate the preview, so that, in-case the
// app was updated while we are generating the preview, we use the old version info,
// which would gets re-written next time.
boolean persistable = mInfo.activityInfo == null
|| mInfo.activityInfo.isPersistable();
mVersions = persistable ? getPackageVersion(mKey.componentName.getPackageName())
: null;
// it's not in the db... we need to generate it
preview = generatePreview(mActivity, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
}
return preview;
}
@Override
protected void onPostExecute(final Bitmap preview) {
mCaller.applyPreview(preview);
// Write the generated preview to the DB in the worker thread
if (mVersions != null) {
mWorkerHandler.post(new Runnable() {
@Override
public void run() {
if (!isCancelled()) {
// If we are still using this preview, then write it to the DB and then
// let the normal clear mechanism recycle the bitmap
writeToDb(mKey, mVersions, preview);
mBitmapToRecycle = preview;
} else {
// If we've already cancelled, then skip writing the bitmap to the DB
// and manually add the bitmap back to the recycled set
synchronized (mUnusedBitmaps) {
mUnusedBitmaps.add(preview);
}
}
}
});
} else {
// If we don't need to write to disk, then ensure the preview gets recycled by
// the normal clear mechanism
mBitmapToRecycle = preview;
}
}
@Override
protected void onCancelled(final Bitmap preview) {
// If we've cancelled while the task is running, then can return the bitmap to the
// recycled set immediately. Otherwise, it will be recycled after the preview is written
// to disk.
if (preview != null) {
mWorkerHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mUnusedBitmaps) {
mUnusedBitmaps.add(preview);
}
}
});
}
}
@Override
public void onCancel() {
cancel(true);
// This only handles the case where the PreviewLoadTask is cancelled after the task has
// successfully completed (including having written to disk when necessary). In the
// other cases where it is cancelled while the task is running, it will be cleaned up
// in the tasks's onCancelled() call, and if cancelled while the task is writing to
// disk, it will be cancelled in the task's onPostExecute() call.
if (mBitmapToRecycle != null) {
mWorkerHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mUnusedBitmaps) {
mUnusedBitmaps.add(mBitmapToRecycle);
}
mBitmapToRecycle = null;
}
});
}
}
}
public void applyPreview(Bitmap bitmap) {
if (mApplyBitmapDeferred) {
mDeferredBitmap = bitmap;
return;
}
if (bitmap != null) {
mWidgetImage.setBitmap(bitmap,
DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext()));
if (mAnimatePreview) {
mWidgetImage.setAlpha(0f);
ViewPropertyAnimator anim = mWidgetImage.animate();
anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
} else {
mWidgetImage.setAlpha(1f);
}
}
}
自此,整個(gè) widget的 預(yù)覽列表數(shù)據(jù)加載完成。
