Android原生Launcher3简要分析

故事扮演 提交于 2019-12-09 11:08:03

Launcher是android手机启动后第一个看到的界面,即手机系统的桌面,下面我们就以android原生的Launcher3为例看看界面布局和显示的数据怎么获取的来简要分析下android手机桌面

Launcher中第一显示的Activity为Launcher.java,下面我们主要看看这个布局文件launcher.xml


//packages/apps/Launcher3/res/layout-land/launcher.xml
<!-- Full screen view projects under the status bar and contains the background -->
<com.android.launcher3.LauncherRootView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto"
    android:id="@+id/launcher"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
 
    <com.android.launcher3.dragndrop.DragLayer
        android:id="@+id/drag_layer"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:background="@drawable/workspace_bg"
        android:importantForAccessibility="no"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
 
        <!-- The workspace contains 5 screens of cells -->
        <!-- DO NOT CHANGE THE ID -->
        <com.android.launcher3.Workspace
            android:id="@+id/workspace"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            launcher:pageIndicator="@id/page_indicator" />
 
        <!-- DO NOT CHANGE THE ID -->
        <include layout="@layout/hotseat"
            android:id="@+id/hotseat"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="right"
            launcher:layout_ignoreInsets="true" />
 
        <include
            android:id="@+id/drop_target_bar"
            layout="@layout/drop_target_bar_vert" />
 
        <include layout="@layout/overview_panel"
            android:id="@+id/overview_panel"
            android:visibility="gone" />
 
        <com.android.launcher3.pageindicators.PageIndicatorCaretLandscape
            android:id="@+id/page_indicator"
            android:layout_width="@dimen/dynamic_grid_page_indicator_height"
            android:layout_height="@dimen/dynamic_grid_page_indicator_height"
            android:layout_gravity="bottom|left"/>
 
        <!-- A place holder view instead of the QSB in transposed layout -->
        <View
            android:layout_width="0dp"
            android:layout_height="10dp"
            android:id="@+id/workspace_blocked_row" />
 
        <include layout="@layout/widgets_view"
            android:id="@+id/widgets_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="invisible" />
 
        <include layout="@layout/all_apps"
            android:id="@+id/apps_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="invisible" />
 
    </com.android.launcher3.dragndrop.DragLayer>
 
</com.android.launcher3.LauncherRootView>
接着看下对应的UI显示,也可以对着上面的布局看看

这个界面显示的主要为Shortcut和Widget,其中最下面一行的几个Shortcut比较特殊,都设置了setIsHotseat属性,在具体显示的时候会根据来判断是不是显示在最下一行,另外显示的个数也在dw_phone_hotseat.xml有默认配置的,当用户还想再这个里面放入其它apk快捷方式时,会根据除去已显示的图标后剩余空间来决定是单独显示图标,还是和其它apk图标放在一个文件夹里显示。
所有的这些数据都会放入到数据库launcher.db中保存。

Hotseat为FrameLayout的子类,通过Launcher里的getDeviceProfile获取DeviceProfile对象,里面包含特定设备的一些配置文件

在InvariantDeviceProfile.java中通过getPredefinedDeviceProfiles来获取所有的device_profiles


//packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
    ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles(Context context) {
        ArrayList<InvariantDeviceProfile> profiles = new ArrayList<>();
        try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
            final int depth = parser.getDepth();
            int type;
 
            while (((type = parser.next()) != XmlPullParser.END_TAG ||
                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
                if ((type == XmlPullParser.START_TAG) && "profile".equals(parser.getName())) {
                    TypedArray a = context.obtainStyledAttributes(
                            Xml.asAttributeSet(parser), R.styleable.InvariantDeviceProfile);
                    int numRows = a.getInt(R.styleable.InvariantDeviceProfile_numRows, 0);
                    int numColumns = a.getInt(R.styleable.InvariantDeviceProfile_numColumns, 0);
                    float iconSize = a.getFloat(R.styleable.InvariantDeviceProfile_iconSize, 0);
                    profiles.add(new InvariantDeviceProfile(
                            a.getString(R.styleable.InvariantDeviceProfile_name),
                            a.getFloat(R.styleable.InvariantDeviceProfile_minWidthDps, 0),
                            a.getFloat(R.styleable.InvariantDeviceProfile_minHeightDps, 0),
                            numRows,
                            numColumns,
                            a.getInt(R.styleable.InvariantDeviceProfile_numFolderRows, numRows),
                            a.getInt(R.styleable.InvariantDeviceProfile_numFolderColumns, numColumns),
                            a.getInt(R.styleable.InvariantDeviceProfile_minAllAppsPredictionColumns, numColumns),
                            iconSize,
                            a.getFloat(R.styleable.InvariantDeviceProfile_iconTextSize, 0),
                            a.getInt(R.styleable.InvariantDeviceProfile_numHotseatIcons, numColumns),
                            a.getFloat(R.styleable.InvariantDeviceProfile_hotseatIconSize, iconSize),
                            a.getResourceId(R.styleable.InvariantDeviceProfile_defaultLayoutId, 0)));
                    a.recycle();
                }
            }
        } catch (IOException|XmlPullParserException e) {
            throw new RuntimeException(e);
        }
        return profiles;
    }
解析device_profiles.xml读取获得ArrayList<InvariantDeviceProfile> profiles,最后根据手机屏幕宽高调用findClosestDeviceProfiles和invDistWeightedInterpolate获取合适的InvariantDeviceProfile,InvariantDeviceProfile里包含了每行每列允许显示的个数等配置

packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
    ArrayList<InvariantDeviceProfile> findClosestDeviceProfiles(
            final float width, final float height, ArrayList<InvariantDeviceProfile> points) {
 
        // Sort the profiles by their closeness to the dimensions
        ArrayList<InvariantDeviceProfile> pointsByNearness = points;
        Collections.sort(pointsByNearness, new Comparator<InvariantDeviceProfile>() {
            public int compare(InvariantDeviceProfile a, InvariantDeviceProfile b) {
                return Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
                        dist(width, height, b.minWidthDps, b.minHeightDps));
            }
        });
 
        return pointsByNearness;
    }
下面简单贴了个device_profiles.xml的配置

/packages/apps/Launcher3/res/xml/device_profiles.xml
<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
    <profile
        launcher:name="Nexus 4"
        launcher:minWidthDps="359"
        launcher:minHeightDps="567"
        launcher:numRows="4"
        launcher:numColumns="4"
        launcher:numFolderRows="4"
        launcher:numFolderColumns="4"
        launcher:minAllAppsPredictionColumns="4"
        launcher:iconSize="60"
        launcher:iconTextSize="13.0"
        launcher:numHotseatIcons="5"
        launcher:hotseatIconSize="56"
        launcher:defaultLayoutId="@xml/default_workspace_4x4"
        />
        ...
</profiles>
default_workspace_4x4.xml

packages/apps/Launcher3/res/xml/default_workspace_4x4.xml
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
 
    <!-- Hotseat -->
    <include launcher:workspace="@xml/dw_phone_hotseat" />
 
    <!-- Bottom row -->
    <resolve
        launcher:screen="0"
        launcher:x="0"
        launcher:y="-1" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
        <favorite launcher:uri="mailto:" />
    </resolve>
 
    <resolve
        launcher:screen="0"
        launcher:x="1"
        launcher:y="-1" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
        <favorite launcher:uri="#Intent;type=images/*;end" />
    </resolve>
 
    <resolve
        launcher:screen="0"
        launcher:x="3"
        launcher:y="-1" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
        <favorite launcher:uri="market://details?id=com.android.launcher" />
    </resolve>
 
</favorites>
dw_phone_hotseat.xml

