Android Launcher2源码分析
<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。如果你希望开发自己的桌面应用。这个申明是必须的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。
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()当搜索/删除框状态发生改变时调用
了解了每个方法的作用之后,就可以开始进一步的分析了。
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);
}
...
}
别的,我们先不管。implements LauncherModel.Callbacks{
mModel = app.setLauncher(this);
我们再来看看LauncherApplication.java中的LauncherModel setLauncher(Launcher launcher) {
mModel.initialize(launcher);
return mModel;
}
从mModel.startLoader(true,
mWorkspace.getCurrentPage());我们大概知道加载流程。但那时具体加载过程我们稍后分析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中。
首先让我们回顾一下整个加载过程的流程是怎样的