Android Launcher2源码分析

Android   Launcher2源码分析

Android源码程序程序中有一个应用程序入口,官方给出的中文翻译为“启动器”。我们一下统称Launcher.

Launcher源码分析,我们还是从AndroidManifest.xml开始:

<application
    android:name="com.android.launcher2.LauncherApplication"
    android:label="@string/application_name"
    android:icon="@drawable/ic_launcher_home"
    android:hardwareAccelerated="@bool/config_hardwareAccelerated"
    android:largeHeap="@bool/config_largeHeap">
    <activity
        android:name="com.android.launcher2.Launcher"
        ...
        >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.HOME" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.MONKEY"/>
        </intent-filter>
    </activity>
    ...
</application>

其他,我们姑且也不管,有三点我们必须说一下:

一、

android:hardwareAccelerated="@bool/config_hardwareAccelerated"

指定了整个应用程序是启用硬件加速的,这样整个应用程序的运行速度会更快。

二、

android:largeHeap="@bool/config_largeHeap"

指定了应用程序使用了大的堆内存,能在一定程度上避免,对内存out of memory错误的出现。

三、

<category android:name="android.intent.category.HOME" />

这个申明,相当于告诉系统这是桌面Activity。如果你希望开发自己的桌面应用。这个申明是必须的

通过这三点的设置,我们大概知道了桌面的核心。

那么,接下来我们从LauncherApplication(com/android/launcher2/LauncherApplication.java)应用程序开始分析

public void onCreate() {
        super.onCreate();
        // 在创建icon cache之前,我们需要判断屏幕的大小和屏幕的像素密度,以便创建合适大小的icon
        final int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
        sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE ||
            screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE;
        sScreenDensity = getResources().getDisplayMetrics().density;

        mIconCache = new IconCache(this);  //用来<span style="color: rgb(51, 51, 51); font-family: 宋体; font-size: 14px; line-height: 28px; text-indent: 28px; background-color: rgb(248, 248, 248);">设置了应用程序的图标的cache</span>
        mModel = new LauncherModel(this, mIconCache);  //<span style="color: rgb(51, 51, 51); font-family: 宋体; font-size: 14px; line-height: 28px; text-indent: 28px; background-color: rgb(248, 248, 248);">LauncherModel主要用于加载桌面的图标、插件和文件夹,同时LaucherModel是一个广播接收器,在程序包发生改变、区域、或者配置文件发生改变时,都会发送广播给LaucherModel,LaucherModel会根据不同的广播来做相应加载操作</span>

        // 注册广播接收器
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        ......
        registerReceiver(mModel, filter);

        //注册ContentObserver,监听LauncherSettings.Favorites.CONTENT_URI数据的变化
        ContentResolver resolver = getContentResolver();
        resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
                mFavoritesObserver);
    }

查看IconCache(com.android.launcher2.IconCache.java)我们很容易发现,这是一个应用程序图标(icon)缓冲生成器。

正如我们刚才所说的,我们知道LauncherModel主要用于加载桌面的图标(icon)、插件(AppWidge)和文件夹(Floder)
和Shortcut。

在LauncherModel(com.android.launcher2.LauncherModel.java)中

public class LauncherModel extends BroadcastReceiver {
        public interface Callbacks {
        public boolean setLoadOnResume();
        public int getCurrentWorkspaceScreen();
        public void startBinding();
        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
        public void bindFolders(HashMap<Long,FolderInfo> folders);
        public void finishBindingItems();
        public void bindAppWidget(LauncherAppWidgetInfo info);
        public void bindAllApplications(ArrayList<ApplicationInfo> apps);
        public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
        public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
        public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
        public void bindPackagesUpdated();
        public boolean isAllAppsVisible();
        public void bindSearchablesChanged();
    }
}

很明显

LauncherModel.startLoader(),开始加载的工作。launcherModel中加载好的内容会通过

LauncherModel.Callbacks接口的回调函数将数据传给需要的组件

setLoadOnResume()     由于Launcher继承自Activity,因此Launcher可能会处于paused状态(onPause()被调用),

则有可能在这段时间内资源可能发生了改变,如应用被删除或新应用安装,因此需要在onResume()中调用此方法进行重新加载。

getCurrentWorkspace()    获取当前屏幕的序号

startBinding()     通知Launcher加载开始,并更新Workspace上的shortcuts

bindItems(ArrayList<ItemInfo> shortcuts, int start, int end)     加载一批内容项到Workspace,加载的内容项包括,

Application、shortcut、folder。

bindFolders(HashMap<Long, FolderInfo> folders)    加载folder的内容

finishBindingItems()    通知Launcher加载结束。

bindAppWidget(LauncherAppWidgetInfo item)    加载AppWidget到Workspace

bindAllApplications(final ArrayList<ApplicationInfo> apps)   在All Apps页加载所有应用的Icon

