一、分析文件介绍
- ActivityManagerService.java
- ActivityTaskManagerInternal.java
- ActivityTaskManagerService.java
- RootActivityContainer.java
- ActivityStartController.java
- LauncherProvider.java
- Launcher.java
- InvariantDeviceProfile.java
- MultiModeController.java
- LauncherProvider.java
- MultiModeUtilities.java
- LauncherSettingsExtension.java
- FeatureOption.java
- config_ext.xml
- device_profiles.xml
二、相关文件介绍
ActivityManagerService.java
Activity会调用startHomeActivityLocked方法,此方法会创建一个Intent,mTopAction和mTopData传给Intent,其中mTopAction为Intent.ACTION_MAIN,Intent的category为android.intent.category.Home。而Launcher的AndroidMainfest.xml文件里面给Launcher定义的category也是Home,根据匹配原则,这样就会启动这个Launcher。
源码位置:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
public ActivityTaskManagerInternal mAtmInternal;
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
....
//启动Launcher
mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
.....
}
}
ActivityTaskManagerInternal.java和ActivityTaskManagerService.java
ActivityTaskManagerInternal.java是一个抽象类,里面定义了启动Luancher的方法,具体实现是由LocalService类,它定义在了ActivityTaskManagerService.java文件中
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
/** @return The intent used to launch the home activity. */
public abstract Intent getHomeIntent();
public abstract boolean startHomeActivity(int userId, String reason);
public abstract boolean startHomeOnDisplay(int userId, String reason, int displayId,
boolean allowInstrumenting, boolean fromHomeKey);
/** Start home activities on all displays that support system decorations. */
public abstract boolean startHomeOnAllDisplays(int userId, String reason);
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
内部类LocalService
public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
private ActivityStartController mActivityStartController; //Activity启动控制器
ActivityStartController getActivityStartController() {
return mActivityStartController;
}
//注意看intent.addCategory(Intent.CATEGORY_HOME),这个代表的就是要启动Activity的意图,通常来说,整个系统的只会有一个应用会在清单文件中配置CATEGORY_HOME,如果配置了多个,系统在启动的时候就会要求用户手动去选择哪个作为启动应用,如果在系统设置应用中进行配置了,就会选择配置的那个应用启动。
Intent getHomeIntent() {
Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
intent.setComponent(mTopComponent);
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);
}
return intent;
}
final class LocalService extends ActivityTaskManagerInternal {
RootActivityContainer mRootActivityContainer; //实际上调用了RootActivityContainer 中的方法
@Override
public Intent getHomeIntent() {
synchronized (mGlobalLock) {
return ActivityTaskManagerService.this.getHomeIntent();
}
}
@Override
public boolean startHomeActivity(int userId, String reason) {
synchronized (mGlobalLock) {
return mRootActivityContainer.startHomeOnDisplay(userId, reason, DEFAULT_DISPLAY);
}
}
@Override
public boolean startHomeOnDisplay(int userId, String reason, int displayId,
boolean allowInstrumenting, boolean fromHomeKey) {
synchronized (mGlobalLock) {
return mRootActivityContainer.startHomeOnDisplay(userId, reason, displayId,
allowInstrumenting, fromHomeKey);
}
}
@Override
public boolean startHomeOnAllDisplays(int userId, String reason) {
synchronized (mGlobalLock) {
return mRootActivityContainer.startHomeOnAllDisplays(userId, reason);
}
}
}
...
}
RootActivityContainer.java
frameworks/base/services/core/java/com/android/server/wm/RootActivityContainer.java
class RootActivityContainer extends ConfigurationContainer
implements DisplayManager.DisplayListener {
ActivityTaskManagerService mService;
//在所有显示设备上启动Home
boolean startHomeOnAllDisplays(int userId, String reason) {
boolean homeStarted = false;
for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
final int displayId = mActivityDisplays.get(i).mDisplayId;
homeStarted |= startHomeOnDisplay(userId, reason, displayId);
}
return homeStarted;
}
boolean startHomeOnDisplay(int userId, String reason, int displayId) {
return startHomeOnDisplay(userId, reason, displayId, false /* allowInstrumenting */,
false /* fromHomeKey */);
}
//这将在可以基于 display 的系统装饰的显示器上启动主页活动Id 默认显示器始终使用主要主页组件
//对于辅助显示,主页活动必须具有类别SECONDARY_HOME然后解析根据下面列出的优先级。
// -如果未设置默认主页,请始终使用配置中定义的辅助主页
//-使用当前选定的主要活动
//-使用与当前选定的主要家庭活动相同的套餐中的活动,如果有多个匹配的活动,请使用第一个活动。
//-使用配置中定义的辅助主页
boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,
boolean fromHomeKey) {
// Fallback to top focused display if the displayId is invalid.
if (displayId == INVALID_DISPLAY) {
displayId = getTopDisplayFocusedStack().mDisplayId;
}
Intent homeIntent = null;
ActivityInfo aInfo = null;
if (displayId == DEFAULT_DISPLAY) {
homeIntent = mService.getHomeIntent(); //获取到需要启动的intent
aInfo = resolveHomeActivity(userId, homeIntent); //解析出需要启动Activity的信息
} else if (shouldPlaceSecondaryHomeOnDisplay(displayId)) {
Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, displayId);
aInfo = info.first;
homeIntent = info.second;
}
if (aInfo == null || homeIntent == null) {
return false;
}
if (!canStartHomeOnDisplay(aInfo, displayId, allowInstrumenting)) {
return false;
}
// 更新home Intent
homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
// Updates the extra information of the intent.
if (fromHomeKey) {
homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
}
// Update the reason for ANR debugging to verify if the user activity is the one that
//开始启动Launcher
final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
aInfo.applicationInfo.uid) + ":" + displayId;
mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
displayId);
return true;
}
}
ActivityStartController.java
frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java
用于控制委派启动的Activity
public class ActivityStartController {
void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, int displayId) {
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
if (!ActivityRecord.isResolverActivity(aInfo.name)) {
// The resolver activity shouldn't be put in home stack because when the foreground is
// standard type activity, the resolver activity should be put on the top of current
// foreground instead of bring home stack to front.
options.setLaunchActivityType(ACTIVITY_TYPE_HOME);
}
options.setLaunchDisplayId(displayId);
//此处执行启动Launcher,intent中包含了ACTION:"android.intent.action.MAIN"和category:"android.intent.category.HOME",这样就直接启动了拥有这两个属性的Activity
mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
.setOutActivity(tmpOutRecord)
.setCallingUid(0)
.setActivityInfo(aInfo)
.setActivityOptions(options.toBundle())
.execute();
mLastHomeActivityStartRecord = tmpOutRecord[0];
final ActivityDisplay display =
mService.mRootActivityContainer.getActivityDisplay(displayId);
final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
if (homeStack != null && homeStack.mInResumeTopActivity) {
// If we are in resume section already, home activity will be initialized, but not
// resumed (to avoid recursive resume) and will stay that way until something pokes it
// again. We need to schedule another resume.
mSupervisor.scheduleResumeTopActivities();
}
}
}
Laucnher3的AndroidManifest.xml
可以看到com.android.launcher3.Launcher这个Activity定义了作为Launcher的ACTION和CATEGORY
<activity
android:name="com.android.launcher3.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
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.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
<category android:name="android.intent.category.LAUNCHER_APP" />
</intent-filter>
<meta-data
android:name="com.android.launcher3.grid.control"
android:value="${packageName}.grid_control" />
</activity>
LauncherProvider.java
packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java
public class LauncherProvider extends ContentProvider {
public static final int SCHEMA_VERSION = 28; //数据库版本号
@Override
public boolean onCreate() {
if (LOGD) LogUtils.d(TAG, "Launcher process started");
mListenerHandler = new Handler(mListenerWrapper);
// 内容提供程序在启动器主进程的整个持续时间内都存在,并且是要创建的第一个组件。
MainProcessInitializer.initialize(getContext().getApplicationContext());
return true;
}
}
Launcher.java
public class Launcher extends BaseDraggingActivity implements LauncherExterns,
LauncherModel.Callbacks, LauncherProviderChangeListener, UserEventDelegate,
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "bianjb onCreate");
RaceConditionTracker.onEvent(ON_CREATE_EVT, ENTER);
//debug时启用严格模式
if (DEBUG_STRICT_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
TraceHelper.beginSection("Launcher-onCreate");
//Launcher监听器,展锐新加,用于监听并重新控制Launcher的行为
mAppMonitor = LauncherAppMonitor.getInstance(this);
mAppMonitor.onLauncherPreCreate(this);
}
LauncherAppMonitor.java(展锐)
packages/apps/Launcher3/src/com/sprd/ext/LauncherAppMonitor.java
InvariantDeviceProfile.java
packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
顾名思义,InvariantDeviceProfile是把显示相关的常量在这里初始化
调用构造方法
它有多个构造方法,调用的是下面这个
//程序加载时就创建了一个静态MainThreadInitializedObject对象,并创建InvariantDeviceProfile作为构造参数传给MainThreadInitializedObject。双冒号为java8 lambda表达式
public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
//gridname的key,通过它获取gridname值
public static final String KEY_IDP_GRID_NAME = "idp_grid_name";
private InvariantDeviceProfile(Context context) {
Log.d(TAG, "bianjb Constructor1");
try{
throw new RuntimeException("打印栈调用测试~!");
}catch(Exception e){
e.printStackTrace();
}
//初始化LauncherAppMonitor,下面有介绍
mMonitor = LauncherAppMonitor.getInstance(context);
mIdpGridKey = MultiModeController.getKeyByMode(context, KEY_IDP_GRID_NAME);
//调用初始化方法,getDefaultGridName获取是否有默认的GridName配置,即几x几
//Utilities.getPrefs(context).getString(mIdpGridKey, getDefaultGridName(context))最终是在sharepref中获取的值,key="idp_grid_name",value=3_by_3,对应的sharepref.xml见下面
initGrid(context, Utilities.getPrefs(context).getString(mIdpGridKey, getDefaultGridName(context)));
//屏幕配置监听器
mConfigMonitor = new ConfigMonitor(context, APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
//
mOverlayMonitor = new OverlayMonitor(context);
}
initGrid初始化显示选项参数常量
//参数gridName,获取指定gridName的显示选项,该值在device_profile.xml中定义
private String initGrid(Context context, String gridName) {
//获取系统窗口管理器
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay(); //获取默认的Display,里面有屏幕相关的信息
//描述有关显示器的常规信息(如其大小、密度和字体缩放)的结构体
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
Point smallestSize = new Point();
Point largestSize = new Point();
//返回应用程序在正常操作下可能遇到的显示大小范围,只要屏幕大小没有物理变化。这基本上是随着方向的变化而看到的大小,考虑到每次旋转中的任何屏幕装饰。例如,状态栏始终位于屏幕顶部,因此它将降低横向和纵向的高度,此处返回的最小高度将是两者中较小的高度。这旨在让应用程序了解它们在经历设备旋转时将遇到的大小范围,以便通过旋转提供稳定的 UI。此处的尺寸考虑了所有标准系统装饰,这些装饰减小了应用程序实际可用的尺寸:状态栏,导航栏,系统栏等。它没有考虑更多瞬态元素,如 IME 软键盘。
//例如在240*320的屏幕机器上,打印如下smallestSize=Point(240, 219), largestSize=Point(320, 299),也就是高度被状态栏占了一部分21像素,所以为219和299
display.getCurrentSizeRange(smallestSize, largestSize);
//加载并解析device_profiles.xml文件,见下面"二"
ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
// This guarantees that width < height
float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
// 对DiaplayOption进行排序,依据从窗口管理器中获取的最小宽高和每个DisplayOption中的最小宽高进行Math.hypot计算,选出最合适的显示选项
Collections.sort(allOptions, (a, b) ->
Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps),
dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)));
//依据算法纠正DiaplayOption数据,返回最合适的DiaplayOption
DisplayOption interpolatedDisplayOption =
invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions);
//上面已经做过排序,那么最接近的显示选项位于第0个位置
GridOption closestProfile = allOptions.get(0).grid; //获取行列,hotseat设置
numRows = closestProfile.numRows; //行数
numColumns = closestProfile.numColumns; //列数
numHotseatIcons = closestProfile.numHotseatIcons; //hotseat图标个数
defaultLayoutId = closestProfile.defaultLayoutId; //hotseat图标定义文件
demoModeLayoutId = closestProfile.demoModeLayoutId;
numFolderRows = closestProfile.numFolderRows; //图标文件夹行数
numFolderColumns = closestProfile.numFolderColumns; //图标文件夹列数
mExtraAttrs = closestProfile.extraAttrs;
//再次判断closestProfile是否与gridName对应
if (!closestProfile.name.equals(gridName)) {
//如果gridname发生变化,则重新保存到shareprefs
Utilities.getPrefs(context).edit()
.putString(mIdpGridKey, closestProfile.name).apply();
}
if (mMonitor.getSRController() != null) {
mMonitor.getSRController().saveGridNameIntoStorage(context, closestProfile.name);
}
iconSize = interpolatedDisplayOption.iconSize; //图标尺寸
iconShapePath = getIconShapePath(context); //
landscapeIconSize = interpolatedDisplayOption.landscapeIconSize; //横屏图标尺寸
iconBitmapSize = ResourceUtils.pxFromDp(iconSize, dm);
iconTextSize = interpolatedDisplayOption.iconTextSize; //字体大小
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
// 如果有APK包含grid参数设置,在这里可以应用,支持覆盖的参数有: numRows, numColumns, iconSize
applyPartnerDeviceProfileOverrides(context, dm);
Point realSize = new Point();
display.getRealSize(realSize); //获取屏幕实际尺寸,例如还是240*320的屏幕,获取到的是240*320
// 实际大小永远不会改变。小边和大边在任何方向上都将保持不变。
int smallSide = Math.min(realSize.x, realSize.y);
int largeSide = Math.max(realSize.x, realSize.y);
//横屏参数
landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */);
//竖屏参数
portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */);
FolderIconController fic = mMonitor.getFolderIconController();
if (fic != null) {
fic.backupOriginalFolderRowAndColumns(numFolderRows, numFolderColumns);
fic.updateFolderRowAndColumns(this);
}
//我们需要确保壁纸中有足够的额外空间用于预期的视差效果
if (context.getResources().getConfiguration().smallestScreenWidthDp >= 720) {
defaultWallpaperSize = new Point(
(int) (largeSide * wallpaperTravelToScreenWidthRatio(largeSide, smallSide)),
largeSide);
} else {
defaultWallpaperSize = new Point(Math.max(smallSide * 2, largeSide), largeSide);
}
ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
mDisplayOptionName = interpolatedDisplayOption.name;
mGridName = closestProfile.name;
return closestProfile.name;
}
getPredefinedDeviceProfiles
//参数gridName,显示是要获取指定gridName的显示参数
static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context, String gridName) {
ArrayList<DisplayOption> profiles = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if ((type == XmlPullParser.START_TAG)
&& GridOption.TAG_NAME.equals(parser.getName())) {
GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser));
final int displayDepth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > displayDepth)
&& type != XmlPullParser.END_DOCUMENT) {
if ((type == XmlPullParser.START_TAG) && "display-option".equals(
parser.getName())) {
profiles.add(new DisplayOption(
gridOption, context, Xml.asAttributeSet(parser)));
}
}
}
}
} catch (IOException|XmlPullParserException e) {
throw new RuntimeException(e);
}
//根据gridName进行过滤
ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
if (!TextUtils.isEmpty(gridName)) {
for (DisplayOption option : profiles) {
if (gridName.equals(option.grid.name)) {
filteredProfiles.add(option);
}
}
}
if (filteredProfiles.isEmpty()) {
// No grid found, use the default options
for (DisplayOption option : profiles) {
if (option.canBeDefault) {
filteredProfiles.add(option);
}
}
}
if (filteredProfiles.isEmpty()) {
throw new RuntimeException("No display option with canBeDefault=true");
}
return filteredProfiles;
}
invDistWeightedInterpolate
调用这个方法时传进去的参数是当前手机真实的宽和高,以及经过排序后得到的与目标匹配度由高到低的profiles集合。具体的操作在代码中进行了注解,其实,很多手机型号一致的,计算的时候不算多,有些许差别,计算出来的偏差值也不多,所以这个偏差值纠正就分析到这里。
static DisplayOption invDistWeightedInterpolate(float width, float height,
ArrayList<DisplayOption> points) {
float weights = 0;
DisplayOption p = points.get(0);
if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) {
return p;
}
DisplayOption out = new DisplayOption();
out.name = "";
for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
p = points.get(i);
float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
weights += w;
out.add(new DisplayOption().add(p).multiply(w));
if (!TextUtils.isEmpty(out.name)) {
out.name += "&";
}
out.name += "<" + (p.grid != null ? p.grid.name : "not found") + "," + p.name + ">";
}
return out.multiply(1.0f / weights);
}
//获取默认的gridname,在该版本中返回的是空值
private String getDefaultGridName(Context context) {
String defaultGridName = "";
DesktopGridController gridController = mMonitor.getDesktopGridController();
if (gridController != null) {
defaultGridName = gridController.getDefaultGridName();
if (mMonitor.getSRController() != null
&& !gridController.isGridNameValid(defaultGridName)) {
if (LogUtils.DEBUG) {
LogUtils.d(TAG, "The default grid name configured is invalid:" + defaultGridName);
}
defaultGridName = mMonitor.getSRController().getGridNameFromStorage(context);
}
if (LogUtils.DEBUG) {
LogUtils.d(TAG, "getDefaultGridName: defaultGridName = " + defaultGridName);
}
}
return defaultGridName;
}
GridOption和DiaplayOption
private static final class DisplayOption {
private final GridOption grid;
private String name;
private final float minWidthDps;
private final float minHeightDps;
private final boolean canBeDefault;
private float iconSize;
private float landscapeIconSize;
private float iconTextSize;
...
}
public static final class GridOption {
public static final String TAG_NAME = "grid-option";
public final String name;
public final int numRows;
public final int numColumns;
private final int numFolderRows;
private final int numFolderColumns;
private final int numHotseatIcons;
private final int defaultLayoutId;
private final int demoModeLayoutId;
private final SparseArray<TypedValue> extraAttrs;
##com.android.launcher3.prefs.xml
这是Launcher3应用主要的sharedprefs文件,作为分析时的参考
`/data/data/com.android.launcher3/shared_prefs/com.android.launcher3.prefs.xml`
``` <
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="launcher.home_bounce_count" value="4" />
<string name="migration_src_workspace_size">2,2</string>
<boolean name="pref_add_icon_to_home" value="true" />
<int name="migration_src_hotseat_count" value="0" />
<boolean name="pref_add_icon_to_home_initialized" value="true" />
<boolean name="sl_EMPTY_DATABASE_CREATED" value="true" />
<int name="migration_src_density_dpi" value="140" />
<string name="idp_grid_name">3_by_3</string>
</map>
MultiModeController.java
packages/apps/Launcher3/src/com/sprd/ext/multimode/MultiModeController.java
import static com.android.launcher3.LauncherProvider.SCHEMA_VERSION; //28
public static String getKeyByMode(Context context, String originalKey) {
return getKeyByMode(context, originalKey, SCHEMA_VERSION, isSingleLayerMode(context));
}
//参数:key, 数据库版本,是单层还是双层显示
public static String getKeyByMode(
Context context, String originalKey, int dbVersion, boolean isSingleLayerMode) {
initControllerIfNeeded(context);
return MultiModeUtilities.getKeyByMode(originalKey, isSingleLayerMode, dbVersion);
}
public static String getKeyByPreMode(Context context, String originalKey) {
return getKeyByMode(context, originalKey, SCHEMA_VERSION, !isSingleLayerMode(context));
}
public static boolean isSingleLayerMode() {
throwIfControllerNotInited();
return sIsSingleLayerMode; //返回变量sIsSingleLayerMode
}
public static void setSingleLayerMode(Context context, boolean isSingleMode) {
MultiModeUtilities.syncSaveNewModel(context,
isSingleMode ? MultiModeUtilities.SINGLE : MultiModeUtilities.DUAL);
//sIsSingleLayerMode在构造方法中赋值
sIsSingleLayerMode = MultiModeUtilities.isSingleLayerMode(context);
sIsDefaultMode = MultiModeUtilities.isDefaultMode(context);
}
public MultiModeController(Context context, LauncherAppMonitor monitor) {
super(context);
//是否支持动态显示
sIsSupportDynamicChange = MultiModeUtilities.isSupportDynamicHomeStyle(context);
//是否是单层显示
sIsSingleLayerMode = MultiModeUtilities.isSingleLayerMode(context);
sIsDefaultMode = MultiModeUtilities.isDefaultMode(context);
monitor.registerCallback(mAppMonitorCallback);
LogUtils.d(TAG, this);
}
//是否支持动态变化
public static boolean isSupportDynamicChange() {
throwIfControllerNotInited();
return sIsSupportDynamicChange;
}
public static void setSupportDynamicChange(boolean isSupport) {
sIsSupportDynamicChange = isSupport;
}
MultiModeUtilities
packages/apps/Launcher3/src/com/sprd/ext/multimode/MultiModeUtilities.java
static final String SINGLE = "single";
static final String DUAL = "dual";
static final String SL_PREFIX = "sl_";
//判断是否是单层显示
static boolean isSingleLayerMode(Context context) {
return FeatureOption.SPRD_MULTI_MODE_SUPPORT.get()
&& SINGLE.equals(getHomeScreenStylePrefValue(context));
}
//在sharepref中获取显示风格的值
static String getHomeScreenStylePrefValue(Context context) {
if (context == null) {
return DUAL;
}
Resources res = context.getResources();
return Utilities.getPrefs(context)
.getString(LauncherSettingsExtension.PREF_HOME_SCREEN_STYLE_KEY,
res.getString(R.string.default_home_screen_style));
//默认值为R.string.default_home_screen_style
}
// We add SL_PREFIX only single layer mode & support dynamic change.
static String getKeyByMode(String originalKey, boolean isSingleMode, int dbVersion) {
if (TextUtils.isEmpty(originalKey)) {
throw new RuntimeException("getKeyByMode() the original key is empty!");
}
String outKey = originalKey;
//如果支持多模式,再进行判断,如果不支持,则使用双层模式
//我修改了show_home_screen_style_settings的值为false,outKey=idp_grid_name
if (MultiModeController.isSupportDynamicChange()) {
switch (dbVersion) {
case 27:
// Android P db version is 27.
outKey = isSingleMode ? SINGLE + "_" + originalKey : DUAL + "_" + originalKey;
break;
default:
//我的设备是单层显示,所以返回的是sl_idp_grid_name
outKey = isSingleMode ? SL_PREFIX + originalKey : originalKey;
break;
}
}
return outKey;
}
//是否支持动态显示
static boolean isSupportDynamicHomeStyle(Context context) {
return FeatureOption.SPRD_MULTI_MODE_SUPPORT.get()
&& context.getResources().getBoolean(R.bool.show_home_screen_style_settings);
}
LauncherSettingsExtension
packages/apps/Launcher3/src/com/sprd/ext/LauncherSettingsExtension.java
public static final String PREF_HOME_SCREEN_STYLE_KEY = "pref_home_screen_style";
FeatureOption
packages/apps/Launcher3/src/com/sprd/ext/FeatureOption.java
public static final TogglableFlag SPRD_MULTI_MODE_SUPPORT = new TogglableFlag(
"SPRD_MULTI_MODE_SUPPORT", getProp("ro.launcher.multimode"),
"enable user can select user aosp mode or singlelayer mode");
public static final TogglableFlag SPRD_DESKTOP_GRID_SUPPORT = new TogglableFlag(
"SPRD_DESKTOP_GRID_SUPPORT", getProp("ro.launcher.desktopgrid"),
"enable allows customization of the columns and rows on the desktop");
config_ext.xml
packages/apps/Launcher3/res/values/config_ext.xml
//从上面分析可以得出结论
<!--default value of launcher style allapps or singlelayer 是否显示单层/双层设置显示-->
<bool name="show_home_screen_style_settings">false</bool>
<!--The value must be dual or single 默认为单层还是双层-->
<string name="default_home_screen_style" translatable="false">dual</string>
device_profiles.xml
位置:packages/apps/Launcher3/res/xml/device_profiles.xml
<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
<grid-option
launcher:name="2_by_2"
launcher:numRows="2"
launcher:numColumns="2"
launcher:numFolderRows="2"
launcher:numFolderColumns="2"
launcher:numHotseatIcons="3"
launcher:defaultLayoutId="@xml/default_workspace_3x3" >
<display-option
launcher:name="Super Short Stubby"
launcher:minWidthDps="255"
launcher:minHeightDps="300"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
<display-option
launcher:name="Shorter Stubby"
launcher:minWidthDps="255"
launcher:minHeightDps="400"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
</grid-option>
<grid-option
launcher:name="3_by_3"
launcher:numRows="3"
launcher:numColumns="3"
launcher:numFolderRows="2"
launcher:numFolderColumns="3"
launcher:numHotseatIcons="3"
launcher:defaultLayoutId="@xml/default_workspace_3x3" >
<display-option
launcher:name="Super Short Stubby"
launcher:minWidthDps="255"
launcher:minHeightDps="300"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
<display-option
launcher:name="Shorter Stubby"
launcher:minWidthDps="255"
launcher:minHeightDps="400"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
</grid-option>
<grid-option
launcher:name="4_by_4"
launcher:numRows="4"
launcher:numColumns="4"
launcher:numFolderRows="3"
launcher:numFolderColumns="4"
launcher:numHotseatIcons="4"
launcher:defaultLayoutId="@xml/default_workspace_4x4" >
<display-option
launcher:name="Short Stubby"
launcher:minWidthDps="275"
launcher:minHeightDps="420"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
<display-option
launcher:name="Stubby"
launcher:minWidthDps="255"
launcher:minHeightDps="450"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
<display-option
launcher:name="Nexus S"
launcher:minWidthDps="296"
launcher:minHeightDps="491.33"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
<display-option
launcher:name="Nexus 4"
launcher:minWidthDps="359"
launcher:minHeightDps="567"
launcher:iconImageSize="54"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
<display-option
launcher:name="Nexus 5"
launcher:minWidthDps="335"
launcher:minHeightDps="567"
launcher:iconImageSize="54"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
</grid-option>
</profiles>
参考
Launcher 8.0源码
Android10 launcher启动流程
Android10.0系统启动之Launcher(桌面)启动流程
网友评论