1.1 任务和返回栈 - 实际数据模型 

这个是指在调度体系里实际保存的TaskRecord实例,而ActivityRecord-TaskRecord-ActivityStack之间的关系建议看官方文档。
任务栈是实际在后台的任务,因此这些任务也都有对应的显示层实例。

其创建与删除通过stack控制: ActivityStack#createTaskRecord(),ActivityStack#removeTask()
当前activity栈可以通过adb shell dumpsys activity activities 命令打印出来,这个命令最终会调用到方法:
AMS#dumpActivitiesLocked() -> ActivityStackSupervisor#dumpActivitiesLocked()

1.2 AMS.mRecentTasks - 历史任务记录

RecentTasks用于在AMS中保存最近使用的task记录,可以通过adb shell dumpsys activity recents命令打印其列表。
ecentTasks应该被看作任务的历史记录而不是实例,虽然保留了TaskRecord对象,但并不一定有对应的activity。
RecentTasks列表是始终有序的,最近使用的task在列表中的位置最靠前。之所以有序,是因为框架里,每次resume后都会把当前应用重新添加到RecentTasks中,典型代码如下:

 ActivityStack#resumeTopActivityInnerLocked() {
......
if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next + " (in existing)");
next.state = ActivityState.RESUMED;
mResumedActivity = next;
next.task.touchActiveTime();
mRecentTasks.addLocked(next.task);
mService.updateLruProcessLocked(next.app, true, null);
updateLRUListLocked(next);
mService.updateOomAdjLocked();
......
}

如上代码第7行即将task置于mRecentTasks头部,而6行是更新task的activeTime。

与add对应的remove,则基本只有AMS#cleanUpRemovedTaskLocked()这一个地方:

 private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess,
boolean removeFromRecents) {
if (removeFromRecents) {
mRecentTasks.remove(tr);
tr.removedFromRecents();
// zeusis : clear the paired TaskRecord and resize fullscreenStack to normal
......
}
.....
}

通常的remove途径,就是用户在多任务上滑快照回收一个应用或在多任务点清理内存按钮批量回收应用。

应用按back键退出做finish,虽然activity都destroy掉了,在整个的应用栈里被删掉,但是taskrecord其实还是保留下来的,保存在mRecentTasks中直到历史记录过多。

####TaskPersister
与RecentTasks相关的还有TaskPersister,用于保存被设定persistent属性的任务列表,并在手机重启后从本地保存的xml重新加载TaskRecord。有兴趣的话可以看TaskRecord#restoreFromXml()及相关流程。

1.3 多任务 - 展示给用户的任务概览

Recents(多任务)中记录的任务列表与其它两者是不一样的,由于是展示给用户的,所以要尽可能符合用户期待,这就造成一些实际已销毁或回收的任务也要保存显示,而一些无关紧要的或特别的应用又需要隐藏起来。

如此说来,这个任务列表首先就是与任务栈解耦的,实际上,多任务的列表是每次启动多任务时从任务历史记录处获取列表,然后再做各种过滤动作获得真正适合展示给用户的列表。在下文3.1节具体讲解了多任务的列表是如何从AMS获取并过滤的。

[Recents官方文档][3]

2 关系

AMS#RecentTasks和SystemUI.Recents.TaskStack区别

  • 两者数据形式上都是线性有序的
  • 后者列表包含于前者,后者是前者过滤而来的,具体过滤步骤见3.1的流程
  • 前者的更新会触发回调改变后者

AMS#RecentTasks和任务栈区别

  • 任务栈是个大的数据模型,taskRecord按序排列在不同栈中,而RecentTasks是线性记录
  • 应用destory后,在RecentTasks中仍保有其taskrecord,但在任务栈中已将其移除

3 具体代码例子

3.1 Recents从AMS.RecentTasks更新列表

