Android Launcher3分析——开篇

Android Launcher3分析——开篇

简介

Launcher就是一个Activity,Launcher的源码中也是继承的Activity。直观体现就是手机的桌面,当我们打开手机的时候,手机的桌面就是Launcher,一个Activity,只是这个Activity做的事情比较多:

  • View方面,可以左右滑动,可以响应长按操作;
  • 逻辑方面,可以承载手机中所有应用的快捷方式,是其他程序的入口;

总的来说,Launcher就是一个包含了许多自定义控件的复杂Activity

整体上看Launcher3

Android四大组件一应俱全,可见Launcher3是一个综合性较强的项目

Activity(6个)

  • com.android.launcher3.Launcher(主要的Activity)
  • com.android.launcher3.ToggleWeightWatcher
  • com.android.launcher3.WallpaperPickerActivity
  • com.android.launcher3.WallpaperCropActivity
  • com.android.launcher3.SettingsActivity
  • com.android.launcher3.MemoryDumpActivity

Service(1个)

  • com.android.launcher3.MemoryTracker

BroadcastReceiver(4个)

  • com.android.launcher3.WallpaperChangedReceiver
  • com.android.launcher3.InstallShortcutReceiver
  • com.android.launcher3.AppWidgetsRestoredReceiver
  • com.android.launcher3.StartupReceiver

ContentProvider(1个)

  • com.android.launcher3.LauncherProvider

如何定义一个Launcher

Android定义一个Launcher很简单,要定义成Launcher的Activity代码如下:

<activity
    android:name="com.android.launcher3.Launcher"
    android:launchMode="singleTask"
    android:clearTaskOnLaunch="true"
    android:stateNotNeeded="true"
    android:theme="@style/Theme"
    android:windowSoftInputMode="adjustPan"
    android:screenOrientation="nosensor"
    android:resumeWhilePausing="true"
    android:taskAffinity=""
    android:enabled="true">
    <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.HOME" />
          <category android:name="android.intent.category.LAUNCHER" />
          <category android:name="android.intent.category.DEFAULT" />
          <category android:name="android.intent.category.MONKEY"/>
    </intent-filter>
</activity>

注意,上面intent-filter的具体含义:

  • android.intent.action.MAIN决定应用程序最先启动的Activity;
  • android.intent.category.HOME决定设备启动后第一个启动的Activity(通常要更改framework层的设置才能使之生效,因为国内系统定制化严重,各家手机厂商的ROM都提供了自己的Launcher)
  • android.intent.category.LAUNCHER决定应用程序是否显示在程序列表里;
  • android.intent.category.DEFAULT,决定可以接受隐式intent的Activity在没有传递intent-filter时是否能匹配成功,添加了该项的能匹配成功,反之匹配失败(当然,如果Activity时应用最先启动的Activity就不需要这个了)
  • android.intent.category.MONKEY,决定Activity是否能被monkey或其他自动化测试工具进行访问测试

先看Launcher对应的布局文件launcher.xml,如下:

launcher.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<!-- 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.DragLayer
        android:id="@+id/drag_layer"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.android.launcher3.FocusIndicatorView
            android:id="@+id/focus_indicator"
            android:layout_width="52dp"
            android:layout_height="52dp" />

        <!-- 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:defaultScreen="@integer/config_workspaceDefaultScreen" />

        <!-- 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" />

        <include
            android:id="@+id/search_drop_target_bar"
            layout="@layout/search_drop_target_bar" />

        <include layout="@layout/overview_panel"
            android:id="@+id/overview_panel"
            android:visibility="gone" />

        <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.DragLayer>

    <ViewStub
        android:id="@+id/launcher_overlay_stub"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:inflatedId="@+id/launcher_overlay"
        android:layout="@layout/launcher_overlay" />
</com.android.launcher3.LauncherRootView>

根布局层次结构:

LauncherRootView extends InsettableFrameLayout
|---DragLayer
    |---FocusIndicatorView
    |---Workspace
    |---hotset.xml(层级如下):
        |---Hotseat extends FramLayout
            |---CellLayout extends ViewGroup
    |---search_drop_target_bar.xml
        |---SearchDropTargetBar extends FrameLayout
            |---LinearLayout(orientation:horizontal)
                |---FrameLayout
                    |---DeleteDropTarget extends ButtonDropTarget(删除应用快捷方式)
                    FrameLayout
                    |---InfoDropTarget extends ButtonDropTarget(查看应用信息)
                    FrameLayout
                    |---UninstallDropTarget extends ButtonDropTarget(卸载应用)
    |---overview_panel.xml(长按屏幕显示,默认隐藏)
        |---LinearLayout
            |---TextView(壁纸按钮):点击可设置壁纸
            |---TextView(小部件按钮):点击可添加小部件(widget)
            |---TextView(设置按钮):点击进入设置
    |---widgets_view.xml(显示小部件的视图)
        |---WidgetsContainerView extends BaseContainerView extends LinearLayout
            |---FrameLayout
                |---FrameLayout
                |---WidgetsRecyclerView extends BaseRecyclerView extends RecyclerView
    |---all_apps.xml(显示所有App的视图,里面的子控件都是通过AppAppsContainerView的实例获取的)
        |---AllAppsContainerView extends BaseContainerView extends LinearLayout
            |---FrameLayout(所有App时图顶部的搜索框)
            |---FrameLayout
                |---FrameLayout
                |---all_apps_container.xml(承载所有App的视图(集列出所有安装的app的容器)
                    |---AllAppsRecyclerViewContainerView
                        |---AllAppsRecyclerView
    |---ViewStub

Launcher中核心类的简单说明

具体说明请点击相应连接

  • Launcher:主界面Activity,最核心且唯一的Activity;
  • LauncherAppState:单例对象,主要有如下作用:
  1. 初始化InvariantDeviceProfileIconCacheWidgetPreviewLoaderLauncherModel等;
  2. 注册广播,用来处理本地配置变化、搜索数据库(global search provider)变化、应用的安装与卸载等;
  • InvariantDeviceProfile:一些不变的设备相关参数管理类,包含横竖屏的两种DeviceProfile;
    • IconCache:应用程序图标缓存类,里面用数据库存储了应用的icon及title等的缓存信息;
    • WidgetPreviewLoader:加载Widget信息数据库,里面用数据库存储了Widget的信息
    • LauncherModel:在内存中保存Launcher的状态,提供读写数据库的API,其内部类LoaderTask用来加载Launcher的内容(包括workspace icons、widgets和all apps icons)
    • LauncherAppsCompat:兼容抽象基类,用来获取已安装的App列表;
    • UserManagerCompat:兼容抽象基类,用来处理不通版本下的用户管理;
  • DragController:拖拽事件控制类,拖拽事件的处理逻辑在这里实现
  • LauncherStateTransitionAnimation:Launcher的动画导演,负责安排不同状态切换之间的动画处理
  • AppWidgetManagerCompat:兼容抽象基类,负责处理不通版本下应用和Widget管理
  • LauncherAppWidgetHost:继承子AppWidgetHost,顾名思义,AppWidgetHost是桌面app、widget等的宿主,之所以继承是为了LauncherAppWidgetHostView能更好的处理长按事件;
  • FocusIndicatorView:一个实现了View.OnFocusChangeListener的View(具体作用上不清楚)
  • DragLayer:一个用来协调子View拖拽事件的ViewGroup,实际上事件的分发拦截等是在DragController,因为DragLayer持有DragController的实例,并调用了setup方法初始化了它;
  • Workspace:一个包含了壁纸和有限数量的页面的较大空间

Launcher主流程

先分析声明周期函数

onCreate()

主要流程都在onCreate中,如下(采用代码中添加注释的方法说明,省略次要代码):

@Override
protected void onCreate(Bundle savedInstanceState) {
  //省略掉严格模式相关代码...
  if (mLauncherCallbacks != null) {
    mLauncherCallbacks.preOnCreate();
  }
  super.onCreate(savedInstanceState);

  // 各种变量初始化
  LauncherAppState.setApplicationContext(getApplicationContext());
  // 初始化LauncherAppState对象
  LauncherAppState app = LauncherAppState.getInstance();

  // 根据配置的orientation来初始化DeviceProfile对象
  mDeviceProfile = getResources().getConfiguration().orientation
    == Configuration.ORIENTATION_LANDSCAPE ?
    app.getInvariantDeviceProfile().landscapeProfile
    : app.getInvariantDeviceProfile().portraitProfile;

  // 初始化SharedPreference对象
  mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
                                      Context.MODE_PRIVATE);
  mIsSafeModeEnabled = getPackageManager().isSafeMode();
  mModel = app.setLauncher(this);
  mIconCache = app.getIconCache();

  mDragController = new DragController(this);
  mInflater = getLayoutInflater();
  mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);

  mStats = new Stats(this);

  mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);

  mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
  mAppWidgetHost.startListening();

  // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
  // this also ensures that any synchronous binding below doesn't re-trigger another
  // LauncherModel load.
  mPaused = false;

  if (PROFILE_STARTUP) {
    android.os.Debug.startMethodTracing(
      Environment.getExternalStorageDirectory() + "/launcher");
  }
  // 设置布局(注意,此时的布局是不包含布局参数的)
  setContentView(R.layout.launcher);
  // 初始化View,进行各种View的初始化,事件绑定
  setupViews();
  // 为布局中的View添加上布局参数
  mDeviceProfile.layout(this);

  lockAllApps();

  // 恢复保存的状态
  mSavedState = savedInstanceState;
  restoreState(mSavedState);

  if (PROFILE_STARTUP) {
    android.os.Debug.stopMethodTracing();
  }

  // 数据加载核心部分,主要有LauncherModel的内部类LoaderTask来完成
  if (!mRestoring) {
    if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
      // If the user leaves launcher, then we should just load items asynchronously when
      // they return.
      mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
    } else {
      // 只有当launcher处于前台,且用户旋转屏幕活着触发方向配置上的改变时我们才同步加载数据
      mModel.startLoader(mWorkspace.getRestorePage());
    }
  }

  // For handling default keys
  mDefaultKeySsb = new SpannableStringBuilder();
  Selection.setSelection(mDefaultKeySsb, 0);

  IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
  registerReceiver(mCloseSystemDialogsReceiver, filter);

  mRotationEnabled = Utilities.isRotationAllowedForDevice(getApplicationContext());
  // In case we are on a device with locked rotation, we should look at preferences to check
  // if the user has specifically allowed rotation.
  if (!mRotationEnabled) {
    mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext(), false);
  }

  // On large interfaces, or on devices that a user has specifically enabled screen rotation,
  // we want the screen to auto-rotate based on the current orientation
  setOrientation();

  if (mLauncherCallbacks != null) {
    mLauncherCallbacks.onCreate(savedInstanceState);
    if (mLauncherCallbacks.hasLauncherOverlay()) {
      ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
      mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
      mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
        mLauncherOverlayContainer, mLauncherOverlayCallbacks);
      mWorkspace.setLauncherOverlay(mLauncherOverlay);
    }
  }

  // 是否显示欢迎说明
  if (shouldShowIntroScreen()) {
    showIntroScreen();
  } else {
    showFirstRunActivity();
    showFirstRunClings();
  }
}

