标签: Activity启动流程 Activity启动时间优化


前一篇文章有介绍Launcher请求AMS过程,参考文章- 《Activity 启动流程(一)》 本文将介绍AMS到ApplicationThread的调用过程 ActivityManagerService.startActivity()

 @Override public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions, UserHandle.getCallingUserId()); } //caller:是指向Launcher的ApplicationThread对象,AMS可以通过该对象与Launcher的主线程进行交互 //intent:包含了所要启动的Activity的信息 //resultTo:是一个ActivityRecord的IBidner对象,它在AMS中会对应Launcher中的一个Activity组件 @Override public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) { enforceNotIsolatedCaller("startActivity");//判断调用者进程是否被隔离 //检查调用者权限 userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivity", null); // TODO: Switch to user app stacks here. //ActivityStarter是AMS管理Activity启动过程的一个管理类 return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, null, null, bOptions, false, userId, null, "startActivityAsUser"); }

在startActivity()方法中UserHandle.getCallingUserId(),该方法是用来获取调用者的UserID,AMS根据这个UserID来确认调用者的权限;

在startActivityAsUser中,继续调用mActivityStarter.startActivityMayWait,ActivityStarter是AMS管理Activity启动过程的一个管理类,最后一个参数"startActivityAsUser"代表启动的理由;

