Activity启动过程剖析

你同样可以在Github上看到这篇文章:https://github.com/onlynight/ActivityStartPrinciple

写在前面

在看这篇文章之前你需要了解android的IPC通信机制里面的ADIL的原理,还有一些常用的设计模式比如代理模式你也需要有所了解,了解这些会让你更容易理解android源码。阅读本文的读者我都默认你是了解这些东西的,如果你正好还没没有看过这些东西,那么我想你推荐我的另外几篇文章,希望能让你快速了解android的IPC的AIDL的设计原理:

  • AIDL使用及原理分析CSDN
  • AIDL使用及原理分析Github
  • 代理模式初探CSDN
  • Proxy_Github

下面我们开始正片。

阅读源码的方法

Android源码数量相当多我们不能从头逐一查看所有文件中的源码,你需要理出一条线索来,你感兴趣的线索比如本文讲的Activity的启动过程分析就是从startActivity函数开始然后逐步深入去查看其中的奥妙。还有一点需要说的是看源码的时候不要太深入细节,我们想了解的代码的逻辑而不是其中如何实现的(当然这个是我看源码的主要目的,如果你的出发点是如何实现的那我们关注的重点就不一样了),梳理代码逻辑让我们能更好的了解android的工作原理,从而在遇到问题的时候不再抓虾而能精准的定位到问题的所在,希望这篇文章能够帮到你。这里同样有一篇文章推荐给你:Android源码阅读配置

需要了解的几个类

开始之前你需要对几个类有所了解知道他们的职责这样有助于理解源码:

1.Activity

An activity is a single, focused thing that the user can do. Almost all
activities interact with the user, so the Activity class takes care of
creating a window for you in which you can place your UI with
{@link #setContentView}. While activities are often presented to the user
as full-screen windows, they can also be used in other ways: as floating
windows (via a theme with {@link android.R.attr#windowIsFloating} set)
or embedded inside of another activity (using {@link ActivityGroup}).

Activity是我们日常中最常用的组件,它通常显示给用户的是一个全屏的窗口,或者一个浮动的窗口(例如音乐播放器的浮动歌词),或者嵌入其他的Activity使用ActivityGroup。它Android四大组件中唯一能和用户交互的组件,这里就不再多做解释了。

2.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.

当Instrumentation打开的时候,在你的应用代码执行之前会首先实例化一个instrumentation,你可以监听所有系统应用交互。
用于执行具体操作的类,辅助Activity的监控和测试。

3.ActivityManagerNative、ActivityManagerProxy、IActivityManager、ActivityManagerService

IPC需要的类,了解过aidl的童鞋应该都不陌生。需要有一个继承IInterface的接口定义ipc的能力接口,需要有个继承Binder类的驱动类,还需要有一个远程服务的代理类Proxy代理远程服务的操作,实现Binder的类可能是个抽象类,还需要有类实例化这个Binder或者继承它实现所有抽象方法。

  1. IActivityManager就是继承了IInterface的能力接口。
  2. ActivityManagerNative继承了Binder实现了IInterface接口。
  3. ActivityManagerProxy有一个mRemote其类型是Binder,远程服务代理。
  4. ActivityManagerService继承了ActivityManagerNative并且实现了它的所有抽象方法。

类结构图如下:

源码逻辑梳理

首先我们来看startActivity这个函数:

// Activity.javapublic void startActivity(Intent intent) {this.startActivity(intent, null);
}public void startActivity(Intent intent, @Nullable Bundle options) {if (options != null) {startActivityForResult(intent, -1, options);} else {// Note we want to go through this call for compatibility with// applications that may have overridden the method.startActivityForResult(intent, -1);}
}public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);if (ar != null) {mMainThread.sendActivityResult(mToken, mEmbeddedID, requestCode, ar.getResultCode(),ar.getResultData());}if (requestCode >= 0) {// If this start is requesting a result, we can avoid making// the activity visible until the result is received.  Setting// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the// activity hidden during this time, to avoid flickering.// This can only be done when a result is requested because// that guarantees we will get information back when the// activity is finished, no matter what happens to it.mStartedActivity = true;}cancelInputsAndStartExitTransition(options);// TODO Consider clearing/flushing other event sources and events for child windows.} else {if (options != null) {mParent.startActivityFromChild(this, intent, requestCode, options);} else {// Note we want to go through this method for compatibility with// existing applications that may have overridden it.mParent.startActivityFromChild(this, intent, requestCode);}}
}

可以看到startActivity函数调用的还是startActivityForResult只是传入的默认requestCode为-1,这里就提醒我们requestCode需要大于等于0,否则和直接调用startActivity是一样的效果不会回调onActivityResult

startActivityForResult出现分支判断是否有mParentmParent即为ActivityGroup,实际上两个分支执行的代码是相同的只是是谁启动activity的区别,startActivityFromChild这个函数可以验证这个说法:

public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,int requestCode, @Nullable Bundle options) {Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, child,intent, requestCode, options);if (ar != null) {mMainThread.sendActivityResult(mToken, child.mEmbeddedID, requestCode,ar.getResultCode(), ar.getResultData());}cancelInputsAndStartExitTransition(options);
}