Recents作为系统界面,虽然与AMS关系紧密,但毕竟是一个独立出来的app模块,所以其列表很难做到与server一侧的情况保持同步,为此,Recents每次启动时都要重刷整个列表,确保符合现场。重刷列表是比较费时的操作,故此,AOSP将Recents设计启动前先做预处理,从server一侧获取列表并作大致过滤,然后启动recentsActivity。

多任务界面的启动通过PhoneStatusBar.showRecentApps方法,在向AMS发起启动activity请求前,会先preload,一个典型的多任务界面启动时调用栈如下:

at com.android.systemui.recents.model.RecentsTaskLoadPlan.preloadRawTasks(RecentsTaskLoadPlan.java:125)
at com.android.systemui.recents.model.RecentsTaskLoadPlan.preloadPlan(RecentsTaskLoadPlan.java:153)
at com.android.systemui.recents.model.RecentsTaskLoader.preloadTasks(RecentsTaskLoader.java:384)
at com.android.systemui.recents.RecentsImpl.startRecentsActivity(RecentsImpl.java:924)
at com.android.systemui.recents.RecentsImpl.showRecents(RecentsImpl.java:316)
at com.android.systemui.recents.Recents.showRecents(Recents.java:308)

通过以下两层方法从AMS获得rawTasks列表SystemServiceProxy#getRecentTasks() -> AMS#getRecentTasks()。
这是最先的两层过滤,AMS一侧的方法用来获取最近符合要求应用列表,而SSP的方法是在调用前者后再根据多任务另外设置的黑名单再过滤一遍。

3.1.1 第一层过滤

AMS#getRecentTasks()

 @Override
public ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
int userId) {
final int callingUid = Binder.getCallingUid();
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
false, ALLOW_FULL_ONLY, "getRecentTasks", null); final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;
final boolean withExcluded = (flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0;
synchronized (this) {
final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(),
callingUid);
final boolean detailed = checkCallingPermission(
android.Manifest.permission.GET_DETAILED_TASKS)
== PackageManager.PERMISSION_GRANTED; if (!isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) {
Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
return ParceledListSlice.emptyList();
}
mRecentTasks.loadUserRecentsLocked(userId); final int recentsCount = mRecentTasks.size();
ArrayList<ActivityManager.RecentTaskInfo> res =
new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount); final Set<Integer> includedUsers;
if (includeProfiles) {
includedUsers = mUserController.getProfileIds(userId);
} else {
includedUsers = new HashSet<>();
}
includedUsers.add(Integer.valueOf(userId)); for (int i = 0; i < recentsCount && maxNum > 0; i++) {
TaskRecord tr = mRecentTasks.get(i);
// Only add calling user or related users recent tasks
if (!includedUsers.contains(Integer.valueOf(tr.userId))) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr);
continue;//不属于该用户组的跳过
} if (tr.realActivitySuspended) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr);
continue;
} // Return the entry if desired by the caller. We always return
// the first entry, because callers always expect this to be the
// foreground app. We may filter others if the caller has
// not supplied RECENT_WITH_EXCLUDED and there is some reason
// we should exclude the entry. if (i == 0
|| withExcluded
|| (tr.intent == null)
|| ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
== 0)) {
if (!allowed) {
// If the caller doesn't have the GET_TASKS permission, then only
// allow them to see a small subset of tasks -- their own and home.
if (!tr.isHomeTask() && tr.effectiveUid != callingUid) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr);
continue;//没有GET_TASKS权限的不能获取其它应用的列表
}
} if ((flags & ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS) != 0) {
if (tr.stack != null && tr.stack.isHomeStack()) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"Skipping, home stack task: " + tr);
continue;
}
}
if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK) != 0) {
final ActivityStack stack = tr.stack;
if (stack != null && stack.isDockedStack() && stack.topTask() == tr) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"Skipping, top task in docked stack: " + tr);
continue;//原生逻辑,在A/r状态下,下屏miniRecents中不会有上屏应用的快照
}
}
if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) {
if (tr.stack != null && tr.stack.isPinnedStack()) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"Skipping, pinned stack task: " + tr);
continue;
}
}
if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
// Don't include auto remove tasks that are finished or finishing.
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"Skipping, auto-remove without activity: " + tr);
continue;//autoRemoveRecents的应用在销毁后会从mRecentsTasks列表中删除,这种情况只是还没来得及删除,但也要过滤掉
}
if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0
&& !tr.isAvailable) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"Skipping, unavail real act: " + tr);
continue;
} if (!tr.mUserSetupComplete) {
// Don't include task launched while user is not done setting-up.
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"Skipping, user setup not complete: " + tr);
String record = tr.toString();
if(record.contains(QQ_NAME) || record.contains(WEIBO_NAME) || record.contains(WECHAT_NAME)){
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,"not skip for dualapp" + tr);
}else{
continue;
}
} ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr);
if (!detailed) {
rti.baseIntent.replaceExtras((Bundle)null);
} res.add(rti);
maxNum--;
}
}
return new ParceledListSlice<>(res);
}
}

