关于Lancher3,我一直觉得很复杂,没有时间去研究它(主要是懒),最近遇到项目的问题(赶鸭子上架),学习了一段时间,于是边记录自己的经验,边和大家共同进步.
Launcher3 和 Launcher3QuickStep 区别
一直迷糊这两个到底有啥区别,边看源码边找文档,终于搞明白了,先说区别,Launcher3QuickStep包含了recents,也就是最近启动的任务列表,可以清理,就是下面这个功能
当时解决问题的时候,单编的时候就有点懵了,应该编译哪个modue,于是找了bp 文件
// Build rule for Launcher3 app.
//
android_app {
name: "Launcher3",
static_libs: [
"Launcher3CommonDepsLib",
],
srcs: [
"src/**/*.java",
"src_shortcuts_overrides/**/*.java",
"src_ui_overrides/**/*.java",
"ext_tests/src/**/*.java",
],
resource_dirs: [
"ext_tests/res",
],
//....
overrides: [
"Home",
"Launcher2",
],
//....
}
那应该是Launcher3 没错了,可是系统的目录并没有这个apk,显然不是了,后来又发现同目录显得android.mk
include $(CLEAR_VARS)
//....
LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3QuickStepLib
//.....
ifneq (,$(wildcard frameworks/base))
LOCAL_PRIVATE_PLATFORM_APIS := true
else
LOCAL_SDK_VERSION := system_current
LOCAL_MIN_SDK_VERSION := 26
endif
LOCAL_PACKAGE_NAME := Launcher3QuickStep
LOCAL_PRIVILEGED_MODULE := true
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 //
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
$(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
//....
include $(BUILD_PACKAGE)
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
可以看到Launcher3QuickStep 是覆盖Launcher3 的,也就是说其实用的是Launcher3QuickStep
看下编译文件build/make/target/product/handheld_system_ext.mk
# /system_ext packages
24 PRODUCT_PACKAGES += \
25 Launcher3QuickStep \
26 Provision \
27 Settings \
28 StorageManager \
29 SystemUI \
30 WallpaperCropper
我贴地修改了为Launcher3 发现确实是没有了recents ,借鉴一下展讯平台的图
序号 | Launcher模块名 | OS | 特性简介 | 备注 |
1 | Launcher3 | Android 9.0-Gc | 提供基础的Home功能 | 会被其他形态的Launcher覆盖 |
2 | Launcher3QuickStep | 在Launchers基础上增加了Quickstep功能。 | AOSP默认Launcher,适用于不带GMS 项目 | |
3 | Launcher3NoQsb | 在Launcher3QuickStep基础上移除了首页固定的Google Search Bar | 需配合GMS包编译,EEA版本专用 | |
4 | SearchLauncher | 在Launcher3的基础上,增加了Google Feed (-1 Screen ) | 需配合GMS包编译,适用于带GMS项目 | |
5 | SearchLauncherQuickStep | 在SearchLauncher的基础上增加了Quickstep1功能。 | ||
6 | Launcher3Go | Android 9.0-Go | 在Launcher3的基础上移除了Widget、Notification dots等功能。 | 适用于512M Ram项目 |
7 | Launcher3QuickStepGo | 在Launcher3Go基础上增加了QuickStepi功能 | 适用于1G Ram项目 | |
8 | Launcher3GoNoQsb | 在Launcher3Go基础上移除了首页固定的Google Search Bar | 需配合GMS包编译,EEA版本专用 |
ps 粉色部分是展讯平台特有的,适用于GMS ,平台为我们开发好了,其他部分是通用的
但是GMS版本Launcher 也有一个问题就是无论用那个版本,都会覆盖抽屉界面的搜索应用的框,这样又有点不太好了,感觉就很暴力(流氓),强制别人用google 的搜索,如何如修改回来呢,这个要感谢同事给的解决方法,后面会说.
但是无论GMS 版本还是非GMS版本,都有一个小问题
1,GMS 版本会覆盖抽屉界面的搜索应用的框,这样又有点不太好了,感觉就很暴力(流氓),强制别人用google 的搜索,如何如修改回来呢,这个要感谢同事给的解决方法思路,后面会说.
2,非GMS 版本倒是应用搜索框还在,但是又没有负一屏了,真是鱼和熊掌不可兼得呀,惆怅.
Launcher 目录功能分类
首页
找首页当然是去找Manifest 里面,里面只一个Launcher.java,没得挑了,就是他了,刚开始开着有点迷糊,里面就包含一个launcher.xml 抽屉布局.一层套一层,就注意一点就可以
protected LauncherOverlayManager getDefaultOverlay() {
return new LauncherOverlayManager() { };
}
后面需要覆盖原生的界面,就可以继承这个进行overlay 原生布局
主页布局-提供主页的数据和工作去的文件夹
/**
* Runnable for the thread that loads the contents of the launcher:
* - workspace icons
* - widgets
* - all apps icons
* - deep shortcuts within apps
*/
com.android.launcher3.model.LoaderTask 加载所有应用的线程
代码是真多呀,我们先一步步解读下,
public void run() {
//.......
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
logASplit(logger, "loadWorkspace");
// Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
// sanitizeData should not be invoked if the workspace is loaded from a db different
// from the main db as defined in the invariant device profile.
// (e.g. both grid preview and minimal device mode uses a different db)
if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
verifyNotStopped();
sanitizeData();
logASplit(logger, "sanitizeData");
}
verifyNotStopped();
mResults.bindWorkspace();
logASplit(logger, "bindWorkspace");
mModelDelegate.workspaceLoadComplete();
// Notify the installer packages of packages with active installs on the first screen.
sendFirstScreenActiveInstallsBroadcast();
logASplit(logger, "sendFirstScreenActiveInstallsBroadcast");
// Take a break
waitForIdle();
logASplit(logger, "step 1 complete");
verifyNotStopped();
// second step
List<LauncherActivityInfo> allActivityList = loadAllApps();
logASplit(logger, "loadAllApps");
verifyNotStopped();
mResults.bindAllApps();
logASplit(logger, "bindAllApps");
verifyNotStopped();
IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
setIgnorePackages(updateHandler);
updateHandler.updateIcons(allActivityList,
LauncherActivityCachingLogic.newInstance(mApp.getContext()),
mApp.getModel()::onPackageIconsUpdated);
logASplit(logger, "update icon cache");
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
verifyNotStopped();
logASplit(logger, "save shortcuts in icon cache");
updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
mApp.getModel()::onPackageIconsUpdated);
}
// Take a break
waitForIdle();
logASplit(logger, "step 2 complete");
verifyNotStopped();
// third step
List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
logASplit(logger, "loadDeepShortcuts");
verifyNotStopped();
mResults.bindDeepShortcuts();
logASplit(logger, "bindDeepShortcuts");
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
verifyNotStopped();
logASplit(logger, "save deep shortcuts in icon cache");
updateHandler.updateIcons(allDeepShortcuts,
new ShortcutCachingLogic(), (pkgs, user) -> { });
}
// Take a break
waitForIdle();
logASplit(logger, "step 3 complete");
verifyNotStopped();
// fourth step
List<ComponentWithLabelAndIcon> allWidgetsList =
mBgDataModel.widgetsModel.update(mApp, null);
logASplit(logger, "load widgets");
verifyNotStopped();
mResults.bindWidgets();
logASplit(logger, "bindWidgets");
verifyNotStopped();
updateHandler.updateIcons(allWidgetsList,
new ComponentWithIconCachingLogic(mApp.getContext(), true),
mApp.getModel()::onWidgetLabelsUpdated);
logASplit(logger, "save widgets in icon cache");
// fifth step
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
loadFolderNames();
}
verifyNotStopped();
updateHandler.finish();
logASplit(logger, "finish icon update");
mModelDelegate.modelLoadComplete();
transaction.commit();
} catch (CancellationException e) {
// Loader stopped, ignore
logASplit(logger, "Cancelled");
} finally {
logger.dumpToLog();
}
TraceHelper.INSTANCE.endSection(traceToken);
}
google 工程师其实也加了注释,总体是这五步
/packages/apps/Launcher3 / src/com/android/launcher3/LauncherProvider.java
/**
* Loads the default workspace based on the following priority scheme:
* 1) From the app restrictions
* 2) From a package provided by play store
* 3) From a partner configuration APK, already in the system image
* 4) The default configuration for the particular device
*/
synchronized private void loadDefaultFavoritesIfNecessary() {
SharedPreferences sp = Utilities.getPrefs(getContext());
if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
Log.d(TAG, "loading default workspace");
AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
if (loader == null) {
loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
}
if (loader == null) {
final Partner partner = Partner.get(getContext().getPackageManager());
if (partner != null && partner.hasDefaultLayout()) {
final Resources partnerRes = partner.getResources();
int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
"xml", partner.getPackageName());
if (workspaceResId != 0) {
loader = new DefaultLayoutParser(getContext(), widgetHost,
mOpenHelper, partnerRes, workspaceResId);
}
}
}
final boolean usingExternallyProvidedLayout = loader != null;
if (loader == null) {
loader = getDefaultLayoutParser(widgetHost);
}
// There might be some partially restored DB items, due to buggy restore logic in
// previous versions of launcher.
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
// Populate favorites table with initial favorites
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
&& usingExternallyProvidedLayout) {
// Unable to load external layout. Cleanup and load the internal layout.
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
getDefaultLayoutParser(widgetHost));
}
clearFlagEmptyDbCreated();
}
}
对应的xml
packages/apps/Launcher3/res/xml/default_workspace_*.xml
看注释,应该是默认加载的首页应用布局,可以在上面xml 修改自己的默认.修改位置,数据库有数据,读取数据库,没有读取默认
抽屉页面
找不到的话就先找布局,通过搜索框里面的关键字,"Search apps",对应布局packages/apps/Launcher3/res/layout/secondary_launcher.xml 这个就是抽屉页面的主要布局
对应Activity com.android.launcher3.secondarydisplay
这里面主要注意的是,搜索应用框,后面定制需要注意
<com.android.launcher3.allapps.search.AppsSearchContainerLayout
android:id="@id/search_container_all_apps"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_field_height"
android:layout_centerHorizontal="true"
android:layout_gravity="top|center_horizontal"
android:background="@drawable/bg_all_apps_searchbox"
android:elevation="1dp"
android:focusableInTouchMode="true"
android:gravity="center"
android:hint="@string/all_apps_search_bar_hint"
android:imeOptions="actionSearch|flagNoExtractUi"
android:inputType="text|textNoSuggestions|textCapWords"
android:maxLines="1"
android:padding="8dp"
android:saveEnabled="false"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textColorHint="@drawable/all_apps_search_hint"
android:textSize="16sp" />
app 视图布局
com.android.launcher3.allapps.AllAppsContainerView
整理了一下遇到的问题
[Q1]搜索应用返回键无法清除输入框状态
packages/apps/Launcher3
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 21bc479..64bf1d0 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -117,6 +117,9 @@
}
public void hideKeyboard() {
+ clearFocus();
hideKeyboardAsync(ActivityContext.lookupContext(getContext()), getWindowToken());
}
[Q2]隐藏首页的google 搜索组件
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 1b6001f..e7599ae 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -53,7 +53,9 @@
* and should be modified at a project level.
*/
+ public static boolean QSB_ON_FIRST_SCREEN = false;
/**
[Q3]文件夹中放入三个应用,图标溢出边界
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 8cd91d3..042559f 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -130,7 +130,9 @@
public float scaleForItem(int numItems) {
// Scale is determined by the number of items in the preview.
final float scale;
- if (numItems <= 3) {
+ if (numItems < 3) {
scale = MAX_SCALE;
} else {
scale = MIN_SCALE;
[Q4]GMS 覆盖原生framework res 配置路径
overlay/GmsConfigOverlayCommon/res/values/config.xml
[Q5]GMS 覆盖原生app 和service
有其他需要覆盖的,可以对应应用的路径,在gms_overlay 下面创建文件覆盖即可
[Q6]GMS 版本无搜索应用框
前面也说道了,GMS 编译的是SearchlauncherQuickStep,修改vendor/partner_gms
1,SearchLauncher+ 原生SearchLauncher.java
diff --git a/apps/SearchLauncher/Android.mk b/apps/SearchLauncher/Android.mk
index 1dd35fc..9dd9cfa 100644
--- a/apps/SearchLauncher/Android.mk
+++ b/apps/SearchLauncher/Android.mk
@@ -23,32 +23,43 @@
LAUNCHER_PATH := ../../../../packages/apps/Launcher3
LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib Launcher3QuickStepLib
-LOCAL_STATIC_JAVA_LIBRARIES := lib_launcherClient
+LOCAL_STATIC_JAVA_LIBRARIES := lib_launcherClient \
+ SystemUISharedLib \
+ SystemUI-statsd
LOCAL_SRC_FILES := \
$(call all-java-files-under, $(LAUNCHER_PATH)/src_shortcuts_overrides) \
- $(call all-java-files-under, $(LAUNCHER_PATH)/src_ui_overrides) \
$(call all-java-files-under, src) \
- $(call all-java-files-under, src_base) \
- $(call all-java-files-under, $(LAUNCHER_PATH)/src)
+ $(call all-java-files-under, src_sim_base) \
+ $(call all-java-files-under, $(LAUNCHER_PATH)/src) \
+ $(call all-java-files-under, $(LAUNCHER_PATH)/quickstep/src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
+ $(LOCAL_PATH)/$(LAUNCHER_PATH)/quickstep/res
LOCAL_PROGUARD_FLAG_FILES := $(LAUNCHER_PATH)/proguard.flags
-LOCAL_MIN_SDK_VERSION := 28
-LOCAL_SDK_VERSION := current
+
+ifneq (,$(wildcard frameworks/base))
+ LOCAL_PRIVATE_PLATFORM_APIS := true
+else
+ LOCAL_SDK_VERSION := system_current
+ LOCAL_MIN_SDK_VERSION := 28
+endif
+
LOCAL_PACKAGE_NAME := SearchLauncher
LOCAL_MODULE_TAGS := optional
LOCAL_USE_AAPT2 := true
LOCAL_AAPT2_ONLY := true
+
LOCAL_PRIVILEGED_MODULE := true
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/$(LAUNCHER_PATH)/quickstep/AndroidManifest.xml
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-BSD SPDX-license-identifier-GPL SPDX-license-identifier-MIT
LOCAL_LICENSE_CONDITIONS := notice restricted
include $(BUILD_PACKAGE)
diff --git a/apps/SearchLauncher/src_sim_base/com/android/searchlauncher/SearchLauncher.java b/apps/SearchLauncher/src_sim_base/com/android/searchlauncher/SearchLauncher.java
new file mode 100644
index 0000000..cb7180e
--- /dev/null
+++ b/apps/SearchLauncher/src_sim_base/com/android/searchlauncher/SearchLauncher.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 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.
+ * SIM1FU010 add for PM-65101 by 00467 at 20220426
+ */
+
+package com.android.searchlauncher;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.systemui.plugins.shared.LauncherOverlayManager;
+
+public class SearchLauncher extends QuickstepLauncher {
+
+ @Override
+ protected LauncherOverlayManager getDefaultOverlay() {
+ return new OverlayCallbackImpl(this);
+ }
+}
diff --git a/products/gms.mk b/products/gms.mk
index 50f2695..e823e07 100644
--- a/products/gms.mk
+++ b/products/gms.mk
@@ -97,7 +97,7 @@
# GMS sample application packages
PRODUCT_PACKAGES += \
GmsSampleIntegration \
- SearchLauncherQuickStep
+ SearchLauncher
include $(ANDROID_PARTNER_GMS_HOME)/products/gms_package_version.mk
diff --git a/products/gms_eea_v2_type4c.mk b/products/gms_eea_v2_type4c.mk
index d91d059..80f6ac1 100644
--- a/products/gms_eea_v2_type4c.mk
+++ b/products/gms_eea_v2_type4c.mk
@@ -7,4 +7,4 @@
Chrome \
SearchSelector \
GmsEEAType4cIntegration \
- SearchLauncherQuickStep
+ SearchLauncher
2,SearchLauncherQuickStep + search 框
diff --git a/apps/SearchLauncher/quickstep/res/layout/search_container_all_apps.xml b/apps/SearchLauncher/quickstep/res/layout/search_container_all_apps.xml
index 1fae132..d6784c6 100644
--- a/apps/SearchLauncher/quickstep/res/layout/search_container_all_apps.xml
+++ b/apps/SearchLauncher/quickstep/res/layout/search_container_all_apps.xml
@@ -49,5 +49,5 @@
android:textColor="?android:attr/textColorSecondary"
android:textColorHint="@drawable/all_apps_search_hint"
android:textSize="16sp"
- android:visibility="invisible" />
只是根据根据自己遇到的问题,由浅入深慢慢分析,写的不对的,还请指正,后续会继续研究Launcher,持续更新.