ActivityStackSupervisor.StartActivityUncheckedLocked()函数分析
来源:互联网 发布:现货波段指标公式源码 编辑:程序博客网 时间:2024/05/01 19:31
一、谁调用的它?
ASS.StartActivityLocked()函数调用的它。注意这里是ASS.StartActivityLocked(),而ASS.StartActivityUncheckedLocked()在函数结尾会进入AS.StartActivityLocked(),注意两者不是相同的函数。
ASS.StartActivityLocked()函数主要作用:创建ActivityRecord。
概要分析:
这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中:
......
//在该函数中,将上一步得到的Activity信息ActivityInfo aInfo传进来,并创建ActivityRecord
final int startActivityLocked(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, ActivityInfo aInfo, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, boolean onlyIfNeeded, boolean componentSpecified) { int err = START_SUCCESS;// 从传进来的参数caller得到调用者的进程信息,并保存在callerApp变量中,这里就是Launcher应用程序的进程信息了。//其中,IApplicationThread caller是Launcher的Binder对象,ProcessRecord callerApp是要启动的程序的进程Record。 ProcessRecord callerApp = null; if (caller != null) { callerApp = mService.getRecordForAppLocked(caller); if (callerApp != null) { callingPid = callerApp.pid; callingUid = callerApp.info.uid; } else { ...... } } ......//参数resultTo是Launcher这个Activity里面的一个Binder对象,通过它可以获得Launcher这个Activity的相关信息,保存在sourceRecord变量中。说明: IBinder resultTo:Launcher这个Activity里面的一个Binder对象; String resultWho:additional identifier for use of resultT。 ActivityRecord sourceRecord :记录Launcher的Activity的ActivityRecord。 ActivityRecord sourceRecord = null; ActivityRecord resultRecord = null; if (resultTo != null) { int index = indexOfTokenLocked(resultTo); ...... if (index >= 0) { sourceRecord = (ActivityRecord)mHistory.get(index); if (requestCode >= 0 && !sourceRecord.finishing) { ...... } }//从intent获取Flags。 int launchFlags = intent.getFlags(); if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { ...... } ......//创建即将要启动的Activity的相关信息,并保存在r变量中。 ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); ......//接着调用startActivityUncheckedLocked函数进行下一步操作。 return startActivityUncheckedLocked(r, sourceRecord, grantedUriPermissions, grantedMode, onlyIfNeeded, true); } ......}
二、它是用来干什么的?
作用:为新开启的Activity创建Task,并将Task保存在ActivityRecord r.task域中。
三、具体分析:
分析(1):判断是否需要创建新的Task,设置launchFlags。
final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord, int startFlags, boolean doResume, Bundle options) { final Intent intent = r.intent; final int callingUid = r.launchedFromUid; int launchFlags = intent.getFlags(); // We'll invoke onUserLeaving before onPause only if the launching // activity did not explicitly state that this is an automated launch.//如果lauching Activity 没有明确指出这个Activity要自动启动的话,我们就要在执行onPause函数之前执行onUseLeaving函数 mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() => mUserLeaving=" + mUserLeaving); // If the caller has asked not to resume at this point, we make note // of this in the record so that we can skip it when trying to find // the top running activity.//如果laucher 没有要求在此时立即启动该Activity,那么 r.delayedResume = true(表示可以延迟resume),当查找最上层正在运行的Activity的时候就可以跳过它了。 if (!doResume) { r.delayedResume = true; } ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; // If the onlyIfNeeded flag is set, then we can do this if the activity // being launched is the same as the one making the call... or, as // a special case, if we do not know the caller then we count the // current top activity as the caller.//ActivityRecord checkedCaller优先等于sourceRecord,其次选择正在运行的Activity if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { ActivityRecord checkedCaller = sourceRecord; if (checkedCaller == null) { checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop); } if (!checkedCaller.realActivity.equals(r.realActivity)) { // Caller is not the same as launcher, so always needed. startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; } }//下面是三种需要创建新的TASK的情况:(1)sourceRecord=null说明要启动的Activity并不是由一个程序启动的,这时候我们总是启动一个新的TASK。(forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK);(2)Launcher的Activity是SingleInstance模式(sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE);(3)r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK。统一处理:设置launchFlags标志。(launchFlags标志决定着是否新创建Task等。) if (sourceRecord == null) { // This activity is not being started from another... in this // case we -always- start a new task. if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { Slog.w(TAG, "startActivity called from non-Activity context; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; } } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // The original activity who is starting us is running as a single // instance... this new activity it is starting must go on its // own task. launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { // The activity being started is a single instance... it always // gets launched into its own task. launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; }//创建ActivityStack sourceStack表示sourceRecord所在的TASK所在的Stack。并通过判断sourceRecord是否还在以及如果在的话,状态如何,来设置launchFlags标志。 final ActivityStack sourceStack; if (sourceRecord != null) { if (sourceRecord.finishing) { // If the source is finishing, we can't further count it as our source. This // is because the task it is associated with may now be empty and on its way out, // so we don't want to blindly throw it in to that task. Instead we will take // the NEW_TASK flow and try to find a task for it. //如果sourceRecord已经处于finishing状态,那么他所在的Task就很有可能已经为空或者即将为空。 //我们就不能盲目throw it in to that task。 if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { Slog.w(TAG, "startActivity called from finishing " + sourceRecord + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; } sourceRecord = null; sourceStack = null; } else { sourceStack = sourceRecord.task.stack; //sourceRecord依然健在 } } else { sourceStack = null;//sourceRecord == null }//如果新的Activity要新创建Task而启动,而且原先的Activity需要返回结果,这种情况下,调用函数r.resultTo.task.stack.sendActivityResultLocked()将 Activity.RESULT_CANCELED结果返回给launcher,并将resultTo置为null,resultTo的使命结束了。 if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { // For whatever reason this activity is being launched into a new // task... yet the caller has requested a result back. Well, that // is pretty messed up, so instead immediately send back a cancel // and let the new task continue launched as normal without a // dependency on its originator. Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho, r.requestCode, Activity.RESULT_CANCELED, null); r.resultTo = null; } boolean addingToTask = false; boolean movedHome = false; TaskRecord reuseTask = null; ActivityStack targetStack;
分析(2):如果不启动新的Task,那么寻找Activity所属的Task并移到前台。
if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // If bring to front is requested, and no result is requested, and // we can find a task that was started with this same // component, then instead of launching bring that one to the front.//从所有Stack中的所有Task中的Activity中查找看能否找到要启动的Activity,根据要启动的Activity启动模式是否是SingleInstance所采用的搜索方法是不一样的。 if (r.resultTo == null) {//检查是否有Task需要被带到前台。如果检测到就赋值给ActivityRecord intentActivity,就不需要启动新的TASK了,否则,intentActivity为空,需要创建新的TASK。这里的intentActivity就是老罗分析里面的taskTop。 // See if there is a task to bring to the front. If this is // a SINGLE_INSTANCE activity, there can be one and only one // instance of it in the history, and it is always in its own // unique task, so we do a special search. ActivityRecord intentActivity = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE ? findTaskLocked(r) : findActivityLocked(intent, r.info);//intentActivity 所在的TASK就是要启动的Activity所在的Task,要启动的Activity所在的Stack targetStack就是intentActivity所在的Stack。 if (intentActivity != null) { if (r.task == null) { r.task = intentActivity.task; } targetStack = intentActivity.task.stack; targetStack.mLastPausedActivity = null; if (DEBUG_TASKS) Slog.d(TAG, "Bring to front target: " + targetStack + " from " + intentActivity); moveHomeStack(targetStack.isHomeStack()); if (intentActivity.task.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. intentActivity.task.setIntent(intent, r.info); } // If the target task is not in the front, then we need // to bring it to the front... except... well, with // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like // to have the same behavior as if a new instance was // being started, which means not bringing it to the front // if the caller is not itself in the front. final ActivityStack lastStack = getLastStack(); ActivityRecord curTop = lastStack == null? null : lastStack.topRunningNonDelayedActivityLocked(notTop); if (curTop != null && (curTop.task != intentActivity.task || curTop.task != lastStack.topTask())) { r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); if (sourceRecord == null || (sourceStack.topActivity() != null && sourceStack.topActivity().task == sourceRecord.task)) { // We really do want to push this one into the // user's face, right now. movedHome = true; if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { // Caller wants to appear on home activity. intentActivity.task.mOnTopOfHome = true; }//这一步,将已经存在的我们的目标Task终于被移动到了前台!参数intentActivity.task就是我们搜索出来的我们要启动的Activity所在的Task,r就是我们要启动的ActivityRecord。 targetStack.moveTaskToFrontLocked(intentActivity.task, r, options); options = null; } } // If the caller has requested that the target task be // reset, then do so. if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r); } if ((startFlags&ActivityManager.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. if (doResume) { resumeTopActivitiesLocked(targetStack, null, options); } else { ActivityOptions.abort(options); } if (r.task == null) Slog.v(TAG, "startActivityUncheckedLocked: task left null", new RuntimeException("here").fillInStackTrace()); return ActivityManager.START_RETURN_INTENT_TO_CALLER; }//如果Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK被置位,那么将除此之外的所有Task都清除掉:reuseTask.performClearTaskLocked(); if ((launchFlags & (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) { // The caller has requested to completely replace any // existing task with its new activity. Well that should // not be too hard... reuseTask = intentActivity.task; reuseTask.performClearTaskLocked(); reuseTask.setIntent(r.intent, r.info); } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // 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. ActivityRecord top = intentActivity.task.performClearTaskLocked(r, launchFlags); if (top != null) { if (top.frontOfTask) { // 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.task.setIntent(r.intent, r.info); } ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); top.deliverNewIntentLocked(callingUid, r.intent); } else { // A special case: we need to // start the activity because it is not currently // running, and the caller has asked to clear the // current task to have this activity at the top. addingToTask = true; // Now pretend like this activity is being started // by the top of its task, so it is put in the // right place. sourceRecord = intentActivity; } } else if (r.realActivity.equals(intentActivity.task.realActivity)) { // In this case the top activity on the task is the // same as the one being launched, so we take that // as a request to bring the task to the foreground. // If the top activity in the task is the root // activity, deliver this new intent to it if it // desires. if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) && intentActivity.realActivity.equals(r.realActivity)) { ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, intentActivity.task); if (intentActivity.frontOfTask) { intentActivity.task.setIntent(r.intent, r.info); } intentActivity.deliverNewIntentLocked(callingUid, r.intent); } else if (!r.intent.filterEquals(intentActivity.task.intent)) { // In this case we are launching the root activity // of the task, but with a different intent. We // should start a new instance on top. addingToTask = true; sourceRecord = intentActivity; } } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { // In this case an activity is being launched in to an // existing task, without resetting that task. This // is typically the situation of launching an activity // from a notification or shortcut. We want to place // the new activity on top of the current task. addingToTask = true; sourceRecord = intentActivity; } else if (!intentActivity.task.rootWasReset) { // In this case we are launching in to an existing task // that has not yet been started from its front door. // The current task has been brought to the front. // Ideally, we'd probably like to place this new task // at the bottom of its stack, but that's a little hard // to do with the current organization of the code so // for now we'll just drop it. intentActivity.task.setIntent(r.intent, r.info); } if (!addingToTask && reuseTask == 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. if (doResume) { targetStack.resumeTopActivityLocked(null, options); } else { ActivityOptions.abort(options); } if (r.task == null) Slog.v(TAG, "startActivityUncheckedLocked: task left null", new RuntimeException("here").fillInStackTrace()); return ActivityManager.START_TASK_TO_FRONT; } } } } //String uri = r.intent.toURI(); //Intent intent2 = new Intent(uri); //Slog.i(TAG, "Given intent: " + r.intent); //Slog.i(TAG, "URI is: " + uri); //Slog.i(TAG, "To intent: " + intent2);// 这段代码的逻辑是看一下,当前在堆栈顶端的Activity是否就是即将要启动的Activity,有些情况下,如果即将要启动的Activity就在堆栈的顶端,那么,就不会重新启动这个Activity的别一个实例了,具体可以参考官方网站http://developer.android.com/reference/android/content/pm/ActivityInfo.html。 if (r.packageName != null) { // 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. ActivityStack topStack = getFocusedStack(); ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop); if (top != null && r.resultTo == null) { if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) { if (top.app != null && top.app.thread != null) { if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); // For paranoia, make sure we have correctly // resumed the top activity. topStack.mLastPausedActivity = null; if (doResume) { resumeTopActivitiesLocked(); } ActivityOptions.abort(options); if ((startFlags&ActivityManager.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! if (r.task == null) Slog.v(TAG, "startActivityUncheckedLocked: task left null", new RuntimeException("here").fillInStackTrace()); return ActivityManager.START_RETURN_INTENT_TO_CALLER; } top.deliverNewIntentLocked(callingUid, r.intent); if (r.task == null) Slog.v(TAG, "startActivityUncheckedLocked: task left null", new RuntimeException("here").fillInStackTrace()); return ActivityManager.START_DELIVERED_TO_TOP; } } } } } else { if (r.resultTo != null) { r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho, r.requestCode, Activity.RESULT_CANCELED, null); } ActivityOptions.abort(options); if (r.task == null) Slog.v(TAG, "startActivityUncheckedLocked: task left null", new RuntimeException("here").fillInStackTrace()); return ActivityManager.START_CLASS_NOT_FOUND; }
分析(3):创建新的Task,及后续处理。
boolean newTask = false; boolean keepCurTransition = false; // Should this be considered a new task? if (r.resultTo == null && !addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { targetStack = adjustStackFocus(r); moveHomeStack(targetStack.isHomeStack());//在此时,若前面的reuseTask为空,在targetStack中创建新的Task。//r.setTask(targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true),null, true); if (reuseTask == null) { r.setTask(targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + r.task); } else { r.setTask(reuseTask, reuseTask, true); } newTask = true;//newTask置位,说明使用了新的Task。 if (!movedHome) { if ((launchFlags & (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) { // Caller wants to appear on home activity, so before starting // their own activity we will bring home to the front. r.task.mOnTopOfHome = true; } } } else if (sourceRecord != null) { TaskRecord sourceTask = sourceRecord.task; targetStack = sourceTask.stack; moveHomeStack(targetStack.isHomeStack()); if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { // In this case, we are adding the activity to an existing // task, but the caller has asked to clear that task if the // activity is already running. ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags); keepCurTransition = true; if (top != null) { ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); top.deliverNewIntentLocked(callingUid, r.intent); // For paranoia, make sure we have correctly // resumed the top activity. targetStack.mLastPausedActivity = null; if (doResume) { targetStack.resumeTopActivityLocked(null); } ActivityOptions.abort(options); if (r.task == null) Slog.w(TAG, "startActivityUncheckedLocked: task left null", new RuntimeException("here").fillInStackTrace()); return ActivityManager.START_DELIVERED_TO_TOP; } } else if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { // In this case, we are launching an activity in our own task // that may already be running somewhere in the history, and // we want to shuffle it to the front of the stack if so. final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r); if (top != null) { final TaskRecord task = top.task; task.moveActivityToFrontLocked(top); ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task); top.updateOptionsLocked(options); top.deliverNewIntentLocked(callingUid, r.intent); targetStack.mLastPausedActivity = null; if (doResume) { targetStack.resumeTopActivityLocked(null); } return ActivityManager.START_DELIVERED_TO_TOP; } }// An existing activity is starting this new activity, so we want// to keep the new one in the same task as the one that is starting// it. r.setTask(sourceTask, sourceRecord.thumbHolder, false); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in existing task " + r.task + " from source " + sourceRecord); } 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.考虑真全面 targetStack = adjustStackFocus(r); moveHomeStack(targetStack.isHomeStack()); ActivityRecord prev = targetStack.topActivity(); r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new guessed " + r.task); }//赋权限,这里的mService就是AMS mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, intent, r.getUriPermissionsLocked()); 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; //最后就进入startActivityLocked(r, newTask, doResume)进一步处理了。 targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options); mService.setFocusedActivityLocked(r); return ActivityManager.START_SUCCESS; }
0 0
- ActivityStackSupervisor.StartActivityUncheckedLocked()函数分析
- ActivityStackSupervisor分析
- 应用启动 ActivityStackSupervisor分析
- Android源码--startActivityUncheckedLocked
- ActivityStackSupervisor.java关键信息解读
- 分析函数
- 分析函数
- 函数分析
- 分析函数
- 分析函数
- 分析函数
- 分析函数
- 分析函数
- 分析函数
- 分析函数
- 分析函数
- 分析函数
- 分析函数
- 关于自增列的操作
- sss
- cocos2d-x 3.0深入了解之三
- J2EE Servlet 学习笔记
- 如何把输入的阿拉伯数字金额转换成中文大写金额
- ActivityStackSupervisor.StartActivityUncheckedLocked()函数分析
- 部署发布
- XML 名称不能以"1"来头
- nginx使用遇到500和400
- 定制天气预报
- c++ 模板类有感
- Maven3路程(三)用Maven创建第一个web项目(1)
- C++自定义Vector模板
- Maven3路程(三)用Maven创建第一个web项目(2)servlet演示