这个方法看起来比较长,但逻辑其实很简单。

首先从入参看,maxNum是所需的列表长度,满足数量即返回。flag是过滤条件。userId用于过滤掉不属于该应用组的应用。
从35行开始,遍历任务历史记录mRecentTasks,根据方法入参携带的flag做相应过滤,不符合要求的跳过,符合要求的则增加到结果列表,直到结果数目符合要求,结束遍历返回结果。

54~60行的逻辑主要针对EXCLUDE_FROM_RECENTS这个标记位。EXCLUDE_FROM_RECENTS,顾名思义,在Recents中不显示,多任务获取列表时,flag不会带有RECENT_WITH_EXCLUDED标识,withExcluded为false,此时应用如果设置了EXCLUDE_FROM_RECENTS就会被跳过不作为结果返回,不过有个特例,表头的应用不受此限制,就是说,从应用A进入多任务仍会有A的快照,也正因此,在SSP中需要另外增加黑名单逻辑对一些特殊的应用再做一次过滤。

60行之后的代码是针对flag中每一个过滤需求跳过相应task。

67~99行是在处理分屏问题过程中我们增加的一些过滤机制,相对应的也增加了各种flag。之所以在这里加过滤机制,是因为许多地方要判断当前后台运行的应用是否支持分屏之类的,这就一定要通过getRecent获取最近任务列表,而多任务的列表有其自己一套过滤机制,且与后台并不完全同步,不能直接拿来用,因此我们只好模仿着增加特别分屏需要的过滤。

从AMS一侧获取列表后,还要继续在ssp的方法中筛掉黑名单里的应用。

3.1.2 第二层过滤

SystemServiceProxy#getRecentTasks()

 public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
...... // Remove home/recents/excluded tasks
int minNumTasksToQuery = 10;
int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
int flags = ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
//ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
ActivityManager.RECENT_IGNORE_UNAVAILABLE |
ActivityManager.RECENT_INCLUDE_PROFILES;
if(mIsInMultiWindowMode == true) {
flags |= ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK;
}
if (includeFrontMostExcludedTask) {
flags |= ActivityManager.RECENT_WITH_EXCLUDED;
}
List<ActivityManager.RecentTaskInfo> tasks = null;
try {
tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId);
} catch (Exception e) {
Log.e(TAG, "Failed to get recent tasks", e);
} // Break early if we can't get a valid set of tasks
if (tasks == null) {
return new ArrayList<>();
} boolean isFirstValidTask = true;
Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
while (iter.hasNext()) {
ActivityManager.RecentTaskInfo t = iter.next(); // NOTE: The order of these checks happens in the expected order of the traversal of the
// tasks // Remove the task if it or it's package are blacklsited
if (sRecentsBlacklist.contains(t.realActivity.getClassName()) ||
sRecentsBlacklist.contains(t.realActivity.getPackageName())) {
iter.remove();
continue;
}
......
} return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));
}