ActivityStarter.startActivityMayWait()

 final int startActivityMayWait(IApplicationThread caller, int callingUid, String callingPackage, Intent intent, String resolvedType, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, WaitResult outResult, Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId, TaskRecord inTask, String reason) { // Refuse possible leaked file descriptors //检查intent是否有文件描述符,有的话抛异常 if (intent != null && intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); } mSupervisor.mActivityMetricsLogger.notifyActivityLaunching(); boolean componentSpecified = intent.getComponent() != null; // Save a copy in case ephemeral needs it final Intent ephemeralIntent = new Intent(intent); // Don't modify the client's object! intent = new Intent(intent); if (componentSpecified && intent.getData() != null && Intent.ACTION_VIEW.equals(intent.getAction()) && mService.getPackageManagerInternalLocked() .isInstantAppInstallerComponent(intent.getComponent())) { // intercept intents targeted directly to the ephemeral installer the // ephemeral installer should never be started with a raw URL; instead // adjust the intent so it looks like a "normal" instant app launch intent.setComponent(null /*component*/); componentSpecified = false; } //获取ResolveInfo /*ResolveInfo这个类是通过解析一个与IntentFilter相对应的intent得到的信息。 *它部分地对应于从AndroidManifest.xml的< intent>标签收集到的信息。 * PackageManager这个类是用来返回各种的关联了当前已装入设备了的应用的包的信息。你可以通过getPacageManager来得到这个类。 * PackageManager manager = getPackageManager(); * * Intent intent = new Intent(Intent.ACTION_MAIN,null); * intent.addCategory(Intent.CATEGORY_LAUNCHER); * //通过Intent查找相关的Activity,更准确 * List< ResolveInfo> appList = manager.queryIntentActivities(intent,0); * //它是通过解析< Intent-filter>标签得到有 * < action android:name=”android.intent.action.MAIN”/> * < action android:name=”android.intent.category.LAUNCHER”/> * * ResolveInfo info = appsList.get(position); * 包名获取方法: info.activityInfo.packageName; * class名: info.activityInfo.name; * icon获取获取方法: ImageView i; i.setImageDrawable(info.activityInfo.loadIcon(manager)); * 应用名称获取方法:info.activityInfo.loadLabel(manager).toString(); * */ ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId); if (rInfo == null) { UserInfo userInfo = mSupervisor.getUserInfo(userId); if (userInfo != null && userInfo.isManagedProfile()) { // Special case for managed profiles, if attempting to launch non-cryto aware // app in a locked managed profile from an unlocked parent allow it to resolve // as user will be sent via confirm credentials to unlock the profile. UserManager userManager = UserManager.get(mService.mContext); boolean profileLockedAndParentUnlockingOrUnlocked = false; long token = Binder.clearCallingIdentity(); try { UserInfo parent = userManager.getProfileParent(userId); profileLockedAndParentUnlockingOrUnlocked = (parent != null) && userManager.isUserUnlockingOrUnlocked(parent.id) && !userManager.isUserUnlockingOrUnlocked(userId); } finally { Binder.restoreCallingIdentity(token); } if (profileLockedAndParentUnlockingOrUnlocked) { rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); } } } // Collect information about the target of the Intent. ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);//将获取的Activity组件保存到ActivityInfo中 ActivityOptions options = ActivityOptions.fromBundle(bOptions);//过渡动画 synchronized (mService) { final int realCallingPid = Binder.getCallingPid(); final int realCallingUid = Binder.getCallingUid(); int callingPid; if (callingUid >= 0) { callingPid = -1; } else if (caller == null) { callingPid = realCallingPid; callingUid = realCallingUid; } else { callingPid = callingUid = -1; } final ActivityStack stack = mSupervisor.mFocusedStack;//获取activity栈 stack.mConfigWillChange = globalConfig != null && mService.getGlobalConfiguration().diff(globalConfig) != 0; if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Starting activity when config will change = " + stack.mConfigWillChange); final long origId = 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)) { final ProcessRecord heavy = mService.mHeavyWeightProcess; if (heavy != null && (heavy.info.uid != aInfo.applicationInfo.uid || !heavy.processName.equals(aInfo.processName))) { int appCallingUid = callingUid; if (caller != null) { ProcessRecord 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); return ActivityManager.START_PERMISSION_DENIED; } } IIntentSender target = mService.getIntentSenderLocked( ActivityManager.INTENT_SENDER_ACTIVITY, "android", appCallingUid, userId, null, null, 0, new Intent[] { intent }, new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); Intent newIntent = new Intent(); if (requestCode >= 0) { // Caller is requesting a result. newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); } newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target)); if (heavy.activities.size() > 0) { ActivityRecord hist = heavy.activities.get(0); newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, hist.packageName); newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, hist.getTask().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; rInfo = mSupervisor.resolveIntent(intent, null /*resolvedType*/, userId); aInfo = rInfo != null ? rInfo.activityInfo : null; if (aInfo != null) { aInfo = mService.getActivityInfoForUser(aInfo, userId); } } } } final ActivityRecord[] outRecord = new ActivityRecord[1]; int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity, componentSpecified, outRecord, inTask, reason);//到该函数处理 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(globalConfig, null, false); } if (outResult != null) { outResult.result = res; if (res == ActivityManager.START_SUCCESS) { mSupervisor.mWaitingActivityLaunched.add(outResult); do { try { mService.wait(); } catch (InterruptedException e) { } } while (outResult.result != START_TASK_TO_FRONT && !outResult.timeout && outResult.who == null); if (outResult.result == START_TASK_TO_FRONT) { res = START_TASK_TO_FRONT; } } if (res == START_TASK_TO_FRONT) { final ActivityRecord r = outRecord[0]; // ActivityRecord may represent a different activity, but it should not be in // the resumed state. if (r.nowVisible && r.state == RESUMED) { outResult.timeout = false; outResult.who = r.realActivity; outResult.totalTime = 0; outResult.thisTime = 0; } else { outResult.thisTime = SystemClock.uptimeMillis(); mSupervisor.waitActivityVisible(r.realActivity, outResult); // Note: the timeout variable is not currently not ever set. do { try { mService.wait(); } catch (InterruptedException e) { } } while (!outResult.timeout && outResult.who == null); } } } mSupervisor.mActivityMetricsLogger.notifyActivityLaunched(res, outRecord[0]); return res; } }

函数中有去获取ResolveInfo,ResolveInfo这个类是通过解析一个与IntentFilter相对应的intent得到的信息。它部分地对应于从AndroidManifest.xml的< intent>标签收集到的信息。具体作用和使用参考《ResolveInfo 的作用和用法》 ActivityInfo是记录Activity组件信息的,上面函数中有去获取Activity的组件保存到ActivityInfo中; ActivityStack是activity栈;作用:用来管理系统所有Activity的各种状态;ActivityStack详细介绍见《Activity栈 -ActivityTask源码分析》

 int startActivityLocked(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, ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, TaskRecord inTask, String reason) { if (TextUtils.isEmpty(reason)) {//检查启动原因,为空抛异常 throw new IllegalArgumentException("Need to specify a reason."); } mLastStartReason = reason; mLastStartActivityTimeMs = System.currentTimeMillis(); mLastStartActivityRecord[0] = null; mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord, inTask);//继续用该方法处理 if (outActivity != null) { // mLastStartActivityRecord[0] is set in the call to startActivity above. outActivity[0] = mLastStartActivityRecord[0]; } // Aborted results are treated as successes externally, but we must track them internally. return mLastStartActivityResult != START_ABORTED ? mLastStartActivityResult : START_SUCCESS; } /** DO NOT call this method directly. Use {@link #startActivityLocked} instead. */ 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, ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, TaskRecord inTask) { int err = ActivityManager.START_SUCCESS; // Pull the optional Ephemeral Installer-only bundle out of the options early. final Bundle verificationBundle = options != null ? options.popAppVerificationBundle() : null; //ProcessRecord是Android系统中用于描述进程的数据结构 ProcessRecord callerApp = null;//调用进程对应的ProcessRecord实例,这里是Launcher进程 if (caller != null) {//检查IApplicationThread的caller,这个值是传过来的,指向的是Launcher所在的应用程序进程的ApplicationThread对象; callerApp = mService.getRecordForAppLocked(caller);//Launcher进程所对应的ProcessRecord对象 if (callerApp != null) { callingPid = callerApp.pid; callingUid = callerApp.info.uid;//保存Launcher进程的pid和UID信息 } else { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid + ") when starting: " + intent.toString()); err = ActivityManager.START_PERMISSION_DENIED; } } final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; if (err == ActivityManager.START_SUCCESS) { Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) + "} from uid " + callingUid); } ActivityRecord sourceRecord = null;//指向Launcher组件的一个ActivityRecord实例,它作为source端 ActivityRecord resultRecord = null; if (resultTo != null) {//resultTo对象指向了Launcher中一个Activity组件,这样它AMS就可以知道需要把结果返回给哪个组件 sourceRecord = mSupervisor.isInAnyStackLocked(resultTo); if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "Will send result to " + resultTo + " " + sourceRecord); if (sourceRecord != null) { if (requestCode >= 0 && !sourceRecord.finishing) {//如果需要返回结果,且启动当前组件的那个Activity组件还未finish() resultRecord = sourceRecord;//则sourceRecord则就是resultRecord } } } final int launchFlags = intent.getFlags(); if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { // Transfer the result target from the source activity to the new // one being started, including any failures. if (requestCode >= 0) { ActivityOptions.abort(options); return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; } resultRecord = sourceRecord.resultTo; if (resultRecord != null && !resultRecord.isInStackLocked()) { resultRecord = null; } resultWho = sourceRecord.resultWho; requestCode = sourceRecord.requestCode; sourceRecord.resultTo = null; if (resultRecord != null) { resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode); } if (sourceRecord.launchedFromUid == callingUid) { // The new activity is being launched from the same uid as the previous // activity in the flow, and asking to forward its result back to the // previous. In this case the activity is serving as a trampoline between // the two, so we also want to update its launchedFromPackage to be the // same as the previous activity. Note that this is safe, since we know // these two packages come from the same uid; the caller could just as // well have supplied that same package name itself. This specifially // deals with the case of an intent picker/chooser being launched in the app // flow to redirect to an activity picked by the user, where we want the final // activity to consider it to have been launched by the previous app activity. callingPackage = sourceRecord.launchedFromPackage; } } if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { // We couldn't find a class that can handle the given Intent. // That's the end of that! err = ActivityManager.START_INTENT_NOT_RESOLVED; } if (err == ActivityManager.START_SUCCESS && aInfo == null) { // We couldn't find the specific class specified in the Intent. // Also the end of the line. err = ActivityManager.START_CLASS_NOT_FOUND; } if (err == ActivityManager.START_SUCCESS && sourceRecord != null && sourceRecord.getTask().voiceSession != null) { // If this activity is being launched as part of a voice session, we need // to ensure that it is safe to do so. If the upcoming activity will also // be part of the voice session, we can only launch it if it has explicitly // said it supports the VOICE category, or it is a part of the calling app. if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) { try { intent.addCategory(Intent.CATEGORY_VOICE); if (!AppGlobals.getPackageManager().activitySupportsIntent( intent.getComponent(), intent, resolvedType)) { Slog.w(TAG, "Activity being started in current voice task does not support voice: " + intent); err = ActivityManager.START_NOT_VOICE_COMPATIBLE; } } catch (RemoteException e) { Slog.w(TAG, "Failure checking voice capabilities", e); err = ActivityManager.START_NOT_VOICE_COMPATIBLE; } } } if (err == ActivityManager.START_SUCCESS && voiceSession != null) { // If the caller is starting a new voice session, just make sure the target // is actually allowing it to run this way. try { if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(), intent, resolvedType)) { Slog.w(TAG, "Activity being started in new voice task does not support: " + intent); err = ActivityManager.START_NOT_VOICE_COMPATIBLE; } } catch (RemoteException e) { Slog.w(TAG, "Failure checking voice capabilities", e); err = ActivityManager.START_NOT_VOICE_COMPATIBLE; } } final ActivityStack resultStack = resultRecord == null ? null : resultRecord.getStack(); if (err != START_SUCCESS) { if (resultRecord != null) { resultStack.sendActivityResultLocked( -1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null); } ActivityOptions.abort(options); return err; } boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp, resultRecord, resultStack, options); abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); if (mService.mController != null) { try { // The Intent we give to the watcher has the extra data // stripped off, since it can contain private information. Intent watchIntent = intent.cloneFilter(); abort |= !mService.mController.activityStarting(watchIntent, aInfo.applicationInfo.packageName); } catch (RemoteException e) { mService.mController = null; } } mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage); mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, callingPid, callingUid, options); intent = mInterceptor.mIntent; rInfo = mInterceptor.mRInfo; aInfo = mInterceptor.mAInfo; resolvedType = mInterceptor.mResolvedType; inTask = mInterceptor.mInTask; callingPid = mInterceptor.mCallingPid; callingUid = mInterceptor.mCallingUid; options = mInterceptor.mActivityOptions; if (abort) { if (resultRecord != null) { resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null); } // We pretend to the caller that it was really started, but // they will just get a cancel result. ActivityOptions.abort(options); return START_ABORTED; } // If permissions need a review before any of the app components can run, we // launch the review activity and pass a pending intent to start the activity // we are to launching now after the review is completed. if (mService.mPermissionReviewRequired && aInfo != null) { if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( aInfo.packageName, userId)) { IIntentSender target = mService.getIntentSenderLocked( ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, callingUid, userId, null, null, 0, new Intent[]{intent}, new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); final int flags = intent.getFlags(); Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); newIntent.setFlags(flags | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); if (resultRecord != null) { newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true); } intent = newIntent; resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId); aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); if (DEBUG_PERMISSIONS_REVIEW) { Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) + "} from uid " + callingUid + " on display " + (mSupervisor.mFocusedStack == null ? DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId)); } } } // If we have an ephemeral app, abort the process of launching the resolved intent. // Instead, launch the ephemeral installer. Once the installer is finished, it // starts either the intent we resolved here [on install error] or the ephemeral // app [on install success]. //如果有一个临时应用程序,请求中止启动,而临时程序已安装,如果启动临时程序,解决intent安装出错,或者安装成功 if (rInfo != null && rInfo.auxiliaryInfo != null) { intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent, callingPackage, verificationBundle, resolvedType, userId); resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); } //创建即将要启动的Activity描述类 ActivityRecord //ActivityRecord 用于描述一个Activity,用来记录一个Activity的所有信息 ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid, callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null, mSupervisor, options, sourceRecord); if (outActivity != null) { outActivity[0] = r; } if (r.appTimeTracker == null && sourceRecord != null) { // If the caller didn't specify an explicit time tracker, we want to continue // tracking under any it has. r.appTimeTracker = sourceRecord.appTimeTracker; } final ActivityStack stack = mSupervisor.mFocusedStack; if (voiceSession == null && (stack.mResumedActivity == null || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) { if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, realCallingPid, realCallingUid, "Activity start")) { PendingActivityLaunch pal = new PendingActivityLaunch(r, sourceRecord, startFlags, stack, callerApp); mPendingActivityLaunches.add(pal); ActivityOptions.abort(options); return ActivityManager.START_SWITCHES_CANCELED; } } if (mService.mDidAppSwitch) { // This is the second allowed switch since we stopped switches, // so now just generally allow switches. Use case: user presses // home (switches disabled, switch to home, mDidAppSwitch now true); // user taps a home icon (coming from home so allowed, we hit here // and now allow anyone to switch again). mService.mAppSwitchesAllowedTime = 0; } else { mService.mDidAppSwitch = true; } doPendingActivityLaunchesLocked(false); return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true, options, inTask, outActivity); }

上述方法中,主要是创建即将要启动的Activity描述类 ActivityRecord和获取Launcher启动着的信息;将这些信息传到startActivity中,继续执行;

 private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, ActivityRecord[] outActivity) { int result = START_CANCELED; try { mService.mWindowManager.deferSurfaceLayout();//布局 result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, startFlags, doResume, options, inTask, outActivity); } finally { // If we are not able to proceed, disassociate the activity from the task. Leaving an // activity in an incomplete state can lead to issues, such as performing operations // without a window container. if (!ActivityManager.isStartResultSuccessful(result) && mStartActivity.getTask() != null) {//如果Activity没有启动成功,但是又占了一个Activity栈,这个时候会去清除Actiivty栈; mStartActivity.getTask().removeActivity(mStartActivity); } mService.mWindowManager.continueSurfaceLayout(); } postStartActivityProcessing(r, result, mSupervisor.getLastStack().mStackId, mSourceRecord, mTargetStack); return result; } // Note: This method should only be called from {@link startActivity}. private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, ActivityRecord[] outActivity) { //设置一些成员变量;初始化属性 /*初始化了一些属性,重要的属性有: mStartActivity: 即Settings的ActivityRecord; mSourceRecord: Launcher的ActivityRecord; mLaunchSingleTop: 是否是singleTop启动模式; mLaunchSingleInstance: singleInstance启动模式; mLaunchSingleTask:singleTask启动模式;*/ setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession, voiceInteractor); computeLaunchingTaskFlags();//将启动模式SingleInstance或者SingleTask转化成FLAG_ACTIVITY_NEW_TASK标签,保存在mLaunchFlags //初始化获取当前的ActivityStack,ActivityStack是用来管理栈的 computeSourceStack(); //将收集到的flags设置给Intent mIntent.setFlags(mLaunchFlags); //找到可以复用的栈或者Activity ActivityRecord reusedActivity = getReusableIntentActivity(); final int preferredLaunchStackId = (mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID; final int preferredLaunchDisplayId = (mOptions != null) ? mOptions.getLaunchDisplayId() : DEFAULT_DISPLAY; if (reusedActivity != null) {//成立,此时reusedActivity不为null,有可复用的Activity或者栈 // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but // still needs to be a lock task mode violation since the task gets cleared out and // the device would otherwise leave the locked task. if (mSupervisor.isLockTaskModeViolation(reusedActivity.getTask(), (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) { mSupervisor.showLockTaskToast(); Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode"); return START_RETURN_LOCK_TASK_MODE_VIOLATION; } //给Activity_B分配栈; //将:要启动的Activity绑定到已经找到的可复用的栈中,这样,它们就在同一个栈中了 if (mStartActivity.getTask() == null) { mStartActivity.setTask(reusedActivity.getTask()); } //设置栈的intent和taskAffinity和rootAffinity; //我们可以发现,第一个被加入到栈的Activity,它的intent就是栈的intent,它的taskAffinity就是栈的affinity和rootAffinity; if (reusedActivity.getTask().intent == null) { // This task was started because of movement of the activity based on affinity... // Now that we are actually launching it, we can assign the base intent. reusedActivity.getTask().setIntent(mStartActivity); } // This code path leads to delivering a new intent, we want to make sure we schedule it // as the first operation, in case the activity will be resumed as a result of later // operations. // 当intent中有FLAG_ACTIVITY_CLEAR_TOP标签,或者启动模式为SingleInstance,SingleTask时; // 也就是说,启动模式为SingleInstance,SingleTask时,就默认为有FLAG_ACTIVITY_CLEAR_TOP标签 if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 || isDocumentLaunchesIntoExisting(mLaunchFlags) || mLaunchSingleInstance || mLaunchSingleTask) { //找到的可复用栈 final TaskRecord task = reusedActivity.getTask(); // In this situation we want to remove all activities from the task up to the one // being started. In most cases this means we are resetting the task to its initial // state. //查找栈中是否存在与mStartActivity相同的Activity,假设存在这个Activity,那就finish并且删除掉栈中所有位于这个Activity前面的Activity final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity, mLaunchFlags); // The above code can remove {@code reusedActivity} from the task, leading to the // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The // task reference is needed in the call below to // {@link setTargetStackAndMoveToFrontIfNeeded}. // 上面的代码有可能把reusedActivity也从栈中给删除了,但是reusedActivity在后面的setTargetStackAndMoveToFrontIfNeeded()方法中会被用到, // 所以得给reusedActivity重新设置task if (reusedActivity.getTask() == null) { reusedActivity.setTask(task); } if (top != null) {//top != null,意味着栈中存在与mStartActivity相同的Activity if (top.frontOfTask) {//位于栈底的Activity才有资格成为frontOfTask,此时的top,不一定位于栈底 // Activity aliases may mean we use different intents for the top activity, // so make sure the task now has the identity of the new intent. top.getTask().setIntent(mStartActivity); } //调用mStartActivity的onNewIntet()方法 deliverNewIntent(top); } } sendPowerHintForLaunchStartIfNeeded(false /* forceSend */, reusedActivity); //(1)mStartActivity的启动,将目标ActivityStack放到前台; //(2)也有可能会导致从栈A跳转到栈B,那我们现在就要把栈B放到前台,以后的操作都是操作栈B的; reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity); final ActivityRecord outResult = outActivity != null && outActivity.length > 0 ? outActivity[0] : null; // When there is a reused activity and the current result is a trampoline activity, // set the reused activity as the result. if (outResult != null && (outResult.finishing || outResult.noDisplay)) { outActivity[0] = reusedActivity; } 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! And for paranoia, make sure we have // correctly resumed the top activity. resumeTargetStackIfNeeded(); return START_RETURN_INTENT_TO_CALLER; } //处理一些特殊标签,判断新启动的Activity是否被添加进已经存在的栈 setTaskFromIntentActivity(reusedActivity); //举个例子,这个例子会使下面的判断成立; //栈中有A,B,C,D 4个Activity,现在再次启动B,启动模式为singleTask; //这样,下面的判断就成立了,函数直接返回,不再往下执行 if (!mAddingToTask && mReuseTask == null) { // We didn't do anything... but it was needed (a.k.a., client don't use that // intent!) And for paranoia, make sure we have correctly resumed the top activity. resumeTargetStackIfNeeded(); if (outActivity != null && outActivity.length > 0) { outActivity[0] = reusedActivity; } return START_TASK_TO_FRONT; } } if (mStartActivity.packageName == null) { final ActivityStack sourceStack = mStartActivity.resultTo != null ? mStartActivity.resultTo.getStack() : null; if (sourceStack != null) { sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo, mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, null /* data */); } ActivityOptions.abort(mOptions); return START_CLASS_NOT_FOUND; } // 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 = mSupervisor.mFocusedStack; final ActivityRecord topFocused = topStack.topActivity(); final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop); //接下来处理SingleTop启动模式,要想SingleTop起作用,那要启动的Activity必须已经位于栈顶 final boolean dontStart = top != null && mStartActivity.resultTo == null && top.realActivity.equals(mStartActivity.realActivity) && top.userId == mStartActivity.userId && top.app != null && top.app.thread != null && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop || mLaunchSingleTask); if (dontStart) {//当启动模式为SINGLE_TOP时,并且要启动的Activity已经位于栈顶,那就执行onNewIntent()方法 // For paranoia, make sure we have correctly resumed the top activity. topStack.mLastPausedActivity = null; if (mDoResume) { mSupervisor.resumeFocusedStackTopActivityLocked(); } 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.getTask(), preferredLaunchStackId, preferredLaunchDisplayId, topStack.mStackId); return START_DELIVERED_TO_TOP; } boolean newTask = false; final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null) ? mSourceRecord.getTask() : null; // Should this be considered a new task? //判断是否要创建一个新task; int result = START_SUCCESS; //mStartActivity.resultTo == null 启动者不关心启动结果 //mLaunchFlags 启动模式 //接下来就开始创建栈或者绑定栈了 if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { newTask = true;//需要创建新栈 由于该Activity的启动参数设置为FLAG_ACTIVITY_NEW_TASK,表明它运行在Launcher之外的任务中;该任务可以是新任务,也可以是某个存在的任务 result = setTaskFromReuseOrCreateNewTask( taskToAffiliate, preferredLaunchStackId, topStack); //如果Activity通过android:taskAffinity属性设置了专属任务,则会去查询该任务是否存在,不存在则会创建它,并将该Activity运行在该任务中 //创建了新的TaskRecord后,它会被保存到ActivityStack::mTaskHistory中,接下来就会进入ActivityStatck去启动Activity } else if (mSourceRecord != null) {//mSourceRecord是ActivityRecord类型,ActivityRecord用来记录一个Activity的所有信息;mSourceRecord不为空, //一般情况下,mSourceRecord就是调用者,如本例中的Launcher; //但也有特殊情况,举个例子,如果启动模式为singleTask,栈中又不存在相同的Activity时,mSourceRecord就是栈顶的Activity, result = setTaskFromSourceRecord(); } else if (mInTask != null) { result = setTaskFromInTask(); } else { // This not being started from an existing activity, and not part of a new task... // just put it in the top task, though these days this case should never happen. setTaskToCurrentTopOrCreateNewTask(); } if (result != START_SUCCESS) { return result; } mService.grantUriPermissionFromIntentLocked(mCallingUid, mStartActivity.packageName, mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId); mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent, mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid)); if (mSourceRecord != null) { mStartActivity.getTask().setTaskToReturnTo(mSourceRecord); } if (newTask) { EventLog.writeEvent( EventLogTags.AM_CREATE_TASK, mStartActivity.userId, mStartActivity.getTask().taskId); } ActivityStack.logStartActivity( EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.getTask()); mTargetStack.mLastPausedActivity = null; sendPowerHintForLaunchStartIfNeeded(false /* forceSend */, mStartActivity); //启动 mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition, mOptions); if (mDoResume) { final ActivityRecord topTaskActivity = mStartActivity.getTask().topRunningActivityLocked(); if (!mTargetStack.isFocusable() || (topTaskActivity != null && topTaskActivity.mTaskOverlay && mStartActivity != topTaskActivity)) { // If the activity is not focusable, we can't resume it, but still would like to // make sure it becomes visible as it starts (this will also trigger entry // animation). An example of this are PIP activities. // Also, we don't want to resume activities in a task that currently has an overlay // as the starting activity just needs to be in the visible paused state until the // over is removed. mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); // Go ahead and tell window manager to execute app transition for this activity // since the app transition will not be triggered through the resume channel. mWindowManager.executeAppTransition(); } else { // If the target stack was not previously focusable (previous top running activity // on that stack was not visible) then any prior calls to move the stack to the // will not update the focused stack. If starting the new activity now allows the // task stack to be focusable, then ensure that we now update the focused stack // accordingly. if (mTargetStack.isFocusable() && !mSupervisor.isFocusedStack(mTargetStack)) { mTargetStack.moveToFront("startActivityUnchecked"); } mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity, mOptions); } } else { mTargetStack.addRecentActivityLocked(mStartActivity); } mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack); mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredLaunchStackId, preferredLaunchDisplayId, mTargetStack.mStackId); return START_SUCCESS; }

在startActivity方法中,会调用startActivityUnchecked继续启动Acitivty,但是会对startActivityUnchecked的结果进行判断,如果Activity没有启动成功,但是又占了一个Activity栈,这个时候会去清除Actiivty栈; 在startActivityUnchecked方法中,主要设置和初始化启动Acitivty 相关属性 1.setInitialState函数中,初始化属性: mStartActivity:即Settings的ActivityRecord; mSourceRecord:Launcher的ActivityRecord; mLaunchSingleTop: 是否是singleTop启动模式; mLaunchSingleInstance:singleInstance启动模式; mLaunchSingleTask:gleTask启动模式; 2.在computeLaunchingTaskFlags中将启动模式SingleInstance或者SingleTask转化成FLAGACTIVITYNEW_TASK标签,保存在mLaunchFlags

3.在computeSourceStack中初始化mSourceStack;

4.ActivityRecord reusedActivity = getReusableIntentActivity();在getReusableIntentActivity函数中得到一个Activity

5.查找栈中是否存在与mStartActivity相同的Activity,假设存在这个Activity,那就finish并且删除掉栈中所有位于这个Activity前面的Activity

final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity, mLaunchFlags);

6.将目标ActivityStack和Task移动到前台:

reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);

setTargetStackAndMoveToFrontIfNeeded()函数的作用,就是将Settings所在的ActivityStack移动到前台,并且将Settings所在栈移动到ActivityStack的顶部;

7.处理一些特殊的标签,判断是否要将Activity添加进栈中;

setTaskFromIntentActivity(reusedActivity);

8.处理SingleTop启动模式,要想SingleTop起作用,那要启动的Activity必须已经位于栈顶;

 final boolean dontStart = top != null && mStartActivity.resultTo == null && top.realActivity.equals(mStartActivity.realActivity) && top.userId == mStartActivity.userId && top.app != null && top.app.thread != null && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop || mLaunchSingleTask);

当启动模式为SingleTop,或者SingleTask,或者含有SINGLE_TOP标签,并且要启动的Activity又已经位于栈顶时,那就执行onNewIntent()方法;

9.开始创建栈或者绑定栈了; 先处理FLAGACTIVITYNEW_TASK标签,含有此标签,表示可能会需要创建一个新栈;

result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, preferredLaunchStackId, topStack):

