[start-activity]Launcher3
目录
概述
在launcher中点击应用来启动应用。
launcher发一个intent来启动应用。
1. 应用图标
通过android studio的tools -> layout inspector工具可以看到,桌面上的app图标使用的是DoubleShadowBubbleTextView类,第二级菜单栏的app图标使用的是BubbleTextView类
桌面图标属于workspace中
第二级菜单栏属于AllAppsRecyclerView中,是滚动栏
源码解析
应用层
1. launcher3
1.1 launcher启动应用的调用栈
execStartActivity:1682, Instrumentation (android.app)
startActivityForResult:5194, Activity (android.app)
startActivityForResult:1499, Launcher (com.android.launcher3)
startActivity:5519, Activity (android.app)
startActivitySafely:161, BaseDraggingActivity (com.android.launcher3)
startActivitySafely:1818, Launcher (com.android.launcher3)
startAppShortcutOrInfoActivity:270, ItemClickHandler (com.android.launcher3.touch)
onClick:95, ItemClickHandler (com.android.launcher3.touch)
lambda$getInstance$0:76, ItemClickHandler (com.android.launcher3.touch)
onClick:-1, -$$Lambda$ItemClickHandler$_rHy-J5yxnvGlFKWSh6CdrSf-lA (com.android.launcher3.touch)
performClick:7259, View (android.view)
performClickInternal:7236, View (android.view)
access$3600:801, View (android.view)
run:27881, View$PerformClick (android.view)
handleCallback:883, Handler (android.os)
dispatchMessage:100, Handler (android.os)
loop:214, Looper (android.os)
main:7356, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:492, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:930, ZygoteInit (com.android.internal.os)
1.2 应用图标onClick事件注册-setOnClickListener-都是ItemClickHandler
// AllAppsGridAdapter.java文件
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_ICON:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.all_apps_icon, parent, false);
icon.setOnClickListener(ItemClickHandler.INSTANCE);
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mIconFocusListener);
// Ensure the all apps icon height matches the workspace icons in portrait mode.
icon.getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
return new ViewHolder(icon);
case VIEW_TYPE_EMPTY_SEARCH:
return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
parent, false));
case VIEW_TYPE_SEARCH_MARKET:
View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
parent, false);
searchMarketView.setOnClickListener(v -> mLauncher.startActivitySafely(
v, mMarketSearchIntent, null, AppLaunchTracker.CONTAINER_SEARCH));
return new ViewHolder(searchMarketView);
case VIEW_TYPE_ALL_APPS_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_divider, parent, false));
case VIEW_TYPE_WORK_TAB_FOOTER:
View footer = mLayoutInflater.inflate(R.layout.work_tab_footer, parent, false);
return new ViewHolder(footer);
default:
throw new RuntimeException("Unexpected view type");
}
}
// Launcher.java文件,创建快捷方式
public View createShortcut(ViewGroup parent, WorkspaceItemInfo info) {
BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.app_icon, parent, false);
favorite.applyFromWorkspaceItem(info);
favorite.setOnClickListener(ItemClickHandler.INSTANCE);
favorite.setOnFocusChangeListener(mFocusHandler);
return favorite;
}
1.3 startActivitySafely-启动应用
public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
@Nullable String sourceContainer) {
if (!hasBeenResumed()) {
// Workaround an issue where the WM launch animation is clobbered when finishing the
// recents animation into launcher. Defer launching the activity until Launcher is
// next resumed.
addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
UiFactory.clearSwipeSharedState(true /* finishAnimation */);
return true;
}
// 走这里
boolean success = super.startActivitySafely(v, intent, item, sourceContainer);
if (success && v instanceof BubbleTextView) {
// This is set to the view that launched the activity that navigated the user away
// from launcher. Since there is no callback for when the activity has finished
// launching, enable the press state and keep this reference to reset the press
// state when we return to launcher.
// 重置按下状态?
BubbleTextView btv = (BubbleTextView) v;
btv.setStayPressed(true);
addOnResumeCallback(btv);
}
return success;
}
1.4 startActivityForResult-启动应用
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
}
if (requestCode == -1 // requestCode == -1,走这里
|| !UiFactory.startActivityForResult(this, intent, requestCode, options)) {
super.startActivityForResult(intent, requestCode, options);
}
}
2. ItemClickHandler
2.1 INSTANCE-单例模式-OnClickListener接口只创建一个对象
public static final OnClickListener INSTANCE = getInstance(null);
// 实现View的OnClickListener接口
public static final OnClickListener getInstance(String sourceContainer) {
// 调用ItemClickHandler的onClick函数
return v -> onClick(v, sourceContainer);
}
2.2 onClick-点击应用
// com.android.launcher3.views.DoubleShadowBubbleTextView{e30f73 VFED..CL. ...P.... 330,378-440,504 #6} , null
private static void onClick(View v, String sourceContainer) {
// Make sure that rogue clicks don't get through while allapps is launching, or after the
// view has detached (it's possible for this to happen if the view is removed mid touch).
if (v.getWindowToken() == null) return;
Launcher launcher = Launcher.getLauncher(v.getContext());
if (!launcher.getWorkspace().isFinishedSwitchingState()) return;
// WorkspaceItemInfo(id=6 type=APP container=desktop screen=1 cell(3,3) span(1,1) minSpan(1,1) rank=0 user=UserHandle{0} title=Gallery)
Object tag = v.getTag();
if (tag instanceof WorkspaceItemInfo) { // 桌面快捷方式,走这里
onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher, sourceContainer);
} else if (tag instanceof FolderInfo) {
if (v instanceof FolderIcon) {
onClickFolderIcon(v);
}
} else if (tag instanceof AppInfo) { // 第二级菜单的全部应用,走这里
startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher,
sourceContainer == null ? CONTAINER_ALL_APPS: sourceContainer);
} else if (tag instanceof LauncherAppWidgetInfo) {
if (v instanceof PendingAppWidgetHostView) {
onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
}
}
}
2.3 onClickAppShortcut-点击桌面的应用快捷方式
public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher,
@Nullable String sourceContainer) {
if (shortcut.isDisabled()) {
final int disabledFlags = shortcut.runtimeStatusFlags
& WorkspaceItemInfo.FLAG_DISABLED_MASK;
if ((disabledFlags &
~FLAG_DISABLED_SUSPENDED &
~FLAG_DISABLED_QUIET_USER) == 0) {
// If the app is only disabled because of the above flags, launch activity anyway.
// Framework will tell the user why the app is suspended.
} else {
if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
// Use a message specific to this shortcut, if it has one.
Toast.makeText(launcher, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
return;
}
// Otherwise just use a generic error message.
int error = R.string.activity_not_available;
if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
error = R.string.safemode_shortcut_error;
} else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
(shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
error = R.string.shortcut_not_available;
}
Toast.makeText(launcher, error, Toast.LENGTH_SHORT).show();
return;
}
}
// Check for abandoned promise
if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
String packageName = shortcut.intent.getComponent() != null ?
shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
if (!TextUtils.isEmpty(packageName)) {
onClickPendingAppItem(v, launcher, packageName,
shortcut.hasStatusFlag(WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE));
return;
}
}
// Start activities
// 启动桌面快捷方式的应用
startAppShortcutOrInfoActivity(v, shortcut, launcher, sourceContainer);
}
2.4 startAppShortcutOrInfoActivity
private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher,
@Nullable String sourceContainer) {
Intent intent;
if (item instanceof PromiseAppInfo) {
PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
intent = promiseAppInfo.getMarketIntent(launcher);
} else {
// Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 pkg=com.android.gallery3d cmp=com.android.gallery3d/.app.GalleryActivity bnds=[350,426][460,552] }
intent = item.getIntent();
}
if (intent == null) {
throw new IllegalArgumentException("Input must have a valid intent");
}
if (item instanceof WorkspaceItemInfo) {
WorkspaceItemInfo si = (WorkspaceItemInfo) item;
if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)
&& Intent.ACTION_VIEW.equals(intent.getAction())) {
// make a copy of the intent that has the package set to null
// we do this because the platform sometimes disables instant
// apps temporarily (triggered by the user) and fallbacks to the
// web ui. This only works though if the package isn't set
intent = new Intent(intent);
intent.setPackage(null);
}
}
if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation()) {
// Preload the icon to reduce latency b/w swapping the floating view with the original.
FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
}
launcher.startActivitySafely(v, intent, item, sourceContainer);
}
3. BaseDraggingActivity
3.1 startActivitySafely-启动应用
public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item,
@Nullable String sourceContainer) {
if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
return false;
}
Bundle optsBundle = (v != null) ? getActivityLaunchOptionsAsBundle(v) : null;
UserHandle user = item == null ? null : item.user;
// Prepare intent
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (v != null) {
intent.setSourceBounds(getViewBounds(v));
}
try { // false
boolean isShortcut = (item instanceof WorkspaceItemInfo)
&& (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
|| item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
&& !((WorkspaceItemInfo) item).isPromise();
if (isShortcut) {
// Shortcuts need some special checks due to legacy reasons.
startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
// user = 0
} else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity
// Bundle[{android:activity.remoteAnimationAdapter=android.view.RemoteAnimationAdapter@f0315e0, android:activity.animType=13}]
startActivity(intent, optsBundle);
AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
Process.myUserHandle(), sourceContainer);
} else {
LauncherAppsCompat.getInstance(this).startActivityForProfile(
intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user,
sourceContainer);
}
getUserEventDispatcher().logAppLaunch(v, intent);
getStatsLogManager().logAppLaunch(v, intent);
return true;
} catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
}
return false;
}
应用接口层
4. Activity
frameworks/base/core/java/android/app/Activity.java
4.1 startActivity-启动应用
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) { // 走这里
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
4.2 startActivityForResult-启动应用
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) { // mParent == null走这里
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar = // 这里启动应用
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
5. Instrumentation
frameworks/base/core/java/android/app/Instrumentation.java
5.1 execStartActivity
// who: Launcher;contextThread:activitythread;token:BinderProxy
// target:Launcher;intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 pkg=com.android.gallery3d cmp=com.android.gallery3d/.app.GalleryActivity bnds=[350,426][460,552] }
// requestCode:-1;options:Bundle[{android:activity.remoteAnimationAdapter=android.view.RemoteAnimationAdapter@f0315e0, android:activity.animType=13}]
@UnsupportedAppUsage
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode:-1, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent);
}
if (result != null) {
am.mHits++;
return result;
} else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
// 这里调用activitytaskmanager来启动应用
int result = ActivityTaskManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
补充
1. java
1.1 lambda表达式
// (argument) -> {expression body}
// argument代表匿名接口setOnClickListener的onClick函数的参数,View view
// 表达式主体是您要执行的代码,其实就是函数主体。可以是单个表达式或多行代码。
// 以button为例子
// 1. 创建了一个匿名类
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
doSomething();
}
});
// 2. 改用lambda表达式,单行搞定
button.setOnClickListener(view -> doSomething());
1.2 匿名类-继承父类或者实现接口
一个类里面的匿名内部类
class Polygon { // 父类
public void display() {
System.out.println("在 Polygon 类内部");
}
}
interface Polygon { // 接口
public void display();
}
class AnonymousDemo {
public void createClass() {
// 创建的匿名类继承了 Polygon 类
Polygon p1 = new Polygon() {
public void display() {
System.out.println("在匿名类内部。");
}
};
p1.display();
}
}
class Main {
public static void main(String[] args) {
AnonymousDemo an = new AnonymousDemo();
an.createClass();
}
}
输出:
在匿名类内部。
OnClickListener就是View类里面的一个接口
frameworks/base/core/java/android/view/View.java
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
问题
1. windowtoken是啥
2. view的gettag是啥
2. 最近应用列表也是一个activity吗?
参考
1. 适用于Android的Java 8:使用Lambda表达式的简介代码
https://code.tutsplus.com/zh-hans/tutorials/java-8-for-android-cleaner-code-with-lambda-expressions--cms-29661
2. Java 匿名类
https://www.runoob.com/java/java-anonymous-class.html