8~12行是多任务从AMS获取列表的默认flag。

上述代码中的21行从AMS获得列表,然后在40~44行里,将黑名单中的去掉。

上文提到,某些特例下,设置了从多任务排除掉的应用仍会在多任务显示,像这种无论如何都不希望在多任务显示的应用,可以在此处加入黑名单,就能够确保从列表中去掉了。

除了这些基本的过滤,Recents还有进行自己的一套过滤,比如说丢弃掉已经太久没有激活过的应用。

3.1.3 第三层过滤

在RecentsTaskLoadPlan#preloadPlan中,上述的preloadRawTasks()执行完后,还会遍历得到的mRawTasks做深一步的预处理和过滤。
其中有个判定项isStackTask将会在后面用作过滤。

 boolean isStackTask = isFreeformTask || !isHistoricalTask(t) ||
(t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS)); /**
* Returns whether this task is too old to be shown.
*/
private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME /* 6h */);
}

如上代码,如果应用有太久没有使用,isHistoricalTask将会为true,isStackTask将可能为false(后面一个条件具体解释起来比较复杂,有兴趣的可以继续阅读源码相关部分思考其用处)。

在随后的处理中,mRawTasks会继续被处理成FilteredTaskList:mStackTaskList,并根据acceptTask()接口返回的值决定是否保留在FilterdTaskList。

at com.android.systemui.recents.model.TaskStack$2.acceptTask(TaskStack.java:608)
at com.android.systemui.recents.model.FilteredTaskList.updateFilteredTasks(TaskStack.java:204)
at com.android.systemui.recents.model.FilteredTaskList.set(TaskStack.java:159)
at com.android.systemui.recents.model.TaskStack.setTasks(TaskStack.java:851)
at com.android.systemui.recents.model.RecentsTaskLoadPlan.preloadPlan(RecentsTaskLoadPlan.java:228)
at com.android.systemui.recents.model.RecentsTaskLoader.preloadTasks(RecentsTaskLoader.java:384)

 public TaskStack() {
// Ensure that we only show non-docked tasks
mStackTaskList.setFilter(new TaskFilter() {
@Override
public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
if (t.isAffiliatedTask()) {
// If this task is affiliated with another parent in the stack, then the
// historical state of this task depends on the state of the parent task
Task parentTask = taskIdMap.get(t.affiliationTaskId);
if (parentTask != null) {
t = parentTask;
}
}
}
return t.isStackTask;
}
});
}

如上代码中16行,如果之前计算得出的isStackTask为false,那就会被过滤掉。mStackTaskList才是最后在多任务中被拿来用的任务列表。

3.2 应用如何设定自己不在多任务中显示

首先要增加EXCLUDE_FROM_RECENTS属性,具体来说,在模块manifest中的里增加如下代码

<activity
android:name="XYZ"
android:excludeFromRecents="true">

但原生逻辑下,从应用直接进入多任务的时候,及时加了exclude属性,当前应用的快照也会保留,如果这种情况也不希望显示。那么需要将自己加入多任务黑名单。
SystemServiceProxy.sRecentsBlacklist:

 final static List<String> sRecentsBlacklist;
static {
sRecentsBlacklist = new ArrayList<>();
sRecentsBlacklist.add("com.android.systemui.tv.pip.PipOnboardingActivity");
sRecentsBlacklist.add("com.android.systemui.tv.pip.PipMenuActivity");
}

上面是我们ROM里当前的黑名单,头两个是原生就有的,后面是针对JUI系统界面需求所增加的,像全局搜索、bigbang这类的,对用户算是系统界面的一部分,但实际上却是通过app实现的应用适合加入黑名单。

3.3 如何获取后台应用列表而不是历史记录

前面一直讲的getRecentTasks()获取的列表包含了已经处于destoryed状态的tasks,如果只想要后台运行应用的列表,可以使用mAm.getRunningTasks(maxNum)方法,这个方法会调用到AMS#getTasks():

 @Override