bindAppsAdded(ArrayList<ApplicationInfo> apps)   通知Launcher一个新的应用被安装,并加载这个应用

bindAppsUpdated(ArrayList<ApplicationInfo> apps)  通知Launcher一个应用发生了更新

bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent)    通知Launcher一个应用被删除了

bindPackagesUpdated()   通知Launcher多个应用发生了更新

isAllAppsVisible()用于在加载的过程中记录当前Launcher的状态,返回true则当前显示的All Apps

bindSearchablesChanged()当搜索/删除框状态发生改变时调用

了解了每个方法的作用之后,就可以开始进一步的分析了。

了解了大概过程,下一步。我们走到Launcher(com.android.launcher2.Launcher.java)

public final class Launcher extends Activity
implements View.OnClickListener,LauncherModel.Callbacks{
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    mModel = app.setLauncher(this);
    mIconCache = app.getIconCache();
    ...
    mAppWidgetManager = AppWidgetManager.getInstance(this);
    mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
    mAppWidgetHost.startListening();
    ...
    //检查本地保存的配置是否需要更新
    checkForLocaleChange();
    setContentView(R.layout.launcher);
    //对UI控件进行初始化和配置
    setupViews();
    //向用户展示指导的页面
    showFirstRunWorkspaceCling();
    registerContentObservers();
    ...
    if (!mRestoring) {
	//为Launcher加载数据
        mModel.startLoader(this, true);
    }
    ...
}

别的,我们先不管。

我们先来看看Launcher的启动