可以看到,在设置布局之后,进行了View的初始化、View的事件绑定等,然后根据DeviceProfile(设备描述类,定义了Launcher在不同设备、不公状态下的以下常量等)的layout方法为初始化的View添加上布局参数。参数设置完成后,就进入了数据加载阶段,数据加载是通过LauncherModel的内部类LoaderTask来完成的(根据当前的配置,来选择时同步加载数据还是异步加载数据)。接下来就是控制Launcher Intro Screen的显示与否了,显示的话,就显示Intro Screen,不显示就进入else部分,显示Launcher Clings(其实就是首次运行Launcher时的一些关于Launcher用途的说明)

Launcher 代码中关于mLauncherCallbacks部分,由于mLauncherCallbacks的赋值操作必须调用setLauncherCallbacks来完成,但该函数只在LauncherExtension中才调用,所以如果要对Launcher做扩展,需要了解这部分代码,否则,可以忽略。

onResume()

onResume()中主要进行的是视图显示状态的恢复、依次执行Runnable任务(包括BindAllApplicationRunnable、BindPackagesUpdatesRunnable以及UpdateOrientationRunnable)、恢复App或App Shortcut的状态,必要的时候还会重新生成workspace上的Widget和QSB等。

Launcher中有一个waitUntilResume函数,字面意思“直到onResume执行才。。。。”,先看看其具体代码:

@Thunk
boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
  if (mPaused) {
    if (LOGD) Log.d(TAG, "Deferring update until onResume");
    if (deletePreviousRunnables) {
      while (mBindOnResumeCallbacks.remove(run)) {
      }
    }
    mBindOnResumeCallbacks.add(run);
    return true;
  } else {
    return false;
  }
}

还有一个重载方法:

private boolean waitUntilResume(Runnable run) {
  return waitUntilResume(run, false);
}

在如下几个地方调用了:

  • Launcher(直接调用的地方)
    • bindAllApplications(final ArrayList apps)
    • bindAllPackages(final WidgetsModel model)
    • onSettingsChanged(String settings, boolean value)
  • Launcher(间接调用的地方,调用的是重载的方法)
    • bindAppsAdded
    • bindAppsUpdated
    • bindAppWidget
    • bindComponentsRemoved
    • bindFolders
    • bindItems
    • bindRestoreItemsChange
    • bindShortcutsChanged
    • bindWidgetsRestored
    • finishBindingItems

可见,与绑定有关的runnable都是在onResume的时候执行的,那么在这些runnable到底都做了什么,有什么功能,如何实现这些功能的呢?这里先提如下几个问题,我们带着问题读代码,马上就能得到答案:

  1. 在这些runnable执行之前,又做了什么了?
  2. 我们都要绑定写什么对象?
  3. 我们要绑定的这些对象怎么得到的?
  4. 我们要把这些对象绑定到哪去?

现在依次解答上面的个问题,其实在onResume之前就已经被LauncherModel(数据处理的核心类)安排好了,现在来答上面的问题:

  1. 在这些runnable执行之前,做了些什么?

    onCreate里面调用了Launcher的startLoader方法,开方法会开启其内部类LoaderTask的run方法来进行如下操作:

    • loadAndBindWorkspace,即加载并绑定workspace;
      • loadWrokspace
      • bindWorkspace
    • loadAndBindAllApps,即加载并绑定All apps;
  2. 我们都要绑定写什么对象?

    想想Launcher上都有什么,显然App、AppShortcut、widget、Folder等都可能有,所以要绑定的当然是这些对象。

  3. 我们要绑定的这些对象怎么得到的?

    通过LauncherModel的内部类LoaderTask来得到这些对象

  4. 我们要把这些对象绑定到哪去?

    该放哪儿去放哪儿去。由于Launcher中的对象(App、AppShortcut、Widget、Folder等)无论是在Desktop(即桌面)或Hotseat(即底部的图标栏)都有相应的坐标点(cellX,cellY)及占地面积(spanX、spanY),我们只要将其按对应坐标对号入座即可。当然特需情况(如图标遮挡什么的)需要特殊处理。