ActivityStackSupervisor.resumeFocusedStackTopActivityLocked

 boolean resumeFocusedStackTopActivityLocked( ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) { if (!readyToResume()) { return false; } if (targetStack != null && isFocusedStack(targetStack)) { return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); } //获取要启动的Activity的状态; final ActivityRecord r = mFocusedStack.topRunningActivityLocked(); if (r == null || r.state != RESUMED) { mFocusedStack.resumeTopActivityUncheckedLocked(null, null); } else if (r.state == RESUMED) { // Kick off any lingering app transitions form the MoveTaskToFront operation. mFocusedStack.executeAppTransition(targetOptions); } return false; }

resumeFocusedStackTopActivityLocked该方法主要处理Activity状态; 接下来调用mFocusedStack.resumeTopActivityUncheckedLocked(null, null);

ActivityStack.resumeTopActivityUncheckedLocked

 boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) { if (mStackSupervisor.inResumeTopActivity) { // Don't even start recursing. return false; } boolean result = false; try { // Protect against recursion. mStackSupervisor.inResumeTopActivity = true; result = resumeTopActivityInnerLocked(prev, options); } finally { mStackSupervisor.inResumeTopActivity = false; } final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); if (next == null || !next.canTurnScreenOn()) { checkReadyForSleep(); } return result; }

接下来调用ActivityStack.resumeTopActivityInnerLocked

 private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { //、、、 ... if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Restarting " + next); mStackSupervisor.startSpecificActivityLocked(next, true, true); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; }

ActivityStack.startSpecificActivityLocked()

 void startSpecificActivityLocked(ActivityRecord r, boolean andResume, boolean checkConfig) { // Is this activity's application already running? //获取即将启动的Activity的所在的应用程序进程 ProcessRecord app = mService.getProcessRecordLocked(r.processName, r.info.applicationInfo.uid, true); r.getStack().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); }
 final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app, boolean andResume, boolean checkConfig) throws RemoteException { ... app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, System.identityHashCode(r), r.info, // TODO: Have this take the merged configuration instead of separate global // and override configs. mergedConfiguration.getGlobalConfiguration(), mergedConfiguration.getOverrideConfiguration(), r.compat, r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results, newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);

app.thread是IApplicationThread类,AIDL 原理可知,IApplicationThread实现类是ApplicationThread; ApplicationThread是AMS所在进程(System)和应用进程的通讯桥梁

android activity启动流程_Activity 启动流程(二)相关推荐

  1. Android Activity:四种启动模式,Intent Flags和任务栈(转自他人博客)

    在Android中每个界面都是一个Activity,切换界面操作其实是多个不同Activity之间的实例化操作.那各个页面跳转关系如何决定呢?如果启动了顺序启动了ABCD的Activiy,如何从D调回 ...

  2. Android Activity的4种启动模式详解(示例)

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/5233269.html 先介绍下Android对Activity的管理,Android采用Task来管理多个A ...

  3. Android Activity的4大启动模式(launchmode)

    前提:Android的activity存储于一个Activity Task中,加载模式决定打开一个Activity时是否需要重新创建Activity.一般情况,早来的Activity先入栈,在栈底,之 ...

  4. android activity启动流程_1307页!一线大厂Android面试全套真题解析!

    /   前言   / 金九银十到了,很多读者都反映有面试的需求,所以我特地给大家准备了一点资料! 下面的题目都是大家在面试一线互联网大厂时经常遇到的面试真题和答案解析,如果大家还有其他好的题目或者好的 ...

  5. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | AMS 启动前使用动态代理替换掉插件 Activity 类 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  6. Android Activity的启动流程分析:以Launcher启动一个Activity为例,分析应用进程的创建、Activity的启动,以及他们和AMS之间的交互

    文章目录 一. Step1 - Step 11:Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity: Step 1. Laun ...

  7. Android 5.1 Lollipop Phone工作流程浅析(十三)__InCallActivity启动Performance浅析

    前置文章: < Android 4.4 Kitkat Phone工作流程浅析(一)__概要和学习计划> <Android 4.4 Kitkat Phone工作流程浅析(二)__UI结 ...

  8. 深入分析Android 9.0源代码——Service启动流程(startService方式)

    引言 点击此处查看<深入分析Android 9.0源代码>系列的组织结构和相关说明. 1 应用进程发起启动请求 本章的调用流程如下图所示: (Context)ContextWrapperC ...

  9. 四.Activity的启动流程-ActivityThread启动Activity的过程

    Activity的启动一般分为普通Activity的启动,根Activity的启动.而根Activity是指应用启动的第一个Activity过程,所以也可以理解为应用的启动过程. 相关内容链接:and ...

最新文章

  1. Go modules基础精进,六大核心概念全解析(下)
  2. 刚刚,中国空间站核心舱“天和”出征太空!
  3. 今天学习了无序列表和有序列表和使用HTML5创建表格
  4. sshpass批量执行操作
  5. 三角函数公式总结(四)
  6. 说透Applet的数字签名之1——Applet及其运行
  7. python.集合转列表_Python列表、元组、字典、集合
  8. java 运行 依赖_java – 运行时中的依赖项确定
  9. 通达OA执着创新 只为让梦想照进现实
  10. CAD2020学习教程
  11. 龙贝格算法例题_数值分析实验报告——龙贝格求积算法 椭圆周长公式
  12. Open JDK patched with font fix
  13. 深入浅出ERC777合约
  14. 谈业务流程全生命周期管理支撑业务流程再造(2)
  15. 泰凌微8258入门教程 问题篇②——make Error, section .text loaded at [x,x] overlaps section .retention_data loaded
  16. Vue中使用swiper构建简易轮播图
  17. 2011年新的个人纳税情况
  18. 2018年12月最新win101809教育版激永久激活密钥和方法
  19. 快端午了,用Python画一盘粽子送给你
  20. ESP8266-Arduino编程实例-LPS25H压阻式压力传感器驱动

热门文章

  1. 点在多边形内外的判断【计算几何】
  2. 如何使用postman访问若依后台权限功能
  3. Go——从文件路径解析解析GAVC坐标解决方案
  4. MySQL——统计某个表每天的总量和增量问题解决方案
  5. JetBrains —— JetBrains系列IDE优化配置(提高启动和运行速度)
  6. linux can t open sh,Linux python3 - Can't open lib 'SQL Server'
  7. centos安装Hue 3.7.0
  8. MyBatis-学习笔记12【12.Mybatis注解开发】
  9. 操作系统 课堂练习题02【8道 经典题目】
  10. Ajax和JSON-学习笔记03【JSON_基础语法】