packages/apps/Launcher3/res/xml/dw_phone_hotseat.xml
<?xml version="1.0" encoding="utf-8"?>
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
    <!-- Dialer, Messaging, [All Apps], Browser, Camera -->
    <resolve
        launcher:container="-101"
        launcher:screen="0"
        launcher:x="0"
        launcher:y="0" >
        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
        <favorite launcher:uri="tel:123" />
        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
    </resolve>
 
    <resolve
        launcher:container="-101"
        launcher:screen="1"
        launcher:x="1"
        launcher:y="0" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
        <favorite launcher:uri="sms:" />
        <favorite launcher:uri="smsto:" />
        <favorite launcher:uri="mms:" />
        <favorite launcher:uri="mmsto:" />
    </resolve>
 
    <!-- All Apps -->
 
    <resolve
        launcher:container="-101"
        launcher:screen="3"
        launcher:x="3"
        launcher:y="0" >
        <favorite
            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
        <favorite launcher:uri="http://www.example.com/" />
    </resolve>
 
    <resolve
        launcher:container="-101"
        launcher:screen="4"
        launcher:x="4"
        launcher:y="0" >
        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
    </resolve>
 
</favorites>
其中Favorites和Workspaces都会保存在launcher.db里的favorites和workspaceScreens表里,上面只是默认显示的,当用户手动把某个应用放入这块时,会根据当前的剩余空间,来决定是单独显示一个icon还是和另一个图标一起显示在一个文件夹里,通过default_workspace_4x4.xml可知道当前主屏幕上默认配置显示那些apk,当然所有这样配置都会保存在数据库中,这样当用户拖拽某个apk后,都会写入到相应数据库中的,下面只是贴了下把默认配置写入数据库的逻辑

//packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java
    synchronized private void loadDefaultFavoritesIfNecessary() {
        SharedPreferences sp = Utilities.getPrefs(getContext());
 
        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
            Log.d(TAG, "loading default workspace");
 
            AppWidgetHost widgetHost = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID);
            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
            if (loader == null) {
                loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
            }
            if (loader == null) {
                final Partner partner = Partner.get(getContext().getPackageManager());
                if (partner != null && partner.hasDefaultLayout()) {
                    final Resources partnerRes = partner.getResources();
                    int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                            "xml", partner.getPackageName());
                    if (workspaceResId != 0) {
                        loader = new DefaultLayoutParser(getContext(), widgetHost,
                                mOpenHelper, partnerRes, workspaceResId);
                    }
                }
            }
 
            final boolean usingExternallyProvidedLayout = loader != null;
            if (loader == null) {
                loader = getDefaultLayoutParser(widgetHost);
            }
 
            // There might be some partially restored DB items, due to buggy restore logic in
            // previous versions of launcher.
            createEmptyDB();
            // Populate favorites table with initial favorites
            if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                    && usingExternallyProvidedLayout) {
                // Unable to load external layout. Cleanup and load the internal layout.
                createEmptyDB();
                mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                        getDefaultLayoutParser(widgetHost));
            }
            clearFlagEmptyDbCreated();
        }
    }
说了这么多,先来看看主界面的显示图,再来看看所有桌面apk的信息是怎么获取,调用getActivityList获取的,其中第一个参数packageName传入的是null,查询所有配置了intent Action为ACTION_MAIN,Category为CATEGORY_LAUNCHER的应用集合列表

//packages/apps/Launcher3/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
    public List<LauncherActivityInfoCompat> getActivityList(String packageName,
            UserHandleCompat user) {
        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        mainIntent.setPackage(packageName);
        List<ResolveInfo> infos = mPm.queryIntentActivities(mainIntent, 0);
        List<LauncherActivityInfoCompat> list =
                new ArrayList<LauncherActivityInfoCompat>(infos.size());
        for (ResolveInfo info : infos) {
            list.add(new LauncherActivityInfoCompatV16(mContext, info));
        }
        return list;
    }
