为何会分析小米系统?
我算是个米粉吧,从10年开始就用HTC G7刷MIUI系统。从此就一直使用MIUI,对于小米系统MIUI,我还是给予很高的评价。好了,这里不是广告。
继续回答问题:第一,感觉MIUI系统从更新V5版本以后没有再大的更新,似我这种对新鲜事物和技术的渴求,让我挺失望。第二,MIUI系统号称是开源了PatchROM, 实质这是开放了一些APK。对于我,远远不能满足。虽说,在学习Android的几年过程中也对MIUI有过分析,但是毕竟公司的事情也是非常多,对MIUI系统的分析总是不够完整和系统。
近期,努力的抽出时间来对MIUI做一个系统的分析。
废话不多说,开始我们的MIUI系统之旅。
话在开头:本博客仅对MIUI系统做学习交流使用,不存在商业行为。如若本博客中的文字及图片对MIUI系统有侵权行为还请及时通知与我,我会根据相关规定做出处理。谢谢大家的支持!
简介:
1. MIUI系统分析的内容基于MIUI PatchROM- ICS的版本,不能完全适应于最新的V5系统;
2. 分析过程中会针对Android原生的源代码进行相互的比较,分析出各自的优势及其缺点;
第一章: MIUI系统之Launcher分析
首先,读者最好对Launcher的源码有过简单的阅读和了解。
分析的源码会在http://github.com/mimijava/MKHome逐步上传。
第一部分:Launcher APP 组成分析
一、从Android四大组件开始介绍,我们都知道Android四大组件: Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器;
1. Activity:MIUI系统有哪些主要的Activity呢?
<!-- 主界面 --> <activity android:name="cn.minking.launcher.Launcher" android:clearTaskOnLaunch="true" android:stateNotNeeded="true" android:theme="@style/Theme" android:launchMode="singleTask" android:screenOrientation="sensorPortrait" android:configChanges="mcc|mnc|keyboard|keyboardHidden|navigation|orientation|uiMode|screenSize"> <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" /> </intent-filter> </activity> <!-- 设置界面 --> <activity android:label="@string/preferences_title" android:name="cn.minking.launcher.LauncherPreferenceActivity" > </activity>
2. Service 服务,MIUI Launcher中是有一个服务的,但是不是特别重要,在此不做介绍。
3.BroadcastReceiver广播接收器:
<!-- 安装APP --> <receiver android:name="cn.minking.launcher.InstallShortcutReceiver" android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"> <intent-filter > <action android:name="com.android.launcher.action.INSTALL_SHORTCUT"/> </intent-filter> </receiver> <!-- 删除APP --> <receiver android:name="cn.minking.launcher.UninstallShortcutReceiver" android:permission="com.android.launcher.permission.UNINSTALL_SHORTCUT"> <intent-filter > <action android:name="com.android.launcher.action.UNINSTALL_SHORTCUT"/> </intent-filter> </receiver> <!-- 安装Widget --> <receiver android:name="cn.minking.launcher.InstallWidgetReceiver" android:permission="com.android.launcher.permission.INSTALL_WIDGET"> <intent-filter> <action android:name="com.android.launcher.action.INSTALL_WIDGET" /> </intent-filter> </receiver> <!-- 存储信息 --> <receiver android:name="cn.minking.launcher.RestoreFinishedReceiver"> <intent-filter> <action android:name="android.intent.action.RESTORE_FINISH" /> </intent-filter> </receiver>
4. Content Provider内容提供者:
<!-- 数据存储 --> <provider android:name="cn.minking.launcher.LauncherProvider" android:readPermission="com.android.launcher.permission.READ_SETTINGS" android:writePermission="com.android.launcher.permission.WRITE_SETTINGS" android:authorities="cn.minking.launcher.settings"/>
二、Launcher Activity的布局分析;
首先看onCreate函数, 下面这个部分是根据MIUI系统及Android原生代码写出的几个重要方法入口及变量的初始化,
/******* 数据 ********/
private LauncherModel mModel;
private IconCache mIconCache;
/******* 桌面内容 ********/
private DragLayer mDragLayer;
private DragController mDragController;
private Workspace mWorkspace;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
Window localWindow = getWindow();
localWindow.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
// 是否全屏显示,不带状态栏
//localWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
//WindowManager.LayoutParams.FLAG_FULLSCREEN);
//mIsHardwareAccelerated = ((Window)(localWindow)).getWindowManager().isHardwareAccelerated();
LauncherApplication launcherApplication = (LauncherApplication)getApplication();
// MODEL与 LAUNCHER APP 绑定
mModel = launcherApplication.setLauncher(this);
mIconCache = launcherApplication.getIconCache();
// 分配拖动控制器
mDragController = new DragController(this);
registerContentObservers();
// 应用消息
mApplicationsMessage = new ApplicationsMessage(this);
setWallpaperDimension();
// 设置Launcher布局
setContentView(R.layout.launcher);
setupViews();
if (!mRestoring) {
/// M: 如果本地信息变更了,设置为重新装载所有信息
if (sLocaleChanged) {
mModel.resetLoadedState(true, true);
sLocaleChanged = false;
}
mIsLoadingWorkspace = true;
if (sPausedFromUserAction) {
// 如果用户离开了launcher, 只需要在回到launcher的时候异步的完成装载
mModel.startLoader(getApplicationContext(), true);
} else {
// 如果用户旋转屏幕或更改配置,则同步装载
mModel.startLoader(getApplicationContext(), true);
}
}
}
此处关注一个桌面最重要的三处,mModel, startLoader、 setupViews();、mDragController, 第一个是桌面上的数据,第二个是桌面的布局,第三个是对桌面的操作及控制。虽然这样说是很简单,一个桌面不外乎这几点,但是慢慢分析才会知道其中的复杂,三者之间的互相联系及依赖的关系往往让阅读代码的人头疼。大道至简,我们就慢慢的分析,一丝一丝的剥离出来。
(2). Launcher的布局,MIUI的布局由Launcher.xml来做为整体的布局,其文件在res/layout/launcher.xml中,可以在我的Github中找到。其组成如下:
<?xml version="1.0" encoding="utf-8"?> <cn.minking.launcher.DragLayer android:id="@id/drag_layer" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 整个桌面的显示,包括背景,不包括小图标状态栏 --> <FrameLayout android:id="@id/screen" android:layout_width="fill_parent" android:layout_height="fill_parent"> <!-- 桌面背景 --> <cn.minking.launcher.Background android:id="@id/drag_layer_background" android:layout_width="fill_parent" android:layout_height="fill_parent" /> <!-- 所有内容显示区 --> <cn.minking.launcher.Workspace android:id="@id/workspace" android:paddingTop="@dimen/status_bar_height" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginBottom="@dimen/workspace_margin_bottom" /> <!-- 缩略图显示 --> <cn.minking.launcher.WorkspaceThumbnailView android:id="@id/workspace_preview" android:paddingTop="@dimen/status_bar_height" android:visibility="invisible" android:layout_width="fill_parent" android:layout_height="fill_parent" /> <!-- 桌面HOTSEAT区 --> <cn.minking.launcher.HotSeats android:id="@id/hot_seats" android:gravity="center" android:layout_gravity="bottom" android:background="@drawable/hotseat_background" android:paddingLeft="@dimen/hotseats_padding_side" android:paddingRight="@dimen/hotseats_padding_side" android:paddingTop="@dimen/hotseats_padding_top" android:paddingBottom="@dimen/hotseats_padding_bottom" android:animationCache="false" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <!-- 删除区 --> <cn.minking.launcher.DeleteZone android:id="@id/delete_zone" android:layout_gravity="top" android:background="@null" android:orientation="horizontal" android:visibility="invisible" android:layout_width="fill_parent" android:layout_height="wrap_content" > <!-- 动画显示删除图标 --> <ImageView android:id="@id/trash" android:background="@drawable/delete_zone_selector" android:visibility="invisible" android:layout_width="fill_parent" android:layout_height="wrap_content" android:src="@drawable/delete_zone_01" android:scaleType="center" /> <!-- 删除提示 --> <TextView android:id="@id/editing_tips" android:layout_gravity="top|center" android:paddingTop="13.0dip" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/editing_tips" style="@style/WorkspaceIconTitle.notification" /> </cn.minking.launcher.DeleteZone> </FrameLayout> <include layout="@layout/folder_cling" /> <!-- 小图标状态栏显示区域 --> <FrameLayout android:paddingTop="@dimen/status_bar_height" android:layout_width="1.0px" android:layout_height="fill_parent"> <FrameLayout android:id="@id/default_position" android:layout_width="1.0px" android:layout_height="fill_parent" /> </FrameLayout> </cn.minking.launcher.DragLayer>
1. DragLayer 实际就是一个FrameLayout;
2. Workspace及WorkspaceThumbnailView实际为一个ViewGroup;
3. Hotseat及DeleteZone
以上这个只是桌面的一个整体布局,桌面上比如图标,WIDGET,文件夹等都有各自的布局格式,这些最后都会作为 Workspace的子控件放入其中,这部分等以后慢慢再道来。
(3). setupViews();
private void setupViews() {
// Drag 控制器
DragController dragController = mDragController;
// Drag 层
mDragLayer = (DragLayer)findViewById(R.id.drag_layer);
mDragLayerBackground = (Background)findViewById(R.id.drag_layer_background);
mDragLayer.setDragController(dragController);
mDragLayer.setLauncher(this);
mScreen = findViewById(R.id.screen);
// 桌面布局
mWorkspace = (Workspace)mDragLayer.findViewById(R.id.workspace);
Workspace workspace = mWorkspace;
mWorkspacePreview = (WorkspaceThumbnailView)mDragLayer.findViewById(R.id.workspace_preview);
workspace.setHapticFeedbackEnabled(false);
workspace.setOnLongClickListener(this);
workspace.setDragController(dragController);
workspace.setLauncher(this);
workspace.setThumbnailView(mWorkspacePreview);
// 删除区
mDeleteZone = (DeleteZone)mDragLayer.findViewById(R.id.delete_zone);
mDeleteZone.setLauncher(this);
mDeleteZone.setDragController(dragController);
// HOTSEAT
mHotSeats = (HotSeats)mDragLayer.findViewById(R.id.hot_seats);
mHotSeats.setLauncher(this);
mHotSeats.setDragController(dragController);
mFolderCling = (FolderCling)findViewById(R.id.folder_cling);
mFolderCling.setLauncher(this);
mFolderCling.setDragController(dragController);
// 设置拖动控制
dragController.setDragScoller(workspace);
dragController.addDragListener(mDeleteZone);
dragController.setScrollView(mDragLayer);
dragController.setMoveTarget(workspace);
dragController.addDropTarget(mHotSeats);
dragController.addDropTarget(workspace);
dragController.addDropTarget(mDeleteZone);
setupAnimations();
mPositionSnap = mDragLayer.findViewById(R.id.default_position);
}
(图1)Launcher布局概略图
上图中其中Workspace为ViewGroup布局,APP、FLODER、WIDGET/GADGET为其子视图,都有各自的视图结构,此处不做介绍,等介绍到时再做分析;
三、Launcher Provider
@Override
public void onCreate(SQLiteDatabase db) {
mMaxId = 1L;
// 创建 favorites 数据库表
db.execSQL("DROP TABLE IF EXISTS favorites");
db.execSQL("CREATE TABLE favorites("
+ "_id INTEGER PRIMARY KEY,"
+ "title TEXT,"
+ "intent TEXT,"
+ "container INTEGER,"
+ "screen INTEGER,"
+ "cellX INTEGER,"
+ "cellY INTEGER,"
+ "spanX INTEGER,"
+ "spanY INTEGER,"
+ "itemType INTEGER,"
+ "appWidgetId INTEGER NOT NULL DEFAULT -1,"
+ "isShortcut INTEGER,"
+ "iconType INTEGER,"
+ "iconPackage TEXT,"
+ "iconResource TEXT,"
+ "icon BLOB,"
+ "uri TEXT,"
+ "displayMode INTEGER,"
+ "launchCount INTEGER NOT NULL DEFAULT 1,"
+ "sortMode INTEGER,"
+ "itemFlags INTEGER NOT NULL DEFAULT 0"
+ ");");
if (mAppWidgetHost != null) {
mAppWidgetHost.deleteHost();
sendAppWidgetResetNotify();
}
// 读取XML中的预设配置
loadFavorites(db);
// 创建WORKSPACE中的Screen表
createScreensTable(db);
}
为了后续读取的桌面布局及手机中的APP、WIDGET摆放位置,首先需要建立一个SQL数据表来保存这些数据,在Provider中创建一个Favorites表。其数据结构如上所示。
// 创建 screens 数据库表
db.execSQL("DROP TABLE IF EXISTS screens");
db.execSQL("CREATE TABLE screens("
+ "_id INTEGER PRIMARY KEY,"
+ "title TEXT,"
+ "screenOrder INTEGER NOT NULL DEFAULT -1);");
PS: 由于以前没有系统完整的写过博客,使用起来比较生疏,第一章节博客布局和格式问题花了点时间在弄好。觉得我分析的不好或者进度太慢的同学请到我的Github下载代码自己研究研究。地址为: http://github.com/mimijava/MKHome/
我的微博: http://weibo.com/ison7 欢迎关注!