其实,2、3、4里面要做的任务都是在1里面完成的,可见数据处理核心还是LauncherModel里面。接下来的文章会对LauncherModel做详细分析。

  • 2
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android LauncherAndroid系统中的一个应用程序,它是用户与设备交互的主要界面。它提供了桌面、应用程序列表、小部件等功能,用户可以通过它来启动应用程序、查看通知、管理设备等。 Android Launcher的源码分析主要包括以下几个方面: 1. 桌面布局:Android Launcher的桌面布局是通过GridView实现的,它可以显示应用程序图标和小部件。在源码中,可以看到GridView的相关代码,包括布局、适配器等。 2. 应用程序列表:Android Launcher的应用程序列表是通过ListView实现的,它可以显示所有安装的应用程序。在源码中,可以看到ListView的相关代码,包括布局、适配器等。 3. 搜索功能:Android Launcher提供了搜索功能,用户可以通过输入关键字来搜索应用程序、联系人等。在源码中,可以看到搜索框的相关代码,包括布局、事件处理等。 4. 桌面小部件:Android Launcher支持桌面小部件,用户可以在桌面上添加各种小部件,如天气、时钟、日历等。在源码中,可以看到小部件的相关代码,包括布局、事件处理等。 5. 动画效果:Android Launcher提供了各种动画效果,如应用程序图标的放大缩小、桌面的滑动等。在源码中,可以看到动画效果的相关代码,包括属性动画、插值器等。 总之,Android Launcher的源码分析涉及到很多方面,需要深入研究才能掌握。 ### 回答2: Android launcher是一个重要的应用程序,它是用户的桌面界面,负责管理应用程序、小部件、壁纸等的展示和操作。在Android开发中,我们可以使用默认的系统Launcher也可以自定义Launcher,下面就来分析一下Android Launcher的源码。 1. Launcher的结构分析 Launcher的展示分为三个层次:桌面、工作区和屏幕。在源码中,它们分别对应着Launcher、Workspace和CellLayout。Launcher类是整个应用程序的外壳,它管理了整个应用程序的生命周期。Workspace管理着桌面上的工作区,它可以管理多个屏幕。而CellLayout则是每个工作区上的单元格容器,用于展示应用程序和小部件的图标。 2. Launcher的主界面 Launcher的主界面一般包括一个搜索栏、一个Dock栏和一个应用程序列表。其中搜索栏和Dock栏是Launcher的核心部分,它们的实现都是需要注意的: 2.1 搜索栏 Launcher的搜索栏是通过SearchView实现的,需要处理SearchView的监听事件和搜索逻辑。其中监听事件可以根据具体需求进行定制,比如支持模糊搜索、联想搜索等。 2.2 Dock栏 Dock栏是一个固定在桌面底部的横向图标栏,它可以管理常用的应用程序和快捷方式。Dock栏的实现需要考虑如下几个问题: - 图标的布局 - 图标的拖动 - 图标的排序 - 图标的管理 3. Workspace实现分析 Workspace是Launcher的核心部分,它基本实现了所有的界面交互逻辑。在Workspace上,用户可以添加、移动、删除应用程序和小部件。Workspace的实现需要考虑如下几个问题: 3.1 桌面上的应用程序和小部件布局 Workspace上的每个屏幕都是通过CellLayout实现的,它负责管理应用程序和小部件的布局和显示。 3.2 图标的拖动和排序 用户可以通过拖动来移动和排序图标,需要考虑如下几个问题: - 拖动的实现逻辑 - 滑动过程的动画效果 - 拖动时的图标缩放效果 3.3 图标的删除和添加 用户可以通过长按图标来触发删除操作,也可以通过添加菜单来添加新的应用程序和小部件。 4. 总结 以上是针对Android Launcher源码的分析,这些内容只是基础的使用方法和思路,具体的实现还需要根据自己的需求和场景进行定制和优化。在实现Launcher的时候,需要注意性能问题,尽量避免不必要的计算和操作;同时,还需要考虑到用户的交互体验,保证操作的流畅和直观。 ### 回答3: Android LauncherAndroid手机主屏幕上最常见的应用,它提供了一个桌面环境和一些关键功能,如快速访问应用程序、设置壁纸、小部件和通知中心等。本文将对Android Launcher源码进行分析,重点关注Launcher的核心组件:Workspace、Folder、App Drawer和Widget。 首先,Workspace是Launcher中最核心的组件之一,它是桌面上的容器,用于展示应用程序图标和小部件。Workspace是一个基于GridView组件自定义的ViewGroup,用于支持滑动手势和快捷方式的编辑管理。Framework层通过Workspace类读取和更新数据,它通过扩展Adapter来创建UI元素,并通过GridLayoutManager装填与交换元素。 其次,Folder是Launcher用于存储和管理应用程序快捷方式的组件。Folder的UI元素扩展自ViewGroup,可以包含多个应用快捷方式,在Folder中用户可以自由地添加、删除和排序快捷方式。Folder同时提供一个搜索框,用于帮助用户快速搜索自己的应用程序。 其次,App Drawer是Launcher的另一个核心组件,它是一个垂直滚动的列表,用于展示所有的应用程序。App Drawer是按字母排序的,同时可以通过搜索框查找用户需要的特定应用程序。App Drawer实现类似于Workspace,只不过它只横向的有一个方向轴且没有操作修改排序功能。 最后,Widget是一个独特的组件,它是一些非小部件的快捷方式,用户可以将它们放置在桌面上。Widget允许用户在主屏幕上快速访问特定功能或信息,例如天气、电子邮件和日历等。Widget UI元素的设计与Folder和App Drawer不同,它们提供更多信息和控制,通常需要桌面空间大才能放下。 总体而言,Android Launcher的源码分析需要涉及多个关键组件,Workspace、Folder、App Drawer和Widget是其中最重要的几个。这些组件深度整合在Launcher中,可以帮助用户快速访问他们需要的应用程序和信息。因此,熟练掌握这些核心组件的基本实现和逻辑是Android开发者不可或缺的技能之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值