可以看到这个函数的内部实现和startActivityForResultmParent==null的分支代码基本相同只是传入的embeddedID不同,下面我们顺着主线继续往往下看,startActivityForResult中调用了Instrumention.execStartActivity方法:

//Instrumention.javapublic ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target,Intent intent, int requestCode, Bundle options) {IApplicationThread whoThread = (IApplicationThread) contextThread;if (mActivityMonitors != null) {synchronized (mSync) {final int N = mActivityMonitors.size();for (int i=0; i<N; i++) {final ActivityMonitor am = mActivityMonitors.get(i);if (am.match(who, null, intent)) {am.mHits++;if (am.isBlocking()) {return requestCode >= 0 ? am.getResult() : null;}break;}}}}try {intent.migrateExtraStreamToClipData();intent.prepareToLeaveProcess(who);int result = ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token, target, requestCode, 0, null, options);checkStartActivityResult(result, intent);} catch (RemoteException e) {throw new RuntimeException("Failure from system", e);}return null;
}

这里就有一些比较复杂的代码,本着研究的心理总要把代码是如何实现的看个清楚,其实不然,你大可根据变量和方法的命名大概知道是什么作用就可以继续看代码了,例如这个mActivityMonitors我们看到它的类型是List<ActivityMonitor>,通过名字和类型我们就大概知道它是用来记录Activity的,大概分析下这里应该不是真正启动Activity的地方这里应该是请求activity返回的检测。继续往下看,这里调用了ActivityManagerNative.startActivity方法,我们继续深入:

//ActivityManagerNative.javastatic public IActivityManager getDefault() {return gDefault.get();
}private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {protected IActivityManager create() {IBinder b = ServiceManager.getService("activity");if (false) {Log.v("ActivityManager", "default service binder = " + b);}IActivityManager am = asInterface(b);if (false) {Log.v("ActivityManager", "default service = " + am);}return am;}
};static public IActivityManager asInterface(IBinder obj) {if (obj == null) {return null;}IActivityManager in =(IActivityManager)obj.queryLocalInterface(descriptor);if (in != null) {return in;}return new ActivityManagerProxy(obj);
}class ActivityManagerProxy implements IActivityManager
{private IBiner mRemote;public ActivityManagerProxy(IBinder remote){mRemote = remote;}public IBinder asBinder(){return mRemote;}public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,String resolvedType, IBinder resultTo, String resultWho, int requestCode,int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {Parcel data = Parcel.obtain();Parcel reply = Parcel.obtain();data.writeInterfaceToken(IActivityManager.descriptor);data.writeStrongBinder(caller != null ? caller.asBinder() : null);data.writeString(callingPackage);intent.writeToParcel(data, 0);data.writeString(resolvedType);data.writeStrongBinder(resultTo);data.writeString(resultWho);data.writeInt(requestCode);data.writeInt(startFlags);if (profilerInfo != null) {data.writeInt(1);profilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);} else {data.writeInt(0);}if (options != null) {data.writeInt(1);options.writeToParcel(data, 0);} else {data.writeInt(0);}mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);reply.readException();int result = reply.readInt();reply.recycle();data.recycle();return result;}...
}

这里看到ActivityManagerNative.getDefault中的gDefault是一个单例其中的Binder就是我们要的远程Binder了,ServiceManager.getService获取到的Binder,这里我们先放放先不看ServiceManager的内部实现,只要我们知道这个远程Binder是从这里获取到的就可以了。拿到这个Binder后通过ActivityManagerNative.asInterface方法将其强转为IActivityManager接口:

static public IActivityManager asInterface(IBinder obj) {if (obj == null) {return null;}IActivityManager in =(IActivityManager)obj.queryLocalInterface(descriptor);if (in != null) {return in;}return new ActivityManagerProxy(obj);
}

