省略一萬字前奏
如果大家沒有源碼购裙,
不介意的話廓推,可以參考https://github.com/Tic-pf/Launcher3-N-Folder 開發(fā)中
主入口Launcher
LauncherAppState
Launcher的onCreate里比較長劳淆,我們依次取代碼片段來分析聊倔,看oncrate方法的這一段,初始化LauncherAppState
public void onCreate() {
...
LauncherAppState app = LauncherAppState.getInstance();
...
}
LauncherAppState是保存一些全局的匀们,核心的對(duì)象。主要有整個(gè)Launcher的工作臺(tái)workspace敢订,Launcher的控制器LauncherModel,應(yīng)用圖標(biāo)的緩存機(jī)制IconCache罢吃,設(shè)備的配置信息InvariantDeviceProfile等楚午。
構(gòu)造方法,首先初始化內(nèi)存的追蹤器TestingUtils尿招,記錄我們app的內(nèi)存信息醒叁,這個(gè)工具在我們開發(fā)其他app分析內(nèi)存信息時(shí)也是很有用的司浪。
private LauncherAppState() {
...
if (TestingUtils.MEMORY_DUMP_ENABLED) {
TestingUtils.startTrackingMemory(sContext);
}
mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
mModel = new LauncherModel(this, mIconCache, mAppFilter);
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
// Register intent receivers
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
// For handling managed profiles
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE);
sContext.registerReceiver(mModel, filter);
...
}
LauncherAppsCompat里添加了一個(gè)應(yīng)用變化的回調(diào),由LauncherModel實(shí)現(xiàn)接口把沼,及時(shí)的響應(yīng)數(shù)據(jù)變化啊易。LauncherAppsCompat是獲取所有應(yīng)用,監(jiān)聽?wèi)?yīng)用變化的一個(gè)抽象饮睬,Android 5.0前后的版本獲取方式不一樣了租谈,這就是Launcher良好適配性的體現(xiàn)了。
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
Android 5.0以前的監(jiān)聽
private void registerForPackageIntents() {
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mContext.registerReceiver(mPackageMonitor, filter);
filter = new IntentFilter();
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mPackageMonitor, filter);
}
Android5.0后的監(jiān)聽
import android.content.pm.LauncherApps;
protected LauncherApps mLauncherApps;
public void addOnAppsChangedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
WrappedCallback wrappedCallback = new WrappedCallback(callback);
synchronized (mCallbacks) {
mCallbacks.put(callback, wrappedCallback);
}
mLauncherApps.registerCallback(wrappedCallback);
}
除了TestingUtils捆愁,應(yīng)用變化監(jiān)聽外割去,初始化兩個(gè)核心對(duì)象IconCache,LauncherModel昼丑。LauncherModel添加了設(shè)備變更呻逆,用戶信息變更的廣播,這是因?yàn)楫?dāng)用戶修改設(shè)備信息如語言菩帝,區(qū)域咖城,用戶信息等,LauncherModel會(huì)刷新數(shù)據(jù)呼奢,改變圖標(biāo)宜雀,標(biāo)題等信息。
LauncherAppState的初始化到這里基本上就完成了握础。
DeviceProfile辐董,InvariantDeviceProfile,Launcher的配置
我們繼續(xù)看禀综,下一步
LauncherAppState app = LauncherAppState.getInstance();
// Load configuration-specific DeviceProfile
mDeviceProfile = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE ?
app.getInvariantDeviceProfile().landscapeProfile
: app.getInvariantDeviceProfile().portraitProfile;
通過Configuration獲取橫屏配置landscapeProfile 或者portraitProfile豎屏配置
DeviceProfile
DeviceProfile的數(shù)據(jù)都是來自InvariantDeviceProfile简烘,封裝一些工具方法,我們直接看重點(diǎn)InvariantDeviceProfile
InvariantDeviceProfile
InvariantDeviceProfile的初始化是在LauncherAppState構(gòu)造里new出來的定枷,調(diào)用的是
InvariantDeviceProfile(Context context) {
...
numRows = closestProfile.numRows;
numColumns = closestProfile.numColumns;
numHotseatIcons = closestProfile.numHotseatIcons;
hotseatAllAppsRank = (int) (numHotseatIcons / 2);
defaultLayoutId = closestProfile.defaultLayoutId;
numFolderRows = closestProfile.numFolderRows;
numFolderColumns = closestProfile.numFolderColumns;
minAllAppsPredictionColumns = closestProfile.minAllAppsPredictionColumns;
iconSize = interpolatedDeviceProfileOut.iconSize;
iconBitmapSize = Utilities.pxFromDp(iconSize, dm);
iconTextSize = interpolatedDeviceProfileOut.iconTextSize;
hotseatIconSize = interpolatedDeviceProfileOut.hotseatIconSize;
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
// If the partner customization apk contains any grid overrides, apply them
// Supported overrides: numRows, numColumns, iconSize
applyPartnerDeviceProfileOverrides(context, dm);
Point realSize = new Point();
display.getRealSize(realSize);
// The real size never changes. smallSide and largeSide will remain the
// same in any orientation.
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 */);
portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
smallSide, largeSide, false /* isLandscape */);
}
可以看到夸研,通過closestProfile和interpolatedDeviceProfileOut拿到了一系列配置項(xiàng),如桌面的行依鸥,列亥至,Hotseat(桌面底部固定的應(yīng)用欄)的個(gè)數(shù),Hotseat所有應(yīng)用的位置贱迟,布局id姐扮,文件夾的行列,圖標(biāo)的大小等等衣吠。之后再將這些信息new出我們的DeviceProfile對(duì)象茶敏。
那問題來了,closestProfile和interpolatedDeviceProfileOut是什么缚俏?惊搏?怎么計(jì)算出來的贮乳?
...
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
Point smallestSize = new Point();
Point largestSize = new Point();
display.getCurrentSizeRange(smallestSize, largestSize);
// This guarantees that width < height
minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
ArrayList<InvariantDeviceProfile> closestProfiles =
findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles());
InvariantDeviceProfile interpolatedDeviceProfileOut =
invDistWeightedInterpolate(minWidthDps, minHeightDps, closestProfiles);
...
可以發(fā)現(xiàn),通過設(shè)備的寬高等信息恬惯,從各種分辨率的DeviceProfiles列表中找到最合適的配置信息向拆,查找的方法如下,根據(jù)設(shè)備的寬高跟列表里的的最小寬高差值的平方根從小到大排序酪耳,第一個(gè)就是我們期望的配置
/**
* Returns the closest device profiles ordered by closeness to the specified width and height
*/
// Package private visibility for testing.
ArrayList<InvariantDeviceProfile> findClosestDeviceProfiles(
final float width, final float height, ArrayList<InvariantDeviceProfile> points) {
// Sort the profiles by their closeness to the dimensions
ArrayList<InvariantDeviceProfile> pointsByNearness = points;
Collections.sort(pointsByNearness, new Comparator<InvariantDeviceProfile>() {
public int compare(InvariantDeviceProfile a, InvariantDeviceProfile b) {
return Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
dist(width, height, b.minWidthDps, b.minHeightDps));
}
});
return pointsByNearness;
}
@Thunk float dist(float x0, float y0, float x1, float y1) {
return (float) Math.hypot(x1 - x0, y1 - y0);
}
配置好的InvariantDeviceProfile列表信息如下浓恳,構(gòu)造參數(shù)依次是,
配置名稱碗暗,寬高颈将,行數(shù),列數(shù)言疗,文件夾行數(shù)列數(shù)晴圾,圖標(biāo)大小,圖標(biāo)文本大小
hotseat配置資源文件噪奄,hotseat圖標(biāo)大小死姚,默認(rèn)頁的資源文件
故,當(dāng)我們有新機(jī)型沒有適配梗醇,就可以在這里修改或新增配置
ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles() {
ArrayList<InvariantDeviceProfile> predefinedDeviceProfiles = new ArrayList<>();
// width, height, #rows, #columns, #folder rows, #folder columns,
// iconSize, iconTextSize, #hotseat, #hotseatIconSize, defaultLayoutId.
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby",
255, 300, 2, 3, 2, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby",
255, 400, 3, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby",
275, 420, 3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Stubby",
255, 450, 3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S",
296, 491.33f, 4, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4",
359, 567, 4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5",
335, 567, 5, 4, 5, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_5x4_no_all_apps));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Large Phone",
406, 694, 5, 5, 4, 4, 4, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5));
// The tablet profile is odd in that the landscape orientation
// also includes the nav bar on the side
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 7",
575, 904, 5, 6, 4, 5, 4, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6));
// Larger tablet profiles always have system bars on the top & bottom
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10",
727, 1207, 5, 6, 4, 5, 4, 76, 14.4f, 7, 76, R.xml.default_workspace_5x6));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet",
1527, 2527, 7, 7, 6, 6, 4, 100, 20, 7, 72, R.xml.default_workspace_5x6));
return predefinedDeviceProfiles;
}
Launcher的配置初始化到這里基本上就完成了。
還有一些其他的對(duì)象的初始化撒蟀,包括workspace狀態(tài)變化的動(dòng)畫加載控制LauncherStateTransitionAnimation, 應(yīng)用組件的管理器AppWidgetManagerCompat, 處理組件長按事件的ViewLauncherAppWidgetHost叙谨,因?yàn)樵贚auncher的初始化流程里不是特別需要,故后文有機(jī)會(huì)再做介紹保屯。
LauncherModel加載應(yīng)用信息
整個(gè)流程里比較復(fù)雜的就是LauncherModel加載應(yīng)用了, 在onCreate里
mModel = app.setLauncher(this);
LauncherAppState里調(diào)用了LauncherModel的初始化方法 initialize,參數(shù)是Launcher
LauncherModel setLauncher(Launcher launcher) {
getLauncherProvider().setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher);
mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
new LauncherAccessibilityDelegate(launcher) : null;
return mModel;
}
LauncherModel的initialize參數(shù)是LauncherModel.Callbacks手负,Launcher里實(shí)現(xiàn)了LauncherModel.Callbacks的一系列接口用于綁定獲取到的應(yīng)用信息,文件夾信息姑尺,屏幕信息等等
public void initialize(Callbacks callbacks) {
synchronized (mLock) {
// Disconnect any of the callbacks and drawables associated with ItemInfos on the
// workspace to prevent leaking Launcher activities on orientation change.
unbindItemInfosAndClearQueuedBindRunnables();
mCallbacks = new WeakReference<Callbacks>(callbacks);
}
}
在加載數(shù)據(jù)之前先清除正在運(yùn)行的線程DeferredBindRunnables竟终,清除DeferredHandler里等待執(zhí)行的任務(wù),并且將所有ItemInfo unbind
/** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
void unbindWorkspaceItemsOnMainThread() {
// Ensure that we don't use the same workspace items data structure on the main thread
// by making a copy of workspace items first.
final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
synchronized (sBgLock) {
tmpItems.addAll(sBgWorkspaceItems);
tmpItems.addAll(sBgAppWidgets);
}
Runnable r = new Runnable() {
@Override
public void run() {
for (ItemInfo item : tmpItems) {
item.unbind();
}
}
};
runOnMainThread(r);
}
接著我們才看到真正開始加載數(shù)據(jù)了切蟋,onCreate里的LauncherModel調(diào)用startLoader统捶,創(chuàng)建線程加載數(shù)據(jù)
...
if (!mRestoring) {
if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
// If the user leaves launcher, then we should just load items asynchronously when
// they return.
mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
} else {
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
mModel.startLoader(mWorkspace.getRestorePage());
}
}
...
當(dāng)?shù)谝淮未蜷_時(shí),會(huì)調(diào)用 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE)柄粹,停止舊的Loader任務(wù)喘鸟,stopLoaderLocked,然后new出一個(gè)LoaderTask(mApp.getContext(), loadFlags) Runnable驻右,通過Handler sworker post出去什黑,開始加載任務(wù)
public void startLoader(int synchronousBindPage) {
startLoader(synchronousBindPage, LOADER_FLAG_NONE);
}
public void startLoader(int synchronousBindPage, int loadFlags) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue();
synchronized (mLock) {
// Clear any deferred bind-runnables from the synchronized load process
// We must do this before any loading/binding is scheduled below.
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.clear();
}
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
// If there is already one running, tell it to stop.
stopLoaderLocked();
mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
&& mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
} else {
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
}
}
}
}
開始加載應(yīng)用信息的任務(wù)后,由于后續(xù)的篇幅比較長堪夭,請(qǐng)看下一篇文章的詳細(xì)介紹愕把。