public List<RunningTaskInfo> getTasks(int maxNum, int flags) {
final int callingUid = Binder.getCallingUid();
ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();
synchronized(this) {
final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(),
callingUid); // TODO: Improve with MRU list from all ActivityStacks.
mStackSupervisor.getTasksLocked(maxNum, list, callingUid, allowed);
}
return list;
}

StackSuperVisor#getTasksLocked()方法会深搜遍历activity任务栈,然后截取所需数目的列表并返回。
不过mAm.getRunningTasks()这个方法已经是@Deprecated的了。
我们看到第9行有个TODO,但这个已经好几年没有变化了,大概是RecentTasks已经基本够用了。

3.2 mRecentTasks中的taskRecord重回任务栈舞台

从多任务点击快照与一般启动应用的方式不一样。一般从Launcher启动或是应用间跳转都是借助Intent,在新建task之前,会遍历任务栈中的应用看是否有intent相同的task并复用之。
而从多任务启动应用,却与intent无关,是直接使用taskId的:
ASS#startActivityFromRecentsInner

 final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {
......
task = anyTaskForIdLocked(taskId, RESTORE_FROM_RECENTS, launchStackId);
if (task == null) {
continueUpdateBounds(HOME_STACK_ID);
mWindowManager.executeAppTransition();
throw new IllegalArgumentException(
"startActivityFromRecentsInner: Task " + taskId + " not found.");
}
......
}

ASS#anyTaskForIdLocked

 TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents, int stackId) {
int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
ActivityStack stack = stacks.get(stackNdx);
TaskRecord task = stack.taskForIdLocked(id);
if (task != null) {
return task;
}
}
} // Don't give up! Look in recents.//如果任务栈中没有,尝试在RecentTasks中搜索
if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents");
TaskRecord task = mRecentTasks.taskForIdLocked(id);
if (task == null) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "\tDidn't find task id=" + id + " in recents");
return null;
} if (!restoreFromRecents) {
return task;
} if (!restoreRecentTaskLocked(task, stackId)) {
if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
"Couldn't restore task id=" + id + " found in recents");
return null;
}
if (DEBUG_RECENTS) Slog.w(TAG_RECENTS, "Restored task id=" + id + " from in recents");
return task;
}

由于多任务中显示的是最近任务列表,对用户来说,更是所谓在后台运行的应用,正常情况通过taskid是一定能找到一个可重用的taskrecord的。在anyTaskForIdLocked()中,首先遍历任务栈寻找相同taskid应用,如果找不到则在RecentTasks中继续找,找到后通过restoreRecentTaskLocked将taskRecord重新加入合适的ActivityStack中去。这样,本已被销毁的应用从RecentTasks中被加回任务栈,taskId等信息都不变。
与上面相对的,直接通过intent方式启动activity时,虽然也会尽可能寻找可重用的task,但却只是从任务栈中遍历寻找intent相同的Task,不会从RecentTasks中再寻找一边。
可以说,一个taskRecord实例的唯一标识是taskId,而一个应用task的唯一标识是intent。

