Laucnher3有关AppWidget的源码分析(Android 9.0)

前言

Launcher在Android的AppWidget整个体系中扮演AppWidgetHost的角色,本文分析Launcher对于AppWidget的处理 部分源码分析。

一、AppWidget加载流程

1. Android系统启动,SystemServer创建AppWidgetService,并调用systemReady()方法,在systemReady()方法中做以下三项准备工作:

  1. 通过PackageManager从Android系统中查找所有已经被安装的AppWidget(包含"android.appwidget.action.APPWIDGET_UPDATE“ 的Action和meta-data标签),解析AppWidget的配置信息,封闭成对象,保存到List集合。
  2. 从/data/system/users/0/appwidgets.xml文件读取已经被添加到Launcher的AppWidget信息,封闭成对象,保存到List集合中。
  3. 注册四个广播接收器:第一. Android系统启动完成,第二. Android配置信息改变,第三. 添加删除应用,第四. sdcard的安装与缷载。

2. Android系统启动Launcher应用程序,会做以下准备工作:

  1. 从Launcher应用的数据库查找已经被添加到Launcher的AppWidget信息。
  2. 根据查找到的appWidgetId值(整型值)创建LauncherAppWidgetHostView布局对象。
  3. 根据查找到的appWidgetId值(整型值)从AppWidgetService中获取RemoteViews对象(因为是第一次启动所以RemoteViews对象为空)。
  4. 将获取到的RemoteViews对象的布局解析并设置到第(2)步中创建的LauncherAppWidgetHostView布局对象中。
  5. 将LauncherAppWidgetHostView布局对象添加到Launcher的WorkSpace中(因为RemoteViews对象为空,所以只在Launcher的 WorkSpace中占了一个位置)。

3. Android系统启动完成,发出BOOT_COMPLETED广播,AppWidgetService接收到广播后,会做以下事情:

  1. 获取已经添加到Launcher的AppWidget列表,依次向这个Widget发出APPWIDGET_ENABLED和 APPWIDGET_UPDATE更新广播,根据配置的更新间隔定时发出更新广播。
  2. 每个AppWidget接收到广播后都会调用onEnabled()方法和onUpdate()方法,在onEnabled()方法中进行一些初始化操作,在onUpdate()方法中创建RemoteViews布局对象并通过AppWidgetManager的updateAppWidget(int appWidgetId, RemoteViews remoteViews)方法通知AppWidgetService对象用RemoteViews对象更新appWidgetId所对应的AppWidget。
  3. AppWidgetService接收到了appWidgetId和RemoteViews后,通过appWidgetId查找已经被添加到Launcher的LauncherAppWidgetHostView布局对象,并RemoteViews中的布局更新到LauncherAppWidgetHostView布局对象中。AppWidget显示在Launcher中。

二、AppWidget 预览界面的加载流程

1. 操作流程

image.png

如图,长按空白区域出现弹框 ,点击 widgets选选项 进入下图 widget预览页面


image.png

2. 显示流程源码分析

  1. Workspace 的构造方法中 设置了 setOnTouchListener 交给 WorkspaceTouchListener类处理
 public Workspace(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mLauncher = Launcher.getLauncher(context);
        ....
        //设置 触摸事件
        setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
    }
public class WorkspaceTouchListener implements OnTouchListener, Runnable {
    ...
}
  1. WorkspaceTouchListener 中的 onTouch 中的 mWorkspace.postDelayed(this, getLongPressTimeout()); 自己实现的 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());
                  
                // 长按点击事件 
                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();
            }
        }
    }
  1. OptionsPopupView 中添加选项,根据触摸坐标设置显示的区域 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 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 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());
    }
  1. 其中注意 OptionsPopupView::onWidgetsClicked 这个是 点击事件 显示 WidgetsFullSheet(widget的预览界面)
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 绑定流程

  1. 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);
        }
      ...
}
  1. LauncherModel类中 startLoader 方法中 主要关注 loaderResults.bindWidgets() ,其通过
    callbacks.bindAllWidgets(widgets) ,回调到 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数据 回调到 launcher中

public void bindWidgets() {
        final ArrayList 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);
    }
  1. 向 PopupDataProvider类中设置 widget 数据,调用 AbstractFloatingView.onWidgetsBound();
  Launcher.java

    @Override
    public void bindAllWidgets(final ArrayList allWidgets) {
        mPopupDataProvider.setAllWidgets(allWidgets);
        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
        if (topView != null) {
            topView.onWidgetsBound();
        }
    }

前面也讲到过 widget的预览视图由 WidgetsFullSheet 显示 而WidgetsFullSheet是 AbstractFloatingView的子类,所以最终调用了WidgetsFullSheet.onWidgetsBound()。


image.png
WidgetsFullSheet.java

 @Override
    protected void onWidgetsBound() {
      // 从PopupDataProvider类中取出之前设置进去的数据,填充adapter
        mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets());
    }

  1. 总结上面,widget数据最终 交由 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 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 是一个自定义的view ,构造方法中 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 设置数据。

R.layout.widgets_full_sheet 文件



    

        

        
        

        
    

  1. WidgetsListAdapter 就是一个recycleView的adapter ,onCreateViewHolder()中
    inflate(R.layout.widgets_list_row_view, parent, false) 布局文件如下


    

    

    

widgets_scroll_container.xml 文件


    

然后 看 onBindViewHolder 方法

@Override
    public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
        WidgetListRowEntry entry = mEntries.get(pos);
        List 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);
            }
        }
    }

以单个应用进行分组 作为一级菜单,一个应用可能多个 wideget 作为二级菜单,用WidgetCell作为容器放置于HorizontalScrollView中 横向滚动;for 循环中 获取每一个WidgetCell调用applyFromCellItem() 和 ensurePreview() 。

  1. WidgetCell 作为单个widget显示的容器,applyFromCellItem()设置数据,ensurePreview()中加载预览图


    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);
    }
  1. 其中 预览图 交由WidgetPreviewLoader类中处理 ,其中PreviewLoadTask 是一个 AsyncTask ,从缓存池中获取 空白的bitmap ,从数据库中获取预览图数据 赋值给 空白的bitmap ,最后调用 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
            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);
            }
        }
    }

自此,整个 widget的 预览列表数据加载完成。

你可能感兴趣的:(Laucnher3有关AppWidget的源码分析(Android 9.0))