进入主界面后就是一个AllAppsContainerView,继承FrameLayout,在构造方法中设置的setAdapter为AllAppsGridAdapter AllAppsGridAdapter extends RecyclerView.Adapter,并且在这里调用了mAppsRecyclerView.setApps(mApps)传入了所有的apps 信息

 //packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java
   @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        ...
        // Load the all apps recycler view
        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
        mAppsRecyclerView.setApps(mApps);
        mAppsRecyclerView.setLayoutManager(mLayoutManager);
        mAppsRecyclerView.setAdapter(mAdapter);
        mAppsRecyclerView.setHasFixedSize(true);
        mAppsRecyclerView.addOnScrollListener(mElevationController);
        mAppsRecyclerView.setElevationController(mElevationController);
     ...
    }
 在AllAppsGridAdapter调用onCreateViewHolder和onBindViewHolder来显示view

 //packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
  public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case VIEW_TYPE_SECTION_BREAK:
                return new ViewHolder(new View(parent.getContext()));
            case VIEW_TYPE_ICON:
                /* falls through */
            case VIEW_TYPE_PREDICTION_ICON: {
                BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                        R.layout.all_apps_icon, parent, false);
                icon.setOnClickListener(mIconClickListener);  //设置每个控件的点击事件监听,每个桌面图标都是一个自定义的BubbleTextView
                icon.setOnLongClickListener(mIconLongClickListener);
                icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext())
                        .getLongPressTimeout());
                icon.setOnFocusChangeListener(mIconFocusListener);
 
                // Ensure the all apps icon height matches the workspace icons
                DeviceProfile profile = mLauncher.getDeviceProfile();
                Point cellSize = profile.getCellSize();
                GridLayoutManager.LayoutParams lp =
                        (GridLayoutManager.LayoutParams) icon.getLayoutParams();
                lp.height = cellSize.y;
                icon.setLayoutParams(lp);
                return new ViewHolder(icon);
            }
            case VIEW_TYPE_EMPTY_SEARCH:
                return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
                        parent, false));
            case VIEW_TYPE_SEARCH_MARKET:
                View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
                        parent, false);
                searchMarketView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mLauncher.startActivitySafely(v, mMarketSearchIntent, null);
                    }
                });
                return new ViewHolder(searchMarketView);
            case VIEW_TYPE_SEARCH_DIVIDER:
                return new ViewHolder(mLayoutInflater.inflate(
                        R.layout.all_apps_search_divider, parent, false));
            case VIEW_TYPE_PREDICTION_DIVIDER:
                /* falls through */
            case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
                return new ViewHolder(mLayoutInflater.inflate(
                        R.layout.all_apps_divider, parent, false));
            default:
                throw new RuntimeException("Unexpected view type");
        }
    @Override 
    public void onBindViewHolder(ViewHolder holder, int position) {
        switch (holder.getItemViewType()) {
            case VIEW_TYPE_ICON: {
                AppInfo info = mApps.getAdapterItems().get(position).appInfo;
                BubbleTextView icon = (BubbleTextView) holder.mContent;
                icon.applyFromApplicationInfo(info);
                icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
                break;
            }
            case VIEW_TYPE_PREDICTION_ICON: {
                AppInfo info = mApps.getAdapterItems().get(position).appInfo;
                BubbleTextView icon = (BubbleTextView) holder.mContent;
                icon.applyFromApplicationInfo(info);
                icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
                break;
            }
            case VIEW_TYPE_EMPTY_SEARCH:
                TextView emptyViewText = (TextView) holder.mContent;
                emptyViewText.setText(mEmptySearchMessage);
                emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
                        Gravity.START | Gravity.CENTER_VERTICAL);
                break;
            case VIEW_TYPE_SEARCH_MARKET:
                TextView searchView = (TextView) holder.mContent;
                if (mMarketSearchIntent != null) {
                    searchView.setVisibility(View.VISIBLE);
                } else {
                    searchView.setVisibility(View.GONE);
                }
                break;
        }
        if (mBindViewCallback != null) {
            mBindViewCallback.onBindView(holder);
        }
    }   
 下面再来看看AllAppsGridAdapter构造方法,里面传入了View.OnClickListener和View.OnLongClickListener

    public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener
            iconClickListener, View.OnLongClickListener iconLongClickListener) {
        ...
    }
