安卓系统是市场占用率最高、用户使用率最多的手机系统。大部分安卓手机厂商在AOSP(Android Open Source Project)的基础上进行二次开发,定制符合自家设备使用的安卓系统。本篇文章记录6.0.1版本launcher3(home程序)的加载过程及launcher.java部分函数分析。
安卓6.0.1版本指定launcher3为默认home程序,作为系统第一个app(由ActivityManagerService的systemReady函数通过Intent(intent.addCategory(Intent.CATEGORY_HOME); 这里注册为Intent.CATEGORY_HOME的Activity)方式打开home程序,这里只是简单介绍不进行详细描述),首先我们看下Launcher3文件夹下的AndroidManifest.xml文件:
<activity
android:name="com.android.launcher3.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:theme="@style/Theme"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="landscape"
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"/>
</intent-filter>
</activity>
在这段描述中找到了<category android:name="android.intent.category.HOME" />,Launcher.java将作为主Activity进行启动。
下面开始分析Launcher.java的加载过程:
找到protected void onCreate(Bundle savedInstanceState)函数:
程序的设计者考虑到系统启动过程中可能会不止一次启动launcher3程序(比如launcher3意外退出或别的情况导致),采用Builder模式,用一个内部类去实例化一个对象,避免一个类出现过多构造函数。
首先找到if (DEBUG_STRICT_MODE)这条语句,追踪发现DEBUG_STRICT_MODE默认为false,并没有启动严苛模式,继续往下看
if (mLauncherCallbacks != null) {
mLauncherCallbacks.preOnCreate();
}
如果回调方式已经创建,清空回调信息等待再次创建新的实例(当mLauncherCallbacks的OnCreate执行完成后,后续工作由mLauncherCallbacks进行完成,比如关联所有app程序,所有app的响应事件注册等,后续会专门分析launcher3中比较重要的类),继续往下
super.onCreate(savedInstanceState);
这个是Builder模式构成的重要部分,由父类或超父类(存在超父类的情况下)创建实例,
LauncherAppState.setApplicationContext(getApplicationContext());
LauncherAppState app = LauncherAppState.getInstance();
LauncherAppState类获取程序环境上下文进行注册服务,调用DeviceProfile实例加载桌面部件,其中包括Hotseat,workspace等;调用LauncherProvider实例监听provider等;调用LauncherModel启动回调等;调用LauncherAccessibilityDelegate增加动作等。 参考下图:
图1
继续往下分析:
mDragController = new DragController(this);
创建拖动控制器
mInflater = getLayoutInflater();
布局填充(如果桌面上有空余的部分进行填充)
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
创建icon的动画效果(比如光标停留在app上icon会发生变化、workspace切换页也会有变化)
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
获取app部件管理器实例,当前版本判断标准为API21
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();
在AppWidgetHost.startListening()中,AppWidgetHost通过AppWidgetService把AppWidgetHost,HostId等信息注册到AppWidgetService中,启动服务进行监听
setContentView(R.layout.launcher);
翻译出来的大概意思:从布局资源中设置活动内容。资源将会是
膨胀,将所有*视图添加到活动中。
分析结果:从布局资源中设置活动内容,初始化窗口活动空间
setupViews();
进入到SetupViews函数中
mLauncherView = findViewById(R.id.launcher);
这里调用的是packages/apps/Launcher3/res/下的xml,如果你的桌面设置默认是竖屏将会调用layout-port/launcher.xml,如果桌面默认是横屏调用layout-land/launcher.xml,找到“android:id="@+id/launcher"”修改内部参数即可,这种方法适用所有的xml修改
mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
焦点指示器
mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
拖动层(如果从xml中设置显示=gone,home主界面变为空白)
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
这个可以理解为工作空间,用来放置页视图的cellLayout
mWorkspace.setPageSwitchListener(this);
//绑定页转换监听
mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
页指示器(页下面的点,切换页面时可以看到变化)
这几个布局加载方式与mLauncherView = findViewById(R.id.launcher);是同一个xml文件,修改方式也是相同
// Setup the hotseat
mHotseat = (Hotseat) findViewById(R.id.hotseat);
if (mHotseat != null) {
mHotseat.setOnLongClickListener(this);
}
加载hotseat(抽屉,中间是allapps按钮,点击allapps按钮进入二级界面)
设置hotseat长按键监听,如果不需要可以注释掉,对于allapps按钮进入的二级界面(cellLayout)没有效果
mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
描述面板(默认隐藏)
mWidgetsButton = findViewById(R.id.widget_button);
小部件按钮(在主界面空闲处长按屏幕进入可以看到)
事件函数不再分析,按中文意思理解即可
View wallpaperButton = findViewById(R.id.wallpaper_button);
壁纸按钮(在主界面空闲处长按屏幕进入可以看到)
View settingsButton = findViewById(R.id.settings_button);
设置主题按钮(在主界面空闲处长按屏幕进入可以看到)
if (mSearchDropTargetBar != null) {
mSearchDropTargetBar.setup(this, dragController);
// 取消上方搜索框
//mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
}
继续回到OnCreate()函数:
mDeviceProfile.layout(this);
在DeviceProfile实例中加载workspace,hotseat,pageIndicator,overviewMode,如果你想完成hotseat放置到主屏幕中间位置、隐藏页指示器等,可以通过修改DeviceProflle文件达到效果(后续文章将详细介绍DeviceProfile文件)
Oncreate、SetViews函数修改的位置大概就这些,后面内容可以参考上面的思路继续分析。
如果想隐藏主界面长按键功能,请在public boolean onLongClick(View v) 中注释掉所有内容,增加return true;即可,大部分事件函数可以用中文意思去理解。