如果这个Binder在本地能找到那么就直接返回,如果找不到就通过Binder创建一个远程服务的代理类。ActivityManagerProxy将传入的IBinder作为远程服务的Binder,可以看到ActivityManagerProxy.startActivity方法的本质实际上是调用了mRemote.transact方法,请求远程服务完成操作。懂AIDL的朋友都知道transactonTransact是成对出现的,onTransact才是真正的实现。那么问题来了这个onTransact实在哪里实现的呢,当然是ActivityManagerProxy.mRemote已经实现了,现在我们再倒回来找找这个mRemote是从哪里来的。

//ServiceManager.java/*** Returns a reference to a service with the given name.* *@param name the name of the service to get*@return a reference to the service, or <code>null</code> if the service doesn't exist*/
public static IBinder getService(String name) {try {IBinder service = sCache.get(name);if (service != null) {return service;} else {return getIServiceManager().getService(name);}} catch (RemoteException e) {Log.e(TAG, "error in getService", e);}return null;
}private static IServiceManager getIServiceManager() {if (sServiceManager != null) {return sServiceManager;}// Find the service managersServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());return sServiceManager;
}

看到这些熟悉的命名方式ServiceManagerIServiceManagerServiceManagerNative,那么这个ServiceManager也是AIDL实现,我们再来看下这个AIDL的远程代理从哪里来的,这里没有gDefault但是有个静态方法getIServiceManager来获取IInterface接口,继续往里深入:

//BinderInternal.java/*** Return the global "context object" of the system.  This is usually* an implementation of IServiceManager, which you can use to find* other services.*/
public static final native IBinder getContextObject();
//ServiceManagerNative.java/*** Native implementation of the service manager.  Most clients will only* care about getDefault() and possibly asInterface().*@hide*/
public abstract class ServiceManagerNative extends Binder implements IServiceManager{}

这里获取IBinder是通过native方式来实现的,这里我们就不再深入了。我们知道ServiceManager是用来获取其他service的接口那么它的实现必然跟普通的service有所不同,再回到我们之前的方法ServiceManager.getService("activity"),中通过activity参数获取到了一个Binder这个Binder必然是已经继承自Binder并且实现了IActivityManager接口的类,这样的类我们现在只能定位到ActivityManagerNative这个类,但是这个类是个抽象类它并没有实现IActivityManager中的方法,我们再全局搜索一下ActivityManagerNative这个类看有没有实例化这个类的地方或者继承这个类的分抽象类,很幸运我们找到了ActivityManagerService这个类继承了ActivityManagerNative并且实现了接口的方法。

ServiceManager中有个sCache其类型是HashMap<String, IBinder>,既然有getService那么会有相应的addService将service加入到其中:

//ServiceManager.javaprivate HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();/*** Returns a reference to a service with the given name.* *@param name the name of the service to get*@return a reference to the service, or <code>null</code> if the service doesn't exist*/
public static IBinder getService(String name) {try {IBinder service = sCache.get(name);if (service != null) {return service;} else {return getIServiceManager().getService(name);}} catch (RemoteException e) {Log.e(TAG, "error in getService", e);}return null;
}/*** Returns a reference to a service with the given name.* *@param name the name of the service to get*@return a reference to the service, or <code>null</code> if the service doesn't exist*/
public static IBinder getService(String name) {try {IBinder service = sCache.get(name);if (service != null) {return service;} else {return getIServiceManager().getService(name);}} catch (RemoteException e) {Log.e(TAG, "error in getService", e);}return null;
}

好了我们再来看看activity这个service是怎么被添加到里面去的,在ActivityManagerService中找到了相应的代码:

// ActivityManagerService.javapublic void setSystemProcess() {try {ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);ServiceManager.addService("meminfo", new MemBinder(this));ServiceManager.addService("gfxinfo", new GraphicsBinder(this));ServiceManager.addService("dbinfo", new DbBinder(this));if (MONITOR_CPU_USAGE) {ServiceManager.addService("cpuinfo", new CpuBinder(this));}ServiceManager.addService("permission", new PermissionController(this));ApplicationInfo info = mContext.getPackageManager().getApplicationInfo("android", STOCK_PM_FLAGS);mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());synchronized (this) {ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0);app.persistent = true;app.pid = MY_PID;app.maxAdj = ProcessList.SYSTEM_ADJ;app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);mProcessNames.put(app.processName, app.uid, app);synchronized (mPidsSelfLocked) {mPidsSelfLocked.put(app.pid, app);}updateLruProcessLocked(app, false, null);updateOomAdjLocked();}} catch (PackageManager.NameNotFoundException e) {throw new RuntimeException("Unable to find android system package", e);}
}