AllAppsGridAdapter是在AllAppsContainerView的构造方法中初始化的,如下:

      public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Resources res = context.getResources();
 
        mLauncher = Launcher.getLauncher(context);
        mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
        mApps = new AlphabeticalAppsList(context);
        mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
        mApps.setAdapter(mAdapter);
        mLayoutManager = mAdapter.getLayoutManager();
        mItemDecoration = mAdapter.getItemDecoration();
        DeviceProfile grid = mLauncher.getDeviceProfile();
        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !grid.isVerticalBarLayout()) {
            mRecyclerViewBottomPadding = 0;
            setPadding(0, 0, 0, 0);
        } else {
            mRecyclerViewBottomPadding =
                    res.getDimensionPixelSize(R.dimen.all_apps_list_bottom_padding);
        }
        mSearchQueryBuilder = new SpannableStringBuilder();
        Selection.setSelection(mSearchQueryBuilder, 0);
    }
可以看到AllAppsGridAdapter的View.OnClickListener来自mLauncher即Launcher.java,View.OnLongClickListener传入的是this,即在本类的onLongClick中处理,这样即所有item的click事件都在Launcher的onClick处理

//packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
    public void onClick(View v) {
        // Make sure that rogue clicks don't get through while allapps is launching, or after the
        // view has detached (it's possible for this to happen if the view is removed mid touch).
        if (v.getWindowToken() == null) {
            return;
        }
 
        if (!mWorkspace.isFinishedSwitchingState()) {
            return;
        }
 
        if (v instanceof Workspace) {
            if (mWorkspace.isInOverviewMode()) {
                showWorkspace(true);
            }
            return;
        }
 
        if (v instanceof CellLayout) {
            if (mWorkspace.isInOverviewMode()) {
                mWorkspace.snapToPageFromOverView(mWorkspace.indexOfChild(v));
                showWorkspace(true);
            }
            return;
        }
 
        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            onClickAppShortcut(v);
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                onClickFolderIcon(v);
            }
        } else if ((FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && v instanceof PageIndicator) ||
                (v == mAllAppsButton && mAllAppsButton != null)) {
            onClickAllAppsButton(v);
        } else if (tag instanceof AppInfo) {//如果是apk调用startAppShortcutOrInfoActivity,启动对应Activity
            startAppShortcutOrInfoActivity(v);
        } else if (tag instanceof LauncherAppWidgetInfo) {
            if (v instanceof PendingAppWidgetHostView) {
                onClickPendingWidget((PendingAppWidgetHostView) v);
            }
        }
    }

当点击每个应用的icon后,就会调用startAppShortcutOrInfoActivity(View v)

    private void startAppShortcutOrInfoActivity(View v) {
        ItemInfo item = (ItemInfo) v.getTag();
        Intent intent = item.getIntent();
        if (intent == null) {
            throw new IllegalArgumentException("Input must have a valid intent");
        }
        boolean success = startActivitySafely(v, intent, item);
        getUserEventDispatcher().logAppLaunch(v, intent);
 
        if (success && v instanceof BubbleTextView) {
            mWaitingForResume = (BubbleTextView) v;
            mWaitingForResume.setStayPressed(true);
        }
    }
在这个方法里接着调用本类的startActivitySafely,最终在这个LauncherAppsCompat.getInstance(this).startActivityForProfile

       public void startActivityForProfile(ComponentName component, UserHandleCompat user,
            Rect sourceBounds, Bundle opts) {
        Intent launchIntent = new Intent(Intent.ACTION_MAIN);
        launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        launchIntent.setComponent(component);
        launchIntent.setSourceBounds(sourceBounds);
        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(launchIntent, opts);
    }
这样每点击一个应该icon就会启动一个对应的Activity,本开始只是想看看点击apk启动应用的流程,找到apk icon点击事件找了半天,在这顺带记录下,当然还有很多从数据库获取桌面布局信息,更新布局信息啥的在这没有提到主要是以apk信息的获取和icon单击事件来分析的,当手机里有多个应用了下面两个属性时,就会看到会让用户选择用那个桌面

<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
下面简单总结下桌面的显示流程
1、手机启动后会先启动桌面,在Launcher的数据库中会有每个apk显示位置的详细信息,具体是以坐标的方式。
2、通过getActivityList查询所有需要显示在桌面的apk信息,并返回一个集合。
3、主界面是一个自定义AllAppsRecyclerView继承RecyclerView,设置了AllAppsGridAdapter会根据上面list集合的size创建多少个BubbleTextView(即看到的桌面图标)。
 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!