赞
踩
本文记录了笔者学习 Launcher 2 的过程
参考书籍:
《Android 深度探索(卷 2)》(系统应用源代码分析与 ROM 定制) /李宁 编著 /人民邮电出版社
主要参考:13 ~ 14 章
内容如有错误,请联系作者修改
Launcher 2 就是最常用的系统应用,也是 Android 系统启动后运行的第一个 Android 应用程序,通常称为 Home 或 桌面(为了统一,下文统称为 “Android Home 应用”)
事实上,Launcher 2 是官方的叫法。也就是说,在官方发布的 ROM 中,Android Home 应用的工程名为 Launcher2,生成的 Android 应用文件名为 Launcher2.apk 。但是对于第三方的 ROM 来说,Android Home 应用不一定叫 Launcher2.apk
从这一点可以看出,Android 系统中低层上没有现在 Android Home 应用的文件名。也就是说,Android Home 应用可以是任意的文件名。
既然没有固定 Android Home 应用的文件名,那么通常的作法是使用显式或隐式的方式显示 Android Home 应用中的主窗口。所谓显式就是指直接使用窗口类名显示该窗口,而隐式是使用 Activity Action 显示某一个窗口。想知道详情,最先做的就是看看 Launcher 2 的 AndroidManifest.xml
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
注意:一般的应用,其 Category 命名为 “android.intent.category.LAUNCHER”,如上面的那段代码
<activity
android:name="com.cyanogenmod.trebuchet.Launcher"
android:clearTaskOnLaunch="true"
android:launchMode="singleTask"
android:stateNotNeeded="true"
android:theme="@style/Theme"
android:windowSoftInputMode="adjustPan" >
<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>
注意:对于 Launcher 2 来说,窗口是 “com.cyanogenmod.trebuchet.Launcher”。另外,其 Category 指定了 “android.intent.category.HOME”,代替了 “android.intent.category.LAUNCHER”。此外,该窗口的创建模式还设为了 “singleTask”。
<activity android:name=".MainActivity">
<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" /> // 不可
</intent-filter>
</activity>
其中 Launcher 3 就是 Android 系统应用,My first app 是我自己写的一个小应用。(这是在点击了 HOME 键之后弹出的选择框)
注:在网上查阅后,据闻从Android 4.4 开始就使用 Launcher 3,但是问题不大,照着 Launcher 2 来学,原理是类似的
可是这两类 Android 桌面应用有一个最大的不同,就是前者(Launcher 3)可以访问所有的限制级 API;而后者(普通应用)只能作为普通的 Android 应用使用,如果 Android SDK 不支持的功能,后者(普通应用)无法实现。
Launcher 2 是 Android 应用(经过 Shared 签名的系统应用),所以与普通的 Android 应用一样,都会有一个主布局文件,并且在主窗口类(Launcher)的 onCreate 方法中会进行一系列初始化工作。因此分析 Launcher 2 的源代码通常会从 Launcher.onCreate 方法及其布局主布局文件(launcher.xml)开始
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:launcher="http://schemas.android.com/apk/res/com.cyanogenmod.trebuchet" android:id="@+id/launcher" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/workspace_bg"> <!-- 主要控制桌面上图标的拖动和效果 --> <com.cyanogenmod.trebuchet.DragLayer android:id="@+id/drag_layer" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!-- 默认包含 5 个页面 --> <com.cyanogenmod.trebuchet.Workspace android:id="@+id/workspace" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/workspace_left_padding" android:paddingRight="@dimen/workspace_right_padding" android:paddingTop="@dimen/workspace_top_padding" android:paddingBottom="@dimen/workspace_bottom_padding" launcher:cellCountX="@integer/target_cell_count_x" launcher:cellCountY="@integer/target_cell_count_y" launcher:pageSpacing="@dimen/workspace_page_spacing" launcher:scrollIndicatorPaddingLeft="@dimen/workspace_divider_padding_left" launcher:scrollIndicatorPaddingRight="@dimen/workspace_divider_padding_right" /> ...... <!-- 屏幕上方的 Search 条,以及 “移除/编辑” 按钮 --> <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" /> </com.cyanogenmod.trebuchet.DragLayer> </FrameLayout>
launcher.xml 文件的内容很多,这里只给出了主要的部分,这些给出的布局代码构成了经常使用的 UI。
protected void onCreate(Bundle savedInstanceState) { ...... super.onCreate(savedInstanceState); // 获取 LauncherApplication 对象 LauncherApplication app = ((LauncherApplication) getApplication()); mSharedPrefs = getSharedPreferences( LauncherApplication.getSharedPreferencesKey(), Context.MODE_PRIVATE); // 第一步:创建管理对象 // mModel 是 LauncherModel 类型的变量 mModel = app.setLauncher(this); // 维护 Launcher 2 在内存中状态 mIconCache = app.getIconCache(); // 处理桌面图标 mDragController = new DragController(this); // 处理拖动操作 mInflater = getLayoutInflater(); // Load all preferences PreferencesProvider.load(this); mAppWidgetManager = AppWidgetManager.getInstance(this); mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); mAppWidgetHost.startListening(); // 为了防止重新装载 Android 桌面 mPaused = false; // 第 2 步:获取状态标志 // 下面初始化了一些状态 mShowSearchBar = PreferencesProvider.Interface.Homescreen .getShowSearchBar(); mShowHotseat = PreferencesProvider.Interface.Dock.getShowDock(); mShowDockDivider = PreferencesProvider.Interface.Dock.getShowDivider() && mShowHotseat; mHideIconLabels = PreferencesProvider.Interface.Homescreen .getHideIconLabels(); mAutoRotate = PreferencesProvider.Interface.General .getAutoRotate(getResources().getBoolean(R.bool.allow_rotation)); mFullscreenMode = PreferencesProvider.Interface.General .getFullscreenMode(); if (PROFILE_STARTUP) { android.os.Debug.starMethodTracing(Environment .getExternalStorageDirectory() + "/launcher"); } checkForLocaleChange(); setContentView(R.layout.launcher); // 第 3 步:装载视图 // 装载视图 setupViews(); showFirstRunWorkspaceCling(); registerContentObservers(); lockAllApps(); mSavedState = savedInstanceState; restoreState(mSavedState); // Update customization drawer _after_ restoring the states if (mAppsCustomizeContent != null) { mAppsCustomizeContent.onPackagesUpdated(); } if (PROFILE_STARTUP) { android.os.Debug.stopMethodTracing(); } // 第 4 步:装载所有的 Android 应用 if (!mRestoring) { if (sPausedFromUserAction) { // 如果用户离开 Android 桌面,再回到桌面后,应该使用异步方式重新装载桌面 mModel.startLoader(true, -1); } else { // 如果用户旋转 Android 设备造成了配置的变化,应该同步装载当前页 mModel.startLoader(true, mWorkspace.getCurrentPage()); } // 应用程序未被装载时,会显示圆形进度 if (!mModel.isAllAppsLoaded()) { ViewGroup appsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent .getParent(); mInflater.inflate(R.layout.apps_customize_progressbar, appsCustomizeContentParent); } // 第 5 步:其他的初始化工作 mDefaultKeySsb = new SpannableStringBuilder(); Selection.setSelection(mDefaultKeySsb, 0); Intentfilter filter = new IntentFilter( Intent.ACTION_CLOSE_SYSTEM_DIALOGS); // 注册可以拦截关闭系统对话框的广播接收器 registerReceiver(mCloseSystemDialogsReceiver, filter); // 更新桌面上的图标 updateGlobalIcons(); // 解锁屏幕旋转状态 unlockScreenOrientation(true); }
LauncherApplication app = ((LauncherApplication) getApplication());
LauncherApplication 是全局对象,需要在 AndroidManifest.xml 文件中使用 “application” 标签的 android:name 属性指定 LauncherApplication 类的全名,代码如下:
<application
android:name="com.cyanogenmod.trebuchet.LauncherApplication"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher_home"
android:label="@string/application_name"
android:largeHeap="@bool/config_largeHeap"
android:supportsRtl="true">
......
</application>
LauncherApplication 类主要创建和存储了 Launcher 2 中需要的一些管理对象
例如,维护 Launcher 2 在内存中状态的 LauncherModel 对象(mModel 字段);处理桌面图标的 IconCache 对象(mIconCache 字段);处理拖动操作的 DragController 对象(MDragController 字段)等。这些类的实例都在 LauncherApplication.onCreate 方法中创建。
当系统自动创建 LauncherApplication 对象后,会调用 LauncherApplication.onCreate 方法
mShowSearchBar = PreferencesProvider.Interface.Homescreen
.getShowSearchBar();
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
装载这类 Android 应用使用的方法是 LauncherModel.startLoader。通过该方法可以同步或异步装载所有的应用程序,也就是我们在应用列表中看到的那些图标**(每一个图标对应包含上面 Intent Filter 的一个窗口)**。
由于 LauncherApplication 是全局对象,所以这就保证了随时随地可以获取这些管理对象。LauncherApplication 类的核心是 onCreate 方法,在该方法中创建了这些管理对象,并注册了一些广播接收器。LauncherApplication 类的代码如下:
...... public class LauncherApplication extends Application{ public LauncherModel mModel; public IconCache mIconCache; private static boolean sIsScreenLarge; private static float sScreenDensity; private static int sLongPressTimeout = 300; private static final String sSharedPreferencesKey = "com.cyanogenmod.trebuchet.prefs"; WeakReference<LauncherProvider> mLauncherProvider; @Override public void onCreate(){ super.onCreate(); // set.sIsScreenXLarge and sScreenDensity *before* creating icon cache sIsScreenLarge = getResources().getBoolean(R.bool.is_large_screen); sScreenDensity = getResources().getDisplayMetrics().density; // 创建 IconCache 对象 mIconCache = new IconCache(this); // 创建 LauncherModel 对象 mModel = new LauncherModel(this, mIconCache); // 下面的代码注册广播接收器 // 用于监视安装新的 Android 应用 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); // 用于监视移除 Android 应用 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); // 用于监视 Android 应用是否发生变化 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); registerReceiver(mModel, filter); filter = new IntentFilter(); filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); // 用于监视国家和地区是否发生变化 filter.addAction(Intent.ACTION_LOCALE_CHANGED); // 用于监视配置是否发生变化,例如,横竖屏切换 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); registerReceiver(mModel, filter); filter = new IntentFilter(); filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); registerReceiver(mModel, filter); filter = new IntentFilter(); filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); registerReceiver(mModel, filter); // 监视桌面图标数据库是否发生变化 ContentResolver resolver = getContentResolver(); resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mFavoritesObserver); } ...... // 监视桌面图标数据库是否发生变化 private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()){ @Override public void onChange(boolean selfChange){ // 如果桌面图标数据库发生了变化,重新在后台装载桌面 mModel.resetLoadedState(false, true); mModel.startLoaderFromBackground(); } }; ...... }
在第 1 次注册广播接收器的过程中,指定了如下 3 个 Action:
在 onCreate 方法的最后使用了如下代码注册了一个用于监视图标数据库是否发生变化的监视器
ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
mFavoritesObserver);
其中 LauncherSettings. Favorites. CONTENT_URI 是待监视数据要满足的条件,该常量的代码如下:
static final Uri CONTENT_URI = Uri.parse("content://" +
LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
"?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
如果将 CONTENT_URI 中的常量替换成相应的值,这个 Uri 会是下面的样子:
static final Uri CONTENT_URI =
Uri.parse("content://com.cyanogenmod.trebuchet.settings/favorites?notify=true");
其中这个 Uri 指向了 Launcher 2 使用的数据库 launcher.db 中的 favorites 表中的数据。该表存储了桌面上(包括最下方的 5 个图标)所有图标的相关信息(包括单击图标对应的动作)。当通过 Content Provider 修改 favorites 表中的数据后,就会触发该监视器。在 onCreate 后面创建了该监视器对象,其中有一个 onChange 方法,代码如下:
public void onChange(boolean selfChange){
// 如果桌面图标数据库发生了变化,重新在后台装载桌面
mModel.resetLoadedState(false, true);
mModel.startLoaderFromBackground();
}
当 favorites 表中数据发生变化后,系统会调用该方法在 onChange 方法中通过调用 LauncherModel. startLoaderFromBackground 方法在后台重新装载了桌面。也就是说,通过 Content provider 修改 favorites 表中的数据后,会导致 Android 桌面刷新
private void sendNotify(Uri uri){
String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
if (notify == null || "true".equals(notify)){
// 通知系统 favorites 表中的数据已经发生了变化,调用 ContentObserver.onChange 的工作
// 是由系统来完成的
getContext().getContentResolver().notifyChange(uri, null);
}
}
sendNotify 方法从 Uri (也就是前面给出的 CONTENT_URI 常量的值)中获取了 notify 参数的值,如果该参数值为 true,则通知系统 favorites 表的数据已经发生了变化。
下面是 LauncherProvider.insert 方法代码,在该方法中向 favorites 表成功插入数据后,会调用 sendNotify 方法通知系统 favorites 表的数据已经变化,update 和 delete 方法也会进行类似的操作:
public Uri insert(Uri uri, ContentValues initialValues){
SqlArguments args = new SqlArguments(uri);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final long rowId = dbInsertAndCheck(db, args.table, null, initialValues);
if (rowId <= 0) return null;
uri = ContentUris.withAppendedId(uri, rowId);
// 通知系统 favorites 表的数据已经发生了变化
sendNotify(uri);
return uri;
}
从这一点可以看出,监视器的回调方法 ContentObserver.onChange 实际上是由 Content Provider 在相应的方法(通常是 insert、 update 和 delete)中通知系统调用的
#Intent;
action=android.intent.action.MAIN;
category=android.intent.category.LAUNCHER;
launchFlags=Ox10200000;
package=com.mt.mtxx.mtxx;
component=com.mt.mtxx.mtxx/.TopViewActivity;
end
从 Intent 字段的值可以看出,单击美图秀秀快捷方式显示的窗口是 TopViewActivity,包是 com.mt.mtxx.mtxx,所以只要查询 intent 字段中包含 “component=com.mt.mtxx.mtxx/.TopViewActivity” 字符串即可确认 “美图秀秀” 是否已经在桌面上创建了快捷方式。查询代码如下:
Cursor cursor = getContentResolver(). query(Uri.parse("content://com.cyanogenmod.trebuchet.settings/favorites"), null, "intent like ?", new String[]{"%component=com.mt.mtxx.mtxx/.TopViewActivity%"}, null); // 如果结果集中有记录,那么桌面上至少存在一个美图秀秀的快捷方式 if(cursor.getCount() > 0) { Toast.makeText(this, "美图秀秀已经放在桌面上了", Toast.LENGTH_LONG).show(); } else { // 在桌面上创建美图秀秀的快捷方式 ...... } 在执行上面的代码时必须在 AndroidManifest.xml 文件中执行如下的权限 <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS"/> 如果要修改 favorites 表中的数据,需要指定下面的权限 <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS"/> 执行下面的代码可以删除桌面上的所有 "美图秀秀" 快捷方式 getContentResolver().delete(Uri.parse("content://com.cyanogenmod.trebuchet.settings/favorites"), "intent like ?", new String[]{"%component=com.mt.mtxx.mtxx/.TopViewActivity%"});
在 launcher.xml 布局文件中声明了一些控件,这些控件主要用来控制桌面 UI,所以可以统称为桌面 UI 控制器,这些控件主要包括 DragLayer、Workspace 等。在 Launcher.onCreate 方法中会调用 setup Views 方法来装载这些控件,并进行相应的设置。setupViews 方法的代码如下:
private void setupViews(){ final DragController dragController = mDragController; mLauncherView = findViewById(R.id.launcher); // 装载 DragLayer 控件 mDragLayer = (DragLayer) findViewById(R.id.drag_layer); // 装载 Workspace 控件 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); mQsbDivider = findViewById(R.id.qsb_divider); mDockDivider = findViewById(R.id.dock_divider); mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg); mBlackBackgroundDrawable = new ColorDrawable(Color.BLACK); // 设置拖动层 mDragLayer.setup(this, dragController); // Setup the hotseat mHotseat = (Hotseat) findViewById(R.id.hotseat); // 设置 workspace mWorkspace.setHapticFeedbackEnabled(false); mWorkspace.setOnLongClickListener(this); mWorkspace.setup(dragController); dragController.addDragListener(mWorkspace); // 获取搜索/删除 Bar mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar); // 如果我们隐藏了搜索条,那么也将隐藏搜索分割线 if (!mShowSearchBar && mQsbDivider != null && getCurrentOrientation() == Configuration.ORIENTATION_LANDSCAPE){ mQsbDivider.setVisibility(View.GONE); } if (!mShowHotseat){ mHotseat.setVisibility(View.GONE); } if (mShowDockDivider && mDockDivider != null){ mDockDivider.setVisibility(View.GONE); } // 设置应用程序列表窗口 mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane); mAppsCustomizeContent = (AppsCustomizePagedView)mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content); mAppsCustomizeContent.setup(this, dragController); // 设置拖动控制器 dragController.setDragScoller(mWorkspace); dragController.setScrollView(mDragLayer); dragController.setMoveTarget(mWorkspace); dragController.addDropTarget(mWorkspace); if (mSearchDropTargetBar != null){ // 设置搜索/删除 Bar mSearchDropTargetBar.setup(this, dragController); } }
在装载完桌面 UI 控制器后,将进行最重要的一步,就是装载桌面上的各种视图,包括快捷方式、App Widget、搜索/删除 Bar 等视图。除此之外,还装载了很多桌面 UI 需要的数据。例如,应用程序列表中显示的相关应用程序的信息。在 “2.初始化 Android 桌面” 一节中,给出的 onCreate 方法中使用了下面的代码完成了这些工作。
if (!mRestoring){
if (sPausedFromUserAction){
// 当暂时离开 Home 窗口,再返回后异步装载桌面 UI
mModel.startLoader(true, -1);
}
else{
// 如果配置变化,例如,屏幕旋转,则同步装载当前页
mModel.startLoader(true, mWorkspace.getCurrentPage());
}
}
从这段代码可以看出,完成这些工作分别使用了异步和同步的方式。不管是哪种装载方式,都调用了 LauncherModel.startLoader 方法。该方法的代码如下:
public void startLoader(boolean isLaunching, int synchronousBindPage){ synchronized(mLock){ if (DEBUG_LOADERS){ Log.d(TAG, "startLoader isLaunching=" + isLaunching); } // 在装载桌面 UI 视图之前必须清除所有用于运行的对象,否则可能会重复装载 mDeferredBindRunnables.clear(); // 如果设置了回调对象,并且可以获得回调对象,则完成下面的工作 if (mCallbacks != null && mCallbacks.get() != null){ // 如果当前正处于装载状态,则停止装载(在 LoaderTask 对象中停止) isLaunching = isLaunching || stopLoaderLocked(); // 创建用于同步或异步装载桌面 UI 的 LoaderTask 对象 mLoaderTask = new LoaderTask(mApp, isLaunching); if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded){ // 如果设置了要装载的页索引,则同步装载桌面 UI mLoaderTask.runBindSynchronousPage(synchronousBindPage); } else{ sWorkerThread.setPriority(Thread.NORM_PRIORITY); // 异步装载桌面 UI sWorker.post(mLoaderTask); } } } }
startLoader 方法看上去并不复杂,其中涉及一个非常关键的 LoaderTask 类,该类负责具体装载桌面 UI。当设置了要装载的页索引后,会调用 LoaderTask.runBindSynchronousPage 方法同步装载桌面 UI。如果未设置页索引(通常为 -1),则利用 HanderThread 对象(sWorkerThread 字段)异步调用 LoaderTask 对象装载桌面 UI
在异步装载桌面 UI 的过程中使用了 HandlerThread 对象开始一个新的线程,并且 Handler 对象(sWorker 字段)会与 HandlerThreader 对象绑定,所以调用 Handler.post 方法会在另一个线程中执行 LoaderTask.run 方法(LoaderTask 类实现了 Runnable 接口)。sWorkerThread 和 sWorker 字段的初始化代码如下:
private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
static{
sWorkerThread.start();
}
private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
LoaderTask 是 LauncherModel 的内嵌类,主要任务就是装载任务,所以 LoaderTask 也可称为任务装载器。这里指的任务就是待装载的 Android 应用程序的信息和 Workspace(桌面 UI)。下面先看一下 LoaderTask 类的代码:
private class LoaderTask implements Runnable{ private Context mContext; private boolean mIsLaunching; private boolean mIsLoadingAndBindingWorkspace; private boolean mStopped; private boolean mLoadAndBindStepFinished; private HashMap<Object, CharSequence> mLabelCache; LoaderTask(Context context, boolean isLaunching){ mContext = context; mIsLaunching = isLaunching; mLabelCache = new HashMap<Object, CharSequence>(); } boolean isLaunching(){ return mIsLaunching; } boolean isLoadingWorkspace(){ ruturn mIsLoadingAndBindingWorkspace; } // 装载和绑定所有的 Android 应用 private void loadAndBindAllApps(){ if (DEBUG_LOADERS){ Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded); } if (!mAllAppsLoaded){ // 批量装载所有的 Android 应用 loadAllAppsByBatch(); synchronized (LoaderTask.this){ if (mStopped){ return; } mAllAppsLoaded = true; } else{ // 仅仅绑定所有的 Android 应用 onlyBindAllApps(); } } // 装载和绑定 Workspace private void loadAndBindWorkspace(){ mIsLoadingAndBindingWorkspace = true; // Load the workspace if (DEBUG_LOADERS){ Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); } // 未被装载,继续装载 Workspace if (!mWorkspaceLoaded){ // 装载 Workspace loadWorkspace(); synchronized (LoaderTask.this){ if (mStopped){ return; } mWorkspaceLoaded = true; } } // 绑定 Workspace bingWorkspace(-1); } ...... void runBindSynchronousPage(int synchronousBindPage){ if (synchronousBindPage < 0){ // Ensure that we have a valid page index to load synchronously throw new RuntimeException("Should not call runBindSynchronousPage() without " + "valid page index"); } if (!mAllAppsLoaded || !mWorkspaceLoaded){ // Ensure that we don't try and bind a specified page when the pages have not been // loaded already (we should load everything asynchronously in that case) throw new RuntimeException("Expecting AllApps and Workspace to be loaded"); } synchronized (mLock){ if (mIsLoaderTaskRunning){ // Ensure that we are never running the background loading at this point since // we also touch the background collections throw new RuntimeException("Error! Background loading is already running"); } } // 绑定 Workspace bindWorkspace(synchronousBindPage); // 仅仅绑定所有的 Android 应用 onlyBindAllApps(); } public void run(){ synchronized (mLock){ mIsLoaderTaskRunning = true; } final Callbacks cbk = mCallbacks.get(); // 如果未设置回调对象,或当前显示的是应用程序列表,则优先装载应用程序 // (调用 loadAndBindAllApps 方法),否则优先装载 Workspace(默认值)也就是桌面上的视图 final boolean loaWorkspaceFirst = cbk == null || !cbk.isAllAppsVisible(); keep_running:{ synchronized (mLock){ if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " + mIsLaunching ? "DEFAULT" : "BACKGROUND")); android.os.Process.setThreadPriority(mIsLaunching ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); } // 第一步 if (loadWorkspaceFirst){ if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); // 通常来讲,会首先绑定 Workspace loadAndBindWorkspace(); } else{ if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps"); // 装载和绑定所有的 Android 应用 loadAndBindAllApps(); } if (mStopped){ break keep_running; } .... // 第 2 步 if (loadWorkspaceFirst){ if (DEBUG_LOADERS) Log.d(TAG, "step 2:loading all apps"); // 装载和绑定所有的 Android 应用 loadAndBindAllApps(); } else{ if (DEBUG_LOADERS) Log.d(TAG, "step 2:special: loading workspace"); // 装载和绑定 Workspace loadAndBindWorkspace(); } ...... } ...... } public void stopLocked() { synchronized (LoaderTask.this){ mStopped = true; this.notify(); } } ...... private void loadWorkspace(){ ...... } }
从 LoaderTask 类的代码可以看到一些熟悉的方法。例如,runBindSynchronousPage 就是用于同步装载指定页的方法,而 run 方法则用于异步装载桌面 UI。不管是 runBindSynchronousPage 方法,还是 run 方法,完成的主要工作只有如下两个:
对于 Android 应用来说,装载和绑定是 或 的关系。也就是说,如果 android 应用还没有装载,loadAndBindAllApps 会调用 loadAllAppsByBatch 方法批量装载 Android 应用,如果 Android 应用已经被装载,则会调用 onlyBindAllApp 方法进行绑定
对于 Workspace 来说,需要先调用 loadWorkspace 方法装载 Workspace,然后调用 bindWorkspace 方法绑定 Workspace,所以现在处理 Workspace 和 Android 应用的问题已经转换成了如下4个方法。在后面的部分会详细分析这几个方法的实现原理。
本节会详细分析 Launcher 2 装载 Workspace 区域的原理,读者可以从中获得大量信息,这些信息中很多在其他的文档中极难获得
装载 Workspace 实际上主要是装载桌面上显示的视图,例如,快捷方式图标、App Widgets、文件夹等。这些工作完全是在 loadWorkspace 方法中完成的,所以 loadWorkspace 方法的代码会比较多。下面就让我们看一下 loadWorkspace 方法的实现代码,以便对 Launcher2 如何装载桌面视图有更深入的了解
由于 loadWorkspace 方法的代码比较大,所以在分析 loadWorkspace 方法的实现原理之前最好先搞清楚 loadWorkspace 方法到底做了哪些工作。其实别看 loadWorkspace 方法有数百行的代码,不过该方法其实自始至终只完成了一件最要的工作,就是根据 favorites 表中的记录在桌面上添加各种快捷方式和 AppWidget:
Private void loadWorkspace(){ final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; final Context context = mContext; // 在后面会使用到一些管理对象,所以这里先获得这些对象 final ContentResolver contentResolver = context.getContentResolver(); final PackageManager manager = context.getPackageManager(); final AppWidgetManager widgets = AppWidgetManager.getInstance(context); final boolean isSafeMode = manager.isSaf eMode(); // 如果必须,会在桌面上添加默认的快捷方式,默认快捷方式通常会 // 使用 res/xml/default_workspace.xml 资源文件中描述的布局 mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(0); synchronized (sBgLock){ sBgWorkspaceItems.clear(); sBgAppWidgets.clear(); sBgFolders.clear(); sBgItemsIdMap.clear(); sBgDbIconCache.clear(); final ArrayList<Long> ItemsToRemove = new ArrayList<Long>(); // 查询 favorites 表中的所有数据 final Cursor c = contentResolver.query(LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); // 存储桌面视图的相应信息 final ItemInfo occupied[][][] = new ItemInfo[Launcher.MAX_SCREEN_COUNT][Math.max(sWorkspaceCellCountX, sHotseatCellCount)][Math.max(sWorkspaceCellCountY, sHotseatCellCount)]; try{ // 下面的代码获取 favorites 表中所有需要使用到的字段索引 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); final int itemTypeIndex= c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWIDGET_ID); final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX); final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY); ShortcutInfo info; String intentDescription; LauncherAppWidgetInfo appWidgetInfo; int container; long id; Intent intent = null; // 循环扫描 favorites 表中的所有数据(每一条记录相当于一个桌面视图信息),并根据信息类型 // (快捷方式、文件夹、APPWidget 等)添加相应的视图 while (!mStopped && c.moveToNext()){ try{ // 获取当前添加项的类型 int itemType = c.getInt(itemTypeIndex); switch (itemType){ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: // 指向 Android 应用的快捷方式 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: // 指向 Uri 的快捷方式 intentDescription = c.getString(intentIndex); try{ intent = Intent.parseUri(intentDescription, 0); } catch (URISyntaxException e){ continue; } case LauncherSettings.Favorites.ITEM_TYPE_ALLAPPS: // 显示所有应用程序的快捷方式,该快捷方式通常只有一个, // 也就是屏幕最下方 5 个按钮的中间一个(一个圆圈中有两排省略号的按钮) if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION){ info = getShortcutInfo(manager, intent, context, c, iconIndex, titleIndex, mLabelCache); } else if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT){ info = getShortcutInfo(c, context, iconTypeIndex, iconPackageIndex, iconResourceIndex, iconIndex, titleIndex); // App shortcuts that used to be automatically added to Launcher // didn't always have the correct intent flags set, so do that here if (intent.getAction() != null && intent.getCategories() != null && intent.getAction().equals(Intent.ACTION_MAIN) && intent.Categories().contains(Intent.CATEGORY_LAUNCHER)){ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); } } else{ info = getShortcutInfo(c, context, iconTypeIndex, iconPackageIndex, iconResourceIndex, iconIndex, titleIndex); info.itemType = LauncherSettings.Favorites.ITEM_TYPE_ALLAPPS; } if (info != null){ info.intent = intent; info.id = c.getLong(idIndex); container = c.getInt(containerIndex); info.container = container; info.screen = c.getInt(screenIndex); info.cellX = c.getInt(cellXIndex); info.cellY = c.getInt(cellYIndex); // check & update map of what's occupied if (!checkItemPlacement(occupied, info)){ break; } // 在不同的容器中添加相应的快捷方式 switch (container){ case LauncherSettings.Favorites.CONTAINER_DESKTOP: // 在桌面上添加快捷方式 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: // 在 Hotseat 区域添加快捷方式,也就是屏幕最下方的区域,该区域不会 // 随着桌面左右滑动而滑动,该区域通常最多有 5 个快捷方式,中间的快捷 // 方式可以显示所有的 Android 应用列表 sBgWorkspaceItems.add(info); break; default: // 该项目是一个用户文件夹 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, container); folderInfo.add(info); break; } sBgItemsIdMap.put(info.id, info); // now that we've loaded everything re-save it with the // icon in case it disappears somehow. queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex); } else { // 装载当前快捷方式失败,失败的原因可能是因为 Android 应用已删除,或 // favorites 表中对应的记录有问题。处理的结果是删除这样的记录。 // 获取当前技术的 ID(_id 字段的值) id = c.getLong(idIndex); Log.e(TAG, "Error loading shortcut " + id + ", removing it"); // 删除当前记录 contentResolver.delete(LauncherSettings.Favorites.getContentUri(id, false), null, null); } break; case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: // 当前要添加的桌面视图类型是文件夹 id = c.getLong(idIndex); FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id); folderInfo.title = c.getString(titleIndex); folderInfo.id = id; container = c.getInt(containerIndex); folderInfo.container = container; folderInfo.screen = c.getInt(screenIndex); folderInfo.cellX = c.getInt(cellXIndex); folderInfo.cellY = c.getInt(cellYIndex); // check & update map of what's occupied if (!checkItemPlacement(occupied, folderInfo)){ break; } switch (container){ case LauncherSettings.Favorites.CONTAINER_DESKTOP: // 在桌面上添加文件夹 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: // 将文件夹也作为快捷方式的一种添加到 sBgWorkspaceItems 中 sBgWorkspaceItems.add(folderInfo); break; } sBgItemsIdMap.put(folderInfo.id, folderInfo); sBgFolders.put(folderInfo.id, folderInfo); break; case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: // 要添加的桌面视图类型是 App Widget int appWidgetId = c.getInt(appWidgetIdIndex); id = c.getLong(idIndex); final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(appWidgetId); if (!isSafeMode && (provider == null || provider.provider == null || provider.provider.getPackageName() == null)){ String log = "Deleting Widget that isn't installed anymore: id=" + id + " appWidgetId=" + appWidgetId; Log.e(TAG, log); Launcher.sDumpLogs.add(log); itemsToRemove.add(id); } else{ appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, provider.provider); appWidgetInfo.id = id; appWidgetInfo.screen = c.getInt(screenIndex); appWidgetInfo.cellX = c.getInt(cellXIndex); appWidgetInfo.cellY = c.getInt(cellYIndex); appWidgetInfo.spanX = c.getInt(spanXIndex); appWidgetInfo.spanY = c.getInt(spanYIndex); int[] minSpan = Launcher.getMinSpanForWidget(context, provider); appWidgetInfo.MinSpanX = minSpan[0]; appWidgetInfo.MinSpanY = minSpan[1]; container = c.getInt(containerIndex); if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && container != LauncherSettings.Favorites.CONTAINER_HOTSEAT){ Log.e(TAG, "Widget found where container != " + "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); continue; } appWidgetInfo.container = c.getInt(containerIndex); // check & update map of what's occupied if (!checkItemPlacement(occupied, appWidgetInfo)){ break; } sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); sBgAppWidgets.add(appWidgetInfo); } break; } } catch (Exception e){ Log.w(TAG, "Desktop items loading interrupted:", e); } } } finally{ c.close(); } // 将需要删除的记录从 favorites 表中移除 if (itemsToRemove.size() > 0){ ContentProviderClient client = contentResolver.acquireContentProviderClient(LauncherSettings.Favorites.CONTENT_URI); // Remove dead items for (long id : itemsToRemove){ if (DEBUG_LOADERS){ Log.d(TAG, "Removed id = " + id); } // Don't notify content observers try{ client.delete(LauncherSettings.Favorites.getContentUri(id, false), null, null); } catch (RemoteException e){ Log.w(TAG, "Could not remove id = " + id); } } } ...... } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。