一个 Crash 引发的血案,字节跳动面试

========

crash 解决了,但是更棘手的问题还在后面,crash 只是把这个问题暴露出来了,也就是说两个首页有可能引起这个 crash,但是不是所有两个首页都引发这个 crash,我们还不知道这个问题在线上到底有多么严重,会不会出现很多意想不到的问题。

可能很多同学会说,出现两个 singleTask 的 activity ,他们有可能在两个栈里面。但是第一次灰度的时候我们已经确定了,他们是在一个栈里面,这回线索彻底断了。

天无绝人之路,在首页同学把修复代码集成进手淘的前一天,突然接到了一个钉钉消息,一个同学告诉我,他们有个 crash,要我确认,我看到堆栈之后发现跟前面解决的问题堆栈一致,就告诉他,这个版本修复,低概率的话暂时不用管。

但是他告诉我,我这边必现这个 crash。

按耐住激动的心,让他告诉我复现路径,果然必现。要来代码看到了一个崩快的原因:原来有同学在代码里面调用 startActivtyForResult 启动了首页。

想了一下也没有什么问题,因为 startActivity 内部调用就是 startActivityForResult 来实现的,但是直觉告诉我就是这里有问题,于是写个 demo 验证一下我的想法。

demo 很简单,两个 activity ,第一个 activity 是 singleTask 模式,其中一个按钮,点击正常调用到第二个 activity,第二个 activity 是标准模式,其中两个按钮,第一个按钮调用 startActivity 拉起第一个 activity,第二个按钮通过 startActivityForResult 拉起第一个 activity,启动 requsetCode 传递的是1。具体代码就不贴了,很简单一个 demo。当我点击第二个 activity 的第一个按钮的时候,看到栈里面只剩下一个 activity,符合预期。但是当我点击第二个按钮的时候,神奇的一幕出现了。很明显一个 TaskRecord 里面有两个 MainActivity,而且我确认这个 MainActivity 是 singleTask 的。奇怪的是 startActivity 里面调用的也是 startActivityForResult,只是传递的值是 -1 ,难道真的是这个值决定了这个行为么?

===

真相大白

========

看源码吧(以下代码来自 Android 9.0)

public void startActivity(Intent intent) {

this.startActivity(intent, null);

}

@Override

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);

}

}

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {

startActivityForResult(intent, requestCode, null);

}

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,

@Nullable Bundle options) {

if (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);

}

}

}

可以看到 startActivity 调用到 startActivityForResult,startActivityForResult 调用了 Instrumentation 中的 execStartActivity 执行后续流程(下面 else 中的 mParent.startActivityFromChild 的流程最终也调用到了此处)。

public ActivityResult execStartActivity(

Context who, IBinder contextThread, IBinder token, Activity target,

Intent intent, int requestCode, Bundle options) {

try {

intent.migrateExtraStreamToClipData();

intent.prepareToLeaveProcess(who);

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;

}

可以看到startActivity 调用到 startActivityForResult,startActivityForResult 调用了 Instrumentation 中的 execStartActivity 执行后续流程(下面 else 中的 mParent.startActivityFromChild 的流程最终也调用到了此处)。

public ActivityResult execStartActivity(

Context who, IBinder contextThread, IBinder token, Activity target,

Intent intent, int requestCode, Bundle options) {

try {

intent.migrateExtraStreamToClipData();

intent.prepareToLeaveProcess(who);

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;

}

里面删除一些无关逻辑,最终调用到 ActivityTaskManagerService 中的 startActivity 函数继续流程。

int startActivityAsUser(IApplicationThread caller, String callingPackage,

Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,

int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId,

boolean validateIncomingUser) {

enforceNotIsolatedCaller(“startActivityAsUser”);

userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,

Binder.getCallingPid(), Binder.getCallingUid(), “startActivityAsUser”);

// TODO: Switch to user app stacks here.

return getActivityStartController().obtainStarter(intent, “startActivityAsUser”)

.setCaller(caller)

.setCallingPackage(callingPackage)

.setResolvedType(resolvedType)

.setResultTo(resultTo)

.setResultWho(resultWho)

.setRequestCode(requestCode)

.setStartFlags(startFlags)

.setProfilerInfo(profilerInfo)

.setActivityOptions(bOptions)

.setMayWait(userId)

.execute();

}

ActivityTaskManagerService 的 startActivity 方法只是转换了一下控制权,最终调用到 startActivityAsUser中,把控制权交给 ActivityStarter,传递参数,并最终执行 execute 函数执行后续流程。

int execute() {

try {

if (mRequest.mayWait) {

return startActivityMayWait(mRequest.caller, mRequest.callingUid,

mRequest.callingPackage, mRequest.realCallingPid, mRequest.realCallingUid,

mRequest.intent, mRequest.resolvedType,

mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,

mRequest.resultWho, mRequest.requestCode, mRequest.startFlags,

mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig,

mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.userId,

mRequest.inTask, mRequest.reason,

mRequest.allowPendingRemoteAnimationRegistryLookup,

mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);

} else {

return startActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent,

mRequest.resolvedType, mRequest.activityInfo, mRequest.resolveInfo,

mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,

mRequest.resultWho, mRequest.requestCode, mRequest.callingPid,

mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid,

mRequest.realCallingUid, mRequest.startFlags, mRequest.activityOptions,

mRequest.ignoreTargetSecurity, mRequest.componentSpecified,

mRequest.outActivity, mRequest.inTask, mRequest.reason,

mRequest.allowPendingRemoteAnimationRegistryLookup,

mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);

}

} finally {

onExecutionComplete();

}

}

由于参数重mRequest.mayWait的值为true,所以调用到 startActivityMayWait。