这个setSystemProcess方法在系统启动的时候会被调用,那么ActivityManagerService就被添加到ServiceManager中去了,这里可以看出ActivityManagerService是一个系统级的Service在系统启动的时候就被加载了。

找到来了ActivityManagerNative.gDefault中的源头我们再回过头来理顺Activity的启动过程,我们知道transact后的真正执行者是onTransact方法,那么我们这里的onTransact方法就是ActivityManagerService.onTransact实际就是ActivityManagerNative.onTransact

// ActivityManagerNative.java/** {@hide} */
public abstract class ActivityManagerNative extends Binder implements IActivityManager {@Overridepublic boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws RemoteException {switch (code) {case START_ACTIVITY_TRANSACTION: {data.enforceInterface(IActivityManager.descriptor);IBinder b = data.readStrongBinder();IApplicationThread app = ApplicationThreadNative.asInterface(b);String callingPackage = data.readString();Intent intent = Intent.CREATOR.createFromParcel(data);String resolvedType = data.readString();IBinder resultTo = data.readStrongBinder();String resultWho = data.readString();int requestCode = data.readInt();int startFlags = data.readInt();ProfilerInfo profilerInfo = data.readInt() != 0? ProfilerInfo.CREATOR.createFromParcel(data) : null;Bundle options = data.readInt() != 0? Bundle.CREATOR.createFromParcel(data) : null;int result = startActivity(app, callingPackage, intent, resolvedType,resultTo, resultWho, requestCode, startFlags, profilerInfo, options);reply.writeNoException();reply.writeInt(result);return true;}}}
}class ActivityManagerProxy implements IActivityManager
{public ActivityManagerProxy(IBinder remote){mRemote = remote;}public IBinder asBinder(){return mRemote;}public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,String resolvedType, IBinder resultTo, String resultWho, int requestCode,int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {Parcel data = Parcel.obtain();Parcel reply = Parcel.obtain();data.writeInterfaceToken(IActivityManager.descriptor);data.writeStrongBinder(caller != null ? caller.asBinder() : null);data.writeString(callingPackage);intent.writeToParcel(data, 0);data.writeString(resolvedType);data.writeStrongBinder(resultTo);data.writeString(resultWho);data.writeInt(requestCode);data.writeInt(startFlags);if (profilerInfo != null) {data.writeInt(1);profilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);} else {data.writeInt(0);}if (options != null) {data.writeInt(1);options.writeToParcel(data, 0);} else {data.writeInt(0);}mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);reply.readException();int result = reply.readInt();reply.recycle();data.recycle();return result;}
}

ActivityManagerNative中的mRemote.transact发送了一个START_ACTIVITY_TRANSACTION消息,接着onTransact方法真正将方法最终落地调用了startActivity方法,由于ActivityManagerNative是抽象类,那么就要查看它子类中的这个方法:

//ActivityManagerService.javapublic 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());
}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.return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,profilerInfo, null, null, bOptions, false, userId, null, null);
}

继续深入查看mActivityStarter.startActivityMayWait:

// ActivityStarter.java/*** Controller for interpreting how and then launching activities.** This class collects all the logic for determining how an intent and flags should be turned into* an activity and associated task and stack.*/
class ActivityStarter {...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, IActivityManager.WaitResult outResult, Configuration config,Bundle bOptions, boolean ignoreTargetSecurity, int userId,IActivityContainer iContainer, TaskRecord inTask) {...//化繁为简,省略的这些代码就是为了给startActivityLocked构造参数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, container,inTask);...}final 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, ActivityStackSupervisor.ActivityContainer container,TaskRecord inTask) {...try {mService.mWindowManager.deferSurfaceLayout();err = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, startFlags,true, options, inTask);} finally {mService.mWindowManager.continueSurfaceLayout();}...}private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {...mWindowManager.executeAppTransition();...}...
}

我们就深入到这里就好了不再深入研究代码,mWindowManager的类型是WindowManagerService也是一个aidl的实现,具体实现有兴趣的读者可以深入研究WindowManagerService的内部实现。

本文实际上就是详细讲了ActivityManagerService是如何与前端链接上的,Android中许多其他的服务例如MediaCenter等都是IPC的实现,可以根据本文的的线索和思路查看相应的源码。在梳理完相应的代码逻辑以后就可以关注内部的代码实现了,最后希望本文能够真正帮到你理解Activity的启动过程。

