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





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

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

        
        <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. 初始化InvariantDeviceProfile、IconCache、WidgetPreviewLoader、LauncherModel等;
  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做详细分析。

你可能感兴趣的:(Android学习开发)