private int startActivityMayWait(IApplicationThread caller, int callingUid,

String callingPackage, int requestRealCallingPid, int requestRealCallingUid,

Intent intent, String resolvedType, IVoiceInteractionSession voiceSession,

IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode,

int startFlags, ProfilerInfo profilerInfo, WaitResult outResult,

Configuration globalConfig, SafeActivityOptions options, boolean ignoreTargetSecurity,

int userId, TaskRecord inTask, String reason,

boolean allowPendingRemoteAnimationRegistryLookup,

PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {

int res = startActivity(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo,

voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid,

callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options,

ignoreTargetSecurity, componentSpecified, outRecord, inTask, reason,

allowPendingRemoteAnimationRegistryLookup, originatingPendingIntent,

allowBackgroundActivityStart);

return res;

}

去掉一些多余逻辑,留下关键路径,看到转移到 startActivity 继续执行,

private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,

String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,

String callingPackage, int realCallingPid, int realCallingUid, int startFlags,

SafeActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,

ActivityRecord[] outActivity, TaskRecord inTask, String reason,

boolean allowPendingRemoteAnimationRegistryLookup,

PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {

mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType,

aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,

callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,

options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord,

inTask, allowPendingRemoteAnimationRegistryLookup, originatingPendingIntent,

allowBackgroundActivityStart);

return getExternalResult(mLastStartActivityResult);

}

中间一顿操作,最终调用到下面

private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,

String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,

String callingPackage, int realCallingPid, int realCallingUid, int startFlags,

SafeActivityOptions options,

boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,

TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,

PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {

int result = START_CANCELED;

final ActivityStack startedActivityStack;

try {

mService.mWindowManager.deferSurfaceLayout();

result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,

startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);

} finally {

}

postStartActivityProcessing(r, result, startedActivityStack);

return result;

}

终于调用到了 startActivityUnchecked 这个地方看下里面都做了什么。

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,

ActivityRecord[] outActivity, boolean restrictedBgActivity) {

setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,

voiceInteractor, restrictedBgActivity);

// If the activity being launched is the same as the one currently at the top, then

// we need to check if it should only be launched once.

final ActivityStack topStack = mRootActivityContainer.getTopDisplayFocusedStack();

final ActivityRecord topFocused = topStack.getTopActivity();

final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);

final boolean dontStart = top != null && mStartActivity.resultTo == null

&& top.mActivityComponent.equals(mStartActivity.mActivityComponent)

&& top.mUserId == mStartActivity.mUserId

&& top.attachedToProcess()

&& ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0

|| isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))

// This allows home activity to automatically launch on secondary display when

// display added, if home was the top activity on default display, instead of

// sending new intent to the home activity on default display.

&& (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);

if (dontStart) {

// For paranoia, make sure we have correctly resumed the top activity.

topStack.mLastPausedActivity = null;

if (mDoResume) {

mRootActivityContainer.resumeFocusedStacksTopActivities();

}

ActivityOptions.abort(mOptions);

if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {

// We don’t need to start a new activity, and the client said not to do

// anything if that is the case, so this is it!

return START_RETURN_INTENT_TO_CALLER;

}

deliverNewIntent(top);

// Don’t use mStartActivity.task to show the toast. We’re not starting a new activity

// but reusing ‘top’. Fields in mStartActivity may not be fully initialized.

mSupervisor.handleNonResizableTaskIfNeeded(top.getTaskRecord(), preferredWindowingMode,

mPreferredDisplayId, topStack);

return START_DELIVERED_TO_TOP;

}

}

把一些乱七八糟的逻辑去除掉,抽丝剥茧,看到了dontStart 这个变量,下面这个变量的值控制了activity 启动过程中的复用流程,当它是true 的时候,则通过 resumeFocusedStacksTopActivities直接从栈中拉起一个已经存在的activity ,当他是 false 的时候新拉起一个 activity 接收这次请求。

boolean resumeFocusedStacksTopActivities(

ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {

if (!mStackSupervisor.readyToResume()) {

return false;

}

boolean result = false;

if (targetStack != null && (targetStack.isTopStackOnDisplay()

|| getTopDisplayFocusedStack() == targetStack)) {

result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);

}

for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {

boolean resumedOnDisplay = false;

final ActivityDisplay display = mActivityDisplays.get(displayNdx);

for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {

final ActivityStack stack = display.getChildAt(stackNdx);

final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();

if (!stack.isFocusableAndVisible() || topRunningActivity == null) {

continue;

}

if (stack == targetStack) {

// Simply update the result for targetStack because the targetStack had

// already resumed in above. We don’t want to resume it again, especially in

// some cases, it would cause a second launch failure if app process was dead.

resumedOnDisplay |= result;

continue;

}

if (display.isTopStack(stack) && topRunningActivity.isState(RESUMED)) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

img
img

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip1024b 备注Java获取(资料价值较高,非无偿)
img

最后

由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档

MySQL全家桶笔记

还有更多面试复习笔记分享如下

Java架构专题面试复习

长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-I5XfAvoz-1711599797234)]
[外链图片转存中…(img-MAmnUFtD-1711599797235)]
[外链图片转存中…(img-kh05HNBY-1711599797235)]
[外链图片转存中…(img-9ObKNOx2-1711599797236)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

[外链图片转存中…(img-04TTmp9G-1711599797236)]
[外链图片转存中…(img-NUfkSyrH-1711599797237)]

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip1024b 备注Java获取(资料价值较高,非无偿)
[外链图片转存中…(img-jyrQv5iy-1711599797237)]

最后

由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档

[外链图片转存中…(img-FqbZdDWw-1711599797238)]

还有更多面试复习笔记分享如下

[外链图片转存中…(img-bjNpQJun-1711599797238)]

  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值