Activity启动过程剖析相关推荐

  1. 干货 | 走进Node.js之启动过程剖析

    走进Node.js之启动过程剖析 作者:正龙 (沪江Web前端开发工程师) 本文原创,转载请注明作者及出处. 随着Node.js的普及,越来越多的开发者使用Node.js来搭建环境,也有很多公司开始把 ...

  2. activity 生命周期_死磕Android_App 启动过程(含 Activity 启动过程)

    1. 前言 Activity是日常开发中最常用的组件,系统给我们做了很多很多的封装,让我们平时用起来特别简单,很顺畅.但是你有没有想过,系统内部是如何启动一个Activity的呢?Activity对象 ...

  3. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )

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

  4. activity启动流程_以AMS视角看Activity启动过程

    原文作者:Levi_wayne 原文地址:blog.csdn.net/u012551754/article/details/78822782 特别声明:本文转载自网络,版权归作者所有,如有侵权请联系删 ...

  5. Android系统(117)---Activity启动过程

    Activity启动过程 ###一些基本的概念 ActivityManagerServices,简称AMS,服务端对象,负责系统中所有Activity的生命周期 ActivityThread,App的 ...

  6. Android深入四大组件(五)Android8.0 根Activity启动过程(后篇)

    前言 在几个月前我写了Android深入四大组件(一)应用程序启动过程(前篇)和Android深入四大组件(一)应用程序启动过程(后篇)这两篇文章,它们都是基于Android 7.0,当我开始阅读An ...

  7. Android世界第一个activity启动过程

    Android世界第一个activity启动过程 第一次使用Markdown,感觉不错. Android系统从按下开机键一直到launcher的出现,是一个如何的过程,中间都做出了什么操作呢.带着这些 ...

  8. 死磕Android_App 启动过程(含 Activity 启动过程)

    1. 前言 Activity是日常开发中最常用的组件,系统给我们做了很多很多的封装,让我们平时用起来特别简单,很顺畅.但是你有没有想过,系统内部是如何启动一个Activity的呢?Activity对象 ...

  9. Activity启动过程详解(Android P)

    本章我们来分析Activity的启动过程. 我们知道,Activity可以通过两种方式启动:一种是点击应用程序图标,Launcher会启动主Activity:另一种是在应用程序内部,调用startAc ...

最新文章

  1. Microbiome:地球上有多大比例的原核生物已经被测序了基因组?
  2. CentOS内核优化提示:cannot stat /proc/sys/net/bridge/bridge-nf-call-ip6tables: 没有那个文件或目录...
  3. ESP8266 D1-UNO-R3开发板的初步测试
  4. 设计模式之【抽象工厂模式】
  5. 命令解释器的设计及实现
  6. Session.Abandon和Session.Clear有何不同
  7. 【经验谈】开发工程师人生之路
  8. C# 8 新提案让泛型 Attribute 成为现实
  9. mysql 操作类 C .net_.NET MYSQL数据库操作基类( C#源码)
  10. 打包jar文件后的spring部署及hibernate自动建表经验总结
  11. linux tcp 阻塞时间,TCP的阻塞和重传机制
  12. Java的对象和类 以学生管理系统为例
  13. 从0开始,设计研发一个全功能通用大数据系统
  14. 2022年华为杯中国研究生数学建模竞赛A题思路
  15. python爬取链家二手房楼盘数据信息
  16. 漫谈程序员系列:别说“我已经很努力了”
  17. pytorch:线性回归实战
  18. 当神话故事邂逅 NFT数字藏品:知名艺术家张宏携《西游》拉开元宇宙序幕
  19. 20210514:廉价机械键盘学习
  20. zip格式压缩文件并打包下载

热门文章

  1. solidworks经典实例网盘下载_Solidworks自学视频教程(附源文件)讲解详细到位,成就设计高手...
  2. 随行付微服务测试之静态代码扫描
  3. zookeeper - watcher(9)
  4. mysql-atlas安装及使用教程
  5. runtime 关联对象objc_setAssociatedObject
  6. #define list_entry(ptr, type, member) \   container_of(ptr, type, member)
  7. [20160910]低级错误.txt
  8. redis的观察者模式----------发布订阅功能
  9. Qt 5.5增加了新的GL模块,并改进了跨平台支持
  10. [转]线程安全 c/c++