implements  LauncherModel.Callbacks{
mModel = app.setLauncher(this);

我们再来看看LauncherApplication.java中的

LauncherModel setLauncher(Launcher launcher) {
      mModel.initialize(launcher);
      return mModel;
}

从mModel.startLoader(true,
mWorkspace.getCurrentPage());我们大概知道加载流程。但那时具体加载过程我们稍后分析

我们先来看一下整个Launcher的布局

launcher.xml
<com.android.launcher2.DragLayer
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" 

    android:id="@+id/drag_layer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"> 

    <!-- Keep these behind the workspace so that they are not visible when
         we go into AllApps -->
    <include
        android:id="@+id/dock_divider"
        layout="@layout/workspace_divider"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/button_bar_height"
        android:layout_gravity="bottom"  />
    <include
        android:id="@+id/paged_view_indicator"
        layout="@layout/scroll_indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_marginBottom="@dimen/button_bar_height"  /> 

    <!-- The workspace contains 5 screens of cells -->
    <com.android.launcher2.Workspace
        android:id="@+id/workspace"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="@dimen/qsb_bar_height_inset"
        android:paddingBottom="@dimen/button_bar_height"
        launcher:defaultScreen="2"
        launcher:cellCountX="4"
        launcher:cellCountY="4"
        launcher:pageSpacing="@dimen/workspace_page_spacing"
        launcher:scrollIndicatorPaddingLeft="@dimen/workspace_divider_padding_left"
        launcher:scrollIndicatorPaddingRight="@dimen/workspace_divider_padding_right"> 

        <include android:id="@+id/cell1" layout="@layout/workspace_screen"  />
        <include android:id="@+id/cell2" layout="@layout/workspace_screen"  />
        <include android:id="@+id/cell3" layout="@layout/workspace_screen"  />
        <include android:id="@+id/cell4" layout="@layout/workspace_screen"  />
        <include android:id="@+id/cell5" layout="@layout/workspace_screen"  />
    </com.android.launcher2.Workspace> 

    <include layout="@layout/hotseat"
        android:id="@+id/hotseat"
        android:layout_width="match_parent"
        android:layout_height="@dimen/button_bar_height_plus_padding"
        android:layout_gravity="bottom"  /> 

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

    <include layout="@layout/apps_customize_pane"
        android:id="@+id/apps_customize_pane"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="invisible"  /> 

    <include layout="@layout/workspace_cling"
        android:id="@+id/workspace_cling"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"  /> 

    <include layout="@layout/folder_cling"
        android:id="@+id/folder_cling"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"  />
</com.android.launcher2.DragLayer> 

一、最外层的DragLayer,是一个继承自FramLayout的View控件,显示的就是整个桌面根容器。桌面的所有控件都是位于DragLayer中。

二、id/dock_divider
 它通过include关键字包含了另外一个布局文件workspace_divider.xml ,而这个workspace_divider.xml包含了一ImageView,其实dock_divider就是dock区域上面的那条直线。

三、id/paged_view_indicator
 同样它包含了scroll_indicator.xml,其中包含了一个ImageView,显示的是一个.9的png文件。实际上就是当Launcher滚动翻页的时候,那个淡蓝色的页面指示条。

四、id/workspace 然后桌面的核心容器WorkSpace,如下图所示,当然你看到的只是Workspace的一部分,其实是一个workspace_screen,通过 Launcher.xml可以看到,整个workspace由5个workspace_screen组成,每个workspace_screen其实就是对应桌面一页。而每个workspace_screen包含了一个CellLayout,这是一个自定义控件,继承自ViewGroup,所以它算是一个用来布局的控件,在这里主要用来承载我们每页的桌面图标、widget和文件夹。


、id/hotseat  其实就是这块dock区域了

六、id/qsb_bar
 就是屏幕最顶端的Google搜索框。这个搜索框是独立于图标界面的,所以当我们对桌面进行翻页的时候,

这个搜索框会巍然不动滴固定在最顶端。当然他可不止这么简单。他同样是删除框,用于应用图标移动到这里提示

七、id/apps_customize_pane
   点击dock中显示所有应用程序的按钮后才会从隐藏状态转换为显示状态,如下图所示,

显示了所有应用程序和所有插件的界面。


、id/workspace_cling当第一次运行Launcher2时,会显示的用于指导的动画,以后不再显示

id/folder_cling,第一次使用Folder时,展示给用户的指导画面。

看完布局,我们对于Launcher整体用了清醒地认识

首先让我们回顾一下整个加载过程的流程是怎样的

时间: 2024-08-09 17:02:45

Android Launcher2源码分析的相关文章

[Android]Fragment源码分析(一) 构造

Fragment是Android3.0之后提供的api,被大家广泛所熟知的主要原因还是因为随即附带的ViewPager控件.虽然我并不喜欢用它,但是它确实是一个相对不错的控件.还是我的一贯作风,我将从源码上向大家展示什么是Fragment.我们先写一个简单的代码对Fragment有个直观的认识:(为了保证我们方便调试,我们可以直接使用V4提供的源码包) FragmentTransaction t = getSupportFragmentManager().beginTransaction();

[Android]Volley源码分析(四)

上篇中有提到NetworkDispatcher是通过mNetwork(Network类型)来进行网络访问的,现在来看一下关于Network是如何进行网络访问的. Network部分的类图: Network有一个实现类BasicNetwork,它有一个mHttpStack的属性,实际的网络请求是由这个mHttpStack来进行的,看BasicNetwork的performRequest()方法, 1 @Override 2 public NetworkResponse performRequest

android 从源码分析为什么Listview初次显示时没滚动却自动调用onScroll方法的原因

我们做Listview的分批加载时,需要为Listview调用setOnScrollListener(具体代码可见我上一篇博客) 可是,我们会发现,当运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?(补充:此时onScrollStateChanged并不会调用) 我们先看setOnScrollListener源码: public void setOnScrollListener(OnScrollListener l) { mOnScrollListener =

[Android]Fragment源码分析(三) 事务

Fragment管理中,不得不谈到的就是它的事务管理,它的事务管理写的非常的出彩.我们先引入一个简单常用的Fragment事务管理代码片段: FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction(); ft.add(R.id.fragmentContainer, fragment, "tag"); ft.addToBackStack("<span style="fo

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte

[Android]Volley源码分析(叁)Network

如果各位看官仔细看过我之前的文章,实际上Network这块的只是点小功能的补充.我们来看下NetworkDispatcher的核心处理逻辑: <span style="font-size:18px;">while (true) { try { // Take a request from the queue. request = mQueue.take(); } catch (InterruptedException e) { // We may have been int

[Android]Volley源码分析(肆)应用

通过前面的讲述,相信你已经对Volley的原理有了一定了解.本章将举一些我们能在应用中直接用到的例子,第一个例子是 NetworkImageView类,其实NetworkImageView顾名思义就是将异步的操作封装在了控件本身,这种设计可以充分保留控件的移植性和维护性.NetworkImageView通过调用setImageUrl来指定具体的url: public void setImageUrl(String url, ImageLoader imageLoader) { mUrl = ur

Android IntentService 源码分析

IntentService简介: IntentService是一个通过Context.startService(Intent)启动可以处理异步请求的Service,使用时你只需要继承IntentService和重写其中的onHandleIntent(Intent)方法接收一个Intent对象,该服务会在异步任务完成时自动停止服务. 所有的请求的处理都在IntentService内部工作线程中完成,它们会顺序执行任务(但不会阻塞主线程的执行),某一时刻只能执行一个异步请求. IntnetServi

[Android] Volley源码分析(一)体系结构

Volley:google出的一个用于异步处理的框架.由于本身的易用性和良好的api,使得它能得以广泛的应用.我还是一如既往从源码的方向上来把控它.我们先通过一段简单的代码来了解Volley RequestQueue queue = Volley.newRequestQueue(this); ImageRequest imagerequest = new ImageRequest(url, new Response.Listener<Bitmap>(){ @Override public vo