美文网首页
Laucnher3有关AppWidget的源码分析(Androi

Laucnher3有关AppWidget的源码分析(Androi

作者: 眼中有码 | 来源:发表于2022-06-10 09:37 被阅读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<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());
        }
    
    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<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);
        }
    
    1. 向 PopupDataProvider类中设置 widget 数据,调用 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的预览视图由 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<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 是一个自定义的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 文件

    <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>
    
    1. WidgetsListAdapter 就是一个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);
                }
            }
        }
    
    

    以单个应用进行分组 作为一级菜单,一个应用可能多个 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<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);
                }
            }
        }
    

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

    相关文章

      网友评论

          本文标题:Laucnher3有关AppWidget的源码分析(Androi

          本文链接:https://www.haomeiwen.com/subject/qpxmmrtx.html