Android:Recents和AMS中历史任务的区别的更多相关文章

  1. [Android] Service和IntentService中显示Toast的区别

    1. 表象     Service中可以正常显示Toast,IntentService中不能正常显示Toast,在2.3系统上,不显示toast,在4.3系统上,toast显示,但是不会消失. 2. ...

  2. [Android]在Dagger 2中使用RxJava来进行异步注入(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客: # 在Dagger 2中使用RxJava来进行异步注入 > 原文: 几星期前我写了一篇关于在Dagger 2中使用*Producers*进行 ...

  3. [Android] 怎么在应用中实现密码隐藏?

    [Android] 怎么在应用中实现密码隐藏? 在安卓应用中,用户注册或者登录时,需要把密码隐藏,实现一定的保密效果.在安卓中,可以通过设置EditText组件的TransformationMetho ...

  4. Android在layout xml中使用include

    Android include与merge标签使用详解 - shuqiaoniu的博客 - 博客频道 - CSDN.NEThttp://blog.csdn.net/shuqiaoniu/article ...

  5. Android ListView滑动过程中图片显示重复错乱闪烁问题解决

    最新内容建议直接访问原文:Android ListView滑动过程中图片显示重复错乱闪烁问题解决 主要分析Android ListView滚动过程中图片显示重复.错乱.闪烁的原因及解决方法,顺带提及L ...

  6. android Log 等级以及在Android Studio 的Logcat中过滤方法

    Log等级 等级越高,问题越严重. Log.e(TAG,"级别5,错误信息"); Log.e(TAG,"级别5,错误信息"); Log.w(TAG," ...

  7. android优化从网络中加载图片速度。。

    从网络中加载图片主要要注意两个方面的问题: 1.内存管理:图片占的内存很大,假如图片数量多,很容易让系统抛出out of memory的异常. 同时我们也要注意不同android版本中内存管理的区别. ...

  8. [Android]在Dagger 2中Activities和Subcomponents的多绑定(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6266442.html 在Dagger 2中Activities ...

  9. 导入android项目在eclipse中会报@Override错误

    很多时候导入android项目在eclipse中会报@Override错误,这是由于java编译器的版本不正确,Java 1.5的编译器默认对父类的方法进行覆盖,采用@Override进行说明:但1. ...

随机推荐

  1. 异常-No suppression parameter found for notification

    1 详细异常 Command Start is not currently available for execution. 关闭 kafka gateway 无法启动 java.lang.NullP ...

  2. git命令——git rm、git mv

    git rm git rm命令官方解释 删除的本质 在git中删除一个文件,本质上是从tracked files中移除对这些文件的跟踪.更具体地说,就是将这些文件从staging area移除.然后c ...

  3. Linux sudo(CVE-2019-14287)漏洞复现过程

    简述: 该漏洞编号是CVE-2019-14287. sudo是Linux系统管理指令,允许用户在不需要切换环境的前提下用其他用户的权限运行程序或命令,通常是以root身份运行命令,以减少root用户的 ...

  4. javascript遍历对象属性

    ":[{"name":"a2"}]}; console.log(Object.keys(obj)); Object.keys(obj).forEach ...

  5. pure::variants — 产品线变体管理工具

    pure::variants 是德国 pure-systems 公司的产品,其目的是帮助企业实现对产品线的变体管理,提高企业项目资产的复用效率.pure::variants 的核心理念是运用产品线管理 ...

  6. python函数式编程-装饰器

    在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator). 由于函数也是一个对象,而且函数对象可以赋值给变量,所以通过变量也能调用该函数. >>> def now() ...

  7. MathJax.js是做什么的

    MathJax.js是做什么的 一.总结 一句话总结: 用 MathJax 可以在浏览器页面很美观的显示数学公式 1.MathJax 语法? $$...$$之间是单行公式,$...$之间是行内公式 实 ...

  8. flask中使用ajax 处理前端请求,结果展示在同一页面,不点击页面不展示

    在同一页面点击按钮,后端处理后展示在同一页面,不点击隐藏该结果:与上一篇大同小异,需要在 html.flask.js微调 效果展示: (未点击查询) (点击查询) html: <html> ...

  9. html学习 菜鸟

    1HTML 不是一种编程语言,而是一种标记语言 2只有 <body>区域才会在浏览器中显示. 3<!DOCTYPE>声明有助于浏览器中正确显示网页.doctype 声明是不区分 ...

  10. mybatis-generator数据库注释实体类生成以及generatorConfig文件配置

    项目里新建表时model,mapper以及mapper.xml基本都是用Mybatis Generator(以下简称为MBG)自动生成的,但是MBG自动生成的model的注释实在有点非人类,至少中国人 ...