一直都想过要去看一看一个activity是怎么启动起来的,但一直都没有静下心去认真看一看,现在趁着有时间好好阅读了一下源码,加上网上一些同志的分享,终于吧代码撸的比较清楚了,怕时间一久,又忘记了,赶紧记下来,如果有什么错误和遗漏的话,看官们也请指出。(本文中源码出自Android API 23)
创新互联公司致力于互联网品牌建设与网络营销,包括成都网站建设、网站建设、SEO优化、网络推广、整站优化营销策划推广、电子商务、移动互联网营销等。创新互联公司为不同类型的客户提供良好的互联网应用定制及解决方案,创新互联公司核心团队十多年专注互联网开发,积累了丰富的网站经验,为广大企业客户提供一站式企业网站建设服务,在网站建设行业内树立了良好口碑。
首先我们打开activity的startActivity方法:
@Override
public voidstartActivity(Intent intent) {
mBase.startActivity(intent);
}
Activity继承于 Context,这是个抽象类,里面并没有实现 startActivity() 这个方法,查了一下它的子类,嗯哼,就看了 ContextWrapper实现了它,但是,这个 mBase 又是什么鬼?嗯,也是一个 Context对象,但具体是那个类呢,查了一下 Context的继承关系,找到了 ContentImpl这个类,看一下这个类的介绍:
/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
classContextImplextendsContext {...}
这个类就是Context的Api的共同的实现类了,上面说到的 mBase 对象实际上说的就是这个类的实例了,那么这个对象是怎么来的,啥时候实例化的?不急,往下看,迟早要知道的。
好了,现在来看一下 ContentImpl中 startActivity()的实现:
@Override
public voidstartActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent,null);
}
@Override
public voidstartActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) ==0) {
throw newAndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+" context requires the FLAG_ACTIVITY_NEW_TASK flag."
+" Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(),mMainThread.getApplicationThread(),null,
(Activity)null, intent, -1, options);
}
看红色代码部分,mMainThread就是 ActivityThread类的实例,整个app进程的入口main()函数也是在这个类中的,这个 mMainThread提供了一个 Instrumentation对象
/**
* Base class for implementing application instrumentation code. When running
* with instrumentation turned on, this class will be instantiated for you
* before any of the application code, allowing you to monitor all of the
* interaction the system has with the application. An Instrumentation
* implementation is described to the system through an AndroidManifest.xml's
*<instrumentation> tag.
*/
public classInstrumentation {...}
简单的说,它就是用来监控应用中各种交互行为,从注释上看,我们可以通过 Manifest来指定一个我们自己实现的 Instrumentation类,好了这个不是重点,让我们看一下一个类怎么 startActivity的吧:
publicActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent,intrequestCode, Bundle options) {
//...
//前面的不是重点
try{
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
intresult =ActivityManagerNative.getDefault()
.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 newRuntimeException("Failure from system", e);
}
return null;
}
好吧,怎么这家伙又把启动 activity的事情交给别人做了。。。再点进去看:咦,怎么。。怎么是 IActivityManager的接口方法,好吧,又要找它的实现类了,but 貌似找到的这个家伙 ActivityManagerProxy 并没有认真的做事情啊,它找了一个代理去 startActivity()去了,那我们就去找这个代理去,从 IActivityManager extends IInterface 这个类的继承方式我们就能发现这其实就是Android中的Binder机制,跨进程的通信机制,ActivityManagerProxy找的代理其实就是 ActivityManagerService,ActivityManagerService属于系统进程,它对手机中所有的activity进行了统一的管理,源码中就是这么个获取的:
IBinder b = ServiceManager.getService("activity");
IActivityManager am =asInterface(b);
熟悉的 aidl嘛,好了好了,感觉有点跑偏,往回扯,来看看 AMS 怎么实现 startActivity() 的:
@Override
public final intstartActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,intrequestCode,
intstartFlags, ProfilerInfo profilerInfo, Bundle options) {
returnstartActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, options,
UserHandle.getCallingUserId());
}
@Override
public final intstartActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,intrequestCode,
intstartFlags, ProfilerInfo profilerInfo, Bundle options,intuserId) {
enforceNotIsolatedCaller("startActivity");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false,ALLOW_FULL_ONLY,"startActivity",null);
//TODO: Switch to user app stacks here.
returnmStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType,null,null, resultTo, resultWho, requestCode, startFlags,
profilerInfo,null,null, options,false, userId,null,null);
}
找到了,一个 mStackSupervisor对象,这又是干啥的?字面上的意思就是活动栈的管理器,先不管了,让我们先看看它的实现(...啊,代码不是一般的长,咱们只划重点):
final intstartActivityMayWait(IApplicationThread caller,intcallingUid,
String callingPackage, Intent intent, String resolvedType,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho,intrequestCode,intstartFlags,
ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
Bundle options,booleanignoreTargetSecurity,intuserId,
IActivityContainer iContainer, TaskRecord inTask) {
// Refuse possible leaked file descriptors
if(intent !=null&& intent.hasFileDescriptors()) {
throw newIllegalArgumentException("File descriptors passed in Intent");
}
booleancomponentSpecified = intent.getComponent() !=null;
// Don't modify the client's object!
intent =newIntent(intent);
// Collect information about the target of the Intent.
ActivityInfo aInfo =
resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
ActivityContainer container = (ActivityContainer)iContainer;
synchronized(mService) {
if(container !=null&& container.mParentActivity!=null&&
container.mParentActivity.state!= RESUMED) {
// Cannot start a child activity if the parent is not resumed.
returnActivityManager.START_CANCELED;
}
final intrealCallingPid = Binder.getCallingPid();
final intrealCallingUid = Binder.getCallingUid();
intcallingPid;
if(callingUid >=0) {
callingPid = -1;
}else if(caller ==null) {
callingPid = realCallingPid;
callingUid = realCallingUid;
}else{
callingPid = callingUid = -1;
}
finalActivityStack stack;
if(container ==null|| container.mStack.isOnHomeDisplay()) {
stack =mFocusedStack;
}else{
stack = container.mStack;
}
stack.mConfigWillChange= config !=null&&mService.mConfiguration.diff(config) !=0;
if(DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Starting activity when config will change = "+ stack.mConfigWillChange);
final longorigId = Binder.clearCallingIdentity();
if(aInfo !=null&&
(aInfo.applicationInfo.privateFlags
&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) !=0) {
// This may be a heavy-weight process! Check to see if we already
// have another, different heavy-weight process running.
if(aInfo.processName.equals(aInfo.applicationInfo.packageName)) {
if(mService.mHeavyWeightProcess!=null&&
(mService.mHeavyWeightProcess.info.uid!= aInfo.applicationInfo.uid||
!mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) {
intappCallingUid = callingUid;
if(caller !=null) {
Proce***ecord callerApp =mService.getRecordForAppLocked(caller);
if(callerApp !=null) {
appCallingUid = callerApp.info.uid;
}else{
Slog.w(TAG,"Unable to find app for caller "+ caller
+" (pid="+ callingPid +") when starting: "
+ intent.toString());
ActivityOptions.abort(options);
returnActivityManager.START_PERMISSION_DENIED;
}
}
IIntentSender target =mService.getIntentSenderLocked(
ActivityManager.INTENT_SENDER_ACTIVITY,"android",
appCallingUid, userId,null,null,0,newIntent[] { intent },
newString[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT
| PendingIntent.FLAG_ONE_SHOT,null);
Intent newIntent =newIntent();
if(requestCode >=0) {
// Caller is requesting a result.
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT,true);
}
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT,
newIntentSender(target));
if(mService.mHeavyWeightProcess.activities.size() >0) {
ActivityRecord hist =mService.mHeavyWeightProcess.activities.get(0);
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP,
hist.packageName);
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK,
hist.task.taskId);
}
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
aInfo.packageName);
newIntent.setFlags(intent.getFlags());
newIntent.setClassName("android",
HeavyWeightSwitcherActivity.class.getName());
intent = newIntent;
resolvedType =null;
caller =null;
callingUid = Binder.getCallingUid();
callingPid = Binder.getCallingPid();
componentSpecified =true;
try{
ResolveInfo rInfo =
AppGlobals.getPackageManager().resolveIntent(
intent,null,
PackageManager.MATCH_DEFAULT_ONLY
| ActivityManagerService.STOCK_PM_FLAGS, userId);
aInfo = rInfo !=null? rInfo.activityInfo:null;
aInfo =mService.getActivityInfoForUser(aInfo, userId);
}catch(RemoteException e) {
aInfo =null;
}
}
}
}
int res = startActivityLocked(caller, intent, resolvedType, aInfo,
voiceSession, voiceInteractor, resultTo, resultWho,
requestCode, callingPid, callingUid, callingPackage,
realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,
componentSpecified, null, container, inTask);
Binder.restoreCallingIdentity(origId);
if(stack.mConfigWillChange) {
// If the caller also wants to switch to a new configuration,
// do so now. This allows a clean switch, as we are waiting
// for the current activity to pause (so we will not destroy
// it), and have not yet started the next activity.
mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
"updateConfiguration()");
stack.mConfigWillChange=false;
if(DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Updating to new configuration after starting activity.");
mService.updateConfigurationLocked(config,null,false,false);
}
if(outResult !=null) {
outResult.result= res;
if(res == ActivityManager.START_SUCCESS) {
mWaitingActivityLaunched.add(outResult);
do{
try{
mService.wait();
}catch(InterruptedException e) {
}
}while(!outResult.timeout&& outResult.who==null);
}else if(res == ActivityManager.START_TASK_TO_FRONT) {
ActivityRecord r = stack.topRunningActivityLocked(null);
if(r.nowVisible&& r.state== RESUMED) {
outResult.timeout=false;
outResult.who=newComponentName(r.info.packageName, r.info.name);
outResult.totalTime=0;
outResult.thisTime=0;
}else{
outResult.thisTime= SystemClock.uptimeMillis();
mWaitingActivityVisible.add(outResult);
do{
try{
mService.wait();
}catch(InterruptedException e) {
}
}while(!outResult.timeout&& outResult.who==null);
}
}
}
returnres;
}
}
继续往里走~~
final intstartActivityLocked(IApplicationThread caller,
Intent intent, String resolvedType, ActivityInfo aInfo,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho,intrequestCode,
intcallingPid,intcallingUid, String callingPackage,
intrealCallingPid,intrealCallingUid,intstartFlags, Bundle options,
booleanignoreTargetSecurity,booleancomponentSpecified, ActivityRecord[] outActivity,
ActivityContainer container, TaskRecord inTask) {
interr = ActivityManager.START_SUCCESS;
//...
//中间各种逻辑判断,这里就不贴出来了,贼多。。。我们依旧划个重点
doPendingActivityLaunchesLocked(false);
err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
startFlags,true, options, inTask);//这里记住一下倒数第3个参数是true。
if(err <0) {
// If someone asked to have the keyguard dismissed on the next
// activity start, but we are not actually doing an activity
// switch... just dismiss the keyguard now, because we
// probably want to see whatever is behind it.
notifyActivityDrawnForKeyguard();
}
returnerr;
}
还得往里走。。。好多层:
final intstartActivityUncheckedLocked(finalActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,intstartFlags,
booleandoResume, Bundle options, TaskRecord inTask) {
//前面N个判断...
mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
intent, r.getUriPermissionsLocked(), r.userId);
if(sourceRecord !=null&& sourceRecord.isRecentsActivity()) {
r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);
}
if(newTask) {
EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);
}
ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
targetStack.mLastPausedActivity=null;
targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);
if(!launchTaskBehind) {
// Don't set focus on an activity that's going to the back.
mService.setFocusedActivityLocked(r,"startedActivity");
}
returnActivityManager.START_SUCCESS;
}
这个方法就屌的不行了,好几百行,一行行看下来绝对的心力交瘁,换个角度来看,这个方法的返回值比较有特点,他们都是 ActiviyManager中的常量,让我们看一看这些常量代表的意思:
/**
* Result for IActivityManaqer.startActivity: the activity was started
* successfully as normal.
* @hide
*/
public static final intSTART_SUCCESS=0;
/**
* Result for IActivityManaqer.startActivity: the caller asked that the Intent not
* be executed if it is the recipient, and that is indeed the case.
* @hide
*/
public static final intSTART_RETURN_INTENT_TO_CALLER=1;
/**
* Result for IActivityManaqer.startActivity: activity wasn't really started, but
* a task was simply brought to the foreground.
* @hide
*/
public static final intSTART_TASK_TO_FRONT=2;
/**
* Result for IActivityManaqer.startActivity: activity wasn't really started, but
* the given Intent was given to the existing top activity.
* @hide
*/
public static final intSTART_DELIVERED_TO_TOP=3;
好了,就贴几个,可以看出这些都是我们启动以后 activity的返回的各种结果,可想而知前面的各种逻辑是干嘛的了。那么这样我们就只找成功的那一个常量值 START_SUCCESS,在 startActivityLocked() 我们发现之后方法末尾返回了 START_SUCCESS,其余都不是,那么关键代码也就比较好找了,请看上面红色的重点~~,好了继续走你,这回就来到了 ActivityStack 这个类了:
final voidstartActivityLocked(ActivityRecord r,booleannewTask,
booleandoResume,booleankeepCurTransition, Bundle options) {
//逻辑判断。。比如Home键什么的
if(doResume) {
mStackSupervisor.resumeTopActivitiesLocked(this, r, options);
}
}
还记得前面划重点的参数吧,就是doResume,前面的一些逻辑代码咱们就不谈了,有兴趣的小盆友有时间可以慢慢看,咱们继续往下走 ActivityStackSupervisor.resumeTopActivitiesLocked():
booleanresumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target,
Bundle targetOptions) {
if(targetStack ==null) {
targetStack =mFocusedStack;
}
// Do targetStack first.
booleanresult =false;
if(isFrontStack(targetStack)) {
result = targetStack.resumeTopActivityLocked(target, targetOptions);
}
for(intdisplayNdx =mActivityDisplays.size() -1; displayNdx >=0; --displayNdx) {
finalArrayListstacks =mActivityDisplays.valueAt(displayNdx).mStacks;
for(intstackNdx = stacks.size() -1; stackNdx >=0; --stackNdx) {
finalActivityStack stack = stacks.get(stackNdx);
if(stack == targetStack) {
// Already started above.
continue;
}
if(isFrontStack(stack)) {
stack.resumeTopActivityLocked(null);
}
}
}
returnresult;
}
这个方法代码量比较少,关键的地方就是 resumeTopActivityLocked 了,感觉越来越接近目标了,还有点小激动。。。ActivityStack.resumeTopActivityLocked():
final booleanresumeTopActivityLocked(ActivityRecord prev, Bundle options) {
if(mStackSupervisor.inResumeTopActivity) {
// Don't even start recursing.
return false;
}
booleanresult =false;
try{
// Protect against recursion.
mStackSupervisor.inResumeTopActivity=true;
if(mService.mLockScreenShown== ActivityManagerService.LOCK_SCREEN_LEAVING) {
mService.mLockScreenShown= ActivityManagerService.LOCK_SCREEN_HIDDEN;
mService.updateSleepIfNeededLocked();
}
result = resumeTopActivityInnerLocked(prev, options);
}finally{
mStackSupervisor.inResumeTopActivity=false;
}
returnresult;
}
嗯,是一个内部具体实现方法:
private booleanresumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
//.....
ActivityStack lastStack = mStackSupervisor.getLastStack();
if (next.app != null && next.app.thread != null) {
//.........
try{
//...........
}catch(Exception e) {
// Whoops, need to restart this activity!
if(DEBUG_STATES) Slog.v(TAG_STATES,"Resume failed; resetting state to "
+ lastState +": "+ next);
next.state= lastState;
if(lastStack !=null) {
lastStack.mResumedActivity= lastResumedActivity;
}
Slog.i(TAG,"Restarting because process died: "+ next);
if(!next.hasBeenLaunched) {
next.hasBeenLaunched=true;
}else if(SHOW_APP_STARTING_PREVIEW&& lastStack !=null&&
mStackSupervisor.isFrontStack(lastStack)) {
mWindowManager.setAppStartingWindow(
next.appToken, next.packageName, next.theme,
mService.compatibilityInfoForPackageLocked(next.info.applicationInfo),
next.nonLocalizedLabel, next.labelRes, next.icon, next.logo,
next.windowFlags,null,true);
}
mStackSupervisor.startSpecificActivityLocked(next,true,false);
if(DEBUG_STACK)mStackSupervisor.validateTopActivitiesLocked();
return true;
}
// From this point on, if something goes wrong there is no way
// to recover the activity.
try{
next.visible=true;
completeResumeLocked(next);
}catch(Exception e) {
// If any exception gets thrown, toss away this
// activity and try the next one.
Slog.w(TAG,"Exception thrown during resume of "+ next, e);
requestFinishActivityLocked(next.appToken, Activity.RESULT_CANCELED,null,
"resume-exception",true);
if(DEBUG_STACK)mStackSupervisor.validateTopActivitiesLocked();
return true;
}
next.stopped=false;
}else{
// Whoops, need to restart this activity!
if(!next.hasBeenLaunched) {
next.hasBeenLaunched=true;
}else{
if(SHOW_APP_STARTING_PREVIEW) {
mWindowManager.setAppStartingWindow(
next.appToken, next.packageName, next.theme,
mService.compatibilityInfoForPackageLocked(
next.info.applicationInfo),
next.nonLocalizedLabel,
next.labelRes, next.icon, next.logo, next.windowFlags,
null,true);
}
if(DEBUG_SWITCH) Slog.v(TAG_SWITCH,"Restarting: "+ next);
}
if(DEBUG_STATES) Slog.d(TAG_STATES,"resumeTopActivityLocked: Restarting "+ next);
mStackSupervisor.startSpecificActivityLocked(next,true,true);
}
if(DEBUG_STACK)mStackSupervisor.validateTopActivitiesLocked();
return true;
}
阿西吧,将近400行。。老衲差点就要圆寂在这源码上面了。。耐着性子看了好几遍,还是让老衲找出了重点,前面的判断大概都有比如从 activity 后台到前台,从暂停到恢复,回到home页,屏幕旋转,切换到不同栈 activity 等等各种情况,但是这个不是咱们要的,咱就是要一个 activity 从无到有,所以咱只留相关的代码,ActivityStackSupervisor.startSpecificActivityLocked():
voidstartSpecificActivityLocked(ActivityRecord r,
booleanandResume,booleancheckConfig) {
// Is this activity's application already running?
Proce***ecord app =mService.getProce***ecordLocked(r.processName,
r.info.applicationInfo.uid,true);
r.task.stack.setLaunchTime(r);
if(app !=null&& app.thread!=null) {
try{
if((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) ==0
|| !"android".equals(r.info.packageName)) {
// Don't add this if it is a platform component that is marked
// to run in multiple processes, because this is actually
// part of the framework so doesn't make sense to track as a
// separate apk in the process.
app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
mService.mProcessStats);
}
realStartActivityLocked(r, app, andResume, checkConfig);
return;
}catch(RemoteException e) {
Slog.w(TAG,"Exception when starting activity "
+ r.intent.getComponent().flattenToShortString(), e);
}
// If a dead object exception was thrown -- fall through to
// restart the application.
}
mService.startProcessLocked(r.processName, r.info.applicationInfo,true,0,
"activity", r.intent.getComponent(),false,false,true);
}
啊,终于找到一个比较少代码的方法了,从逻辑判断上看 realStartActivityLocked 才是开启我们应用内的方法嘛,至于 startProcessLocked,它是启动应用进程的,咱们先不管,下一篇文章再理它,看看 realStartActivityLocked 怎么个实现的:
final booleanrealStartActivityLocked(ActivityRecord r,
Proce***ecord app,booleanandResume,booleancheckConfig)
throwsRemoteException {
//...check..
app.thread.scheduleLaunchActivity(newIntent(r.intent), r.appToken,
System.identityHashCode(r), r.info,newConfiguration(mService.mConfiguration),
newConfiguration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
newIntents, !andResume,mService.isNextTransitionForward(), profilerInfo);
// do other things...
return true;
}
忽略掉其他的代码,我们只看重点,这个 thread 是谁呢?还记得我们的 ActivityThread 吧,这个thread其实就是 ActivityThread中的内部类对象 ApplicationThread,我们去看一下这个方法的实现,看了那么多代码,这里其实已经很接近了:
public final voidscheduleLaunchActivity(Intent intent, IBinder token,intident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
intprocState, Bundle state, PersistableBundle persistentState,
ListpendingResults, List pendingNewIntents,
booleannotResumed,booleanisForward, ProfilerInfo profilerInfo) {
updateProcessState(procState,false);
ActivityClientRecord r =newActivityClientRecord();
r.token= token;
r.ident= ident;
r.intent= intent;
r.referrer= referrer;
r.voiceInteractor= voiceInteractor;
r.activityInfo= info;
r.compatInfo= compatInfo;
r.state= state;
r.persistentState= persistentState;
r.pendingResults= pendingResults;<
当前标题:Activity启动流程
浏览路径:http://cdkjz.cn/article/peepgs.html