文档章节

Android Activity启动流程(基于Android8.0系统)

天王盖地虎626
 天王盖地虎626
发布于 06/11 08:32
字数 2572
阅读 25
收藏 0

主要对象介绍

  • ActivityManagerService:负责系统中所有Activity的生命周期;
  • ActivityThread:App的真正入口,当App启动后,会调用其main方法开始执行,开启消息循环队列。是传说中的UI线程,即主线程。与ActivityManagerService配合,一起完成Activity的管理工作;
  • ApplicationThread:用来实现ActivityManagerService与ActivityThread之间的交互。在ActivityManagerService需要管理相关Application中的Activity的生命周期,通过ApplicationThread的代理对象与ActivityThread通讯;
  • ApplictationThreadProxy:是ApplicationThread在服务端的代理对象,负责和客户端的ApplicationThread通讯。AMS就是通过这个代理对象与ActivityThread进行通信的,Android 8.0上以删除该类,采用AIDL接口的方式来进行IPC,实现RPC操作
  • Instrumentation:每一个应用程序都只有一个Instrumentation对象,每个Activity内都有一个对该对象的引用。Instrumentation可以理解为应用进程的管家,ActivityThread要创建或者打开某个Activity时,都需要通过Instrumentation来进行具体的操作;
  • ActvitityStack:Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系、状态信息等。通过ActivityStack决定是否需要启动新的进程;
  • ActivityRecord:ActivityStatck的管理对象,每个Activity在AMS对应的一个ActivityRecord,来记录Activity的状态以及其他信息。可以理解为Activity在服务端的Activity对象的映射;
  • ActivityClientRecord:与ActivityRecord是在服务端(AMS)的记录相对应,是Activity在客户端(ActivityThread)的记录;
  • TaskRecord:AMS抽象出来的任务栈的概念。一个TaskRecord包含若干个ActivityRecord。ASM用它来确保Activity启动和退出顺序。它与Activity的启动模式直接相关。
  • ActivityStarter:启动Activity的控制器,主要用于用来将Intent和flags转换成activity和相关任务栈;
  • ActivityStackSupervisor:主要管理着mHomeStack和mFocusedStack两个ActivityStack等相关信息;

Binder通信

  • 在Android 8.0以前,Binder通信的流程如下:
    客户端: ActivityManagerProxy -> Binder驱动 -> 服务端:ActivityManagerService
    服务端: ApplicationThreadProxy -> Binder驱动 -> 客户端:ApplicationThread
  • Android8.0开始,删除了ActivityManagerNative,AMS的继承类也发生了变化,继承了IActivityManager.Stub接口类。了解Android的Binder机制应该知道,这里IActivityManager.aidl生成的接口类。Android8.0开始,把一些Binder代码转化为了AIDL模板方式:
public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback

ActivityManager中的代码:

    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };
  • 而在Android7.0及其以前的版本上,则是客户端通过ActivityManaagerProxy与服务端的ActivityManager进行通信的
    ActivityManagerNative:
**
*获得IActivityManager类
*/
static public IActivityManager getDefault() {
        return gDefault.get();
    }

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);

}

ActivityManager:

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;
        }
};

可以看出来,两个写法不一样,本质上都是一样的,Android 8.0可能使用了AIDL方式进行ipc。

  • 同理,ApplicationThread同上述一样做了更改:
 private class ApplicationThread extends IApplicationThread.Stub {
    ...
 }

所以在Android 8.0上不存在ActivityManagerProxy和ApplicationThreadProxy,而是采用了AIDL接口的方式来进行通信的。

源码调用栈(基于Android 8.0)

按照自上而下的调用栈的顺序进行调用,以类文件为单位来计步,方便大家去了解调用流程,主要涉及的几个源文件:Instumentation,ActivityMangerService,ActivityStack,ActivityStarter,ActivityStackSupervisor,ActivityThread,Activity等。

  1. Activity
public void startActivity(Intent intent)
    
public void startActivity(Intent intent, @Nullable Bundle options)

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options)
  1. Intrumentation
public ActivityResult execStartActivity(
    Context who, IBinder contextThread, IBinder token, Activity target,
    Intent intent, int requestCode, Bundle options){
    ...
    int result = ActivityManager.getService()
        .startActivity(whoThread, who.getBasePackageName(), intent,
            intent.resolveTypeIfNeeded(who.getContentResolver()),
            token, target != null ? target.mEmbeddedID : null,
            requestCode, 0, null, options);
    ...
}
  1. ActivityManagerService
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());
}
     
            
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) {
    ...
    return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                profilerInfo, null, null, bOptions, false, userId, null, "startActivityAsUser");
}
  1. 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, WaitResult outResult,
    Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, 
    int userId,askRecord inTask, String reason) { 
    ...
    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);
    ...
}
        

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) {
    ...
    mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType,
    aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,
    callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
    options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord,
            inTask);
    ...
}
            
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) {
    ...
    doPendingActivityLaunchesLocked(false);
    return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true,
    options, inTask, outActivity);
}
            

private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
    IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
    int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
    ActivityRecord[] outActivity) {
    ...
    result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
        startFlags, doResume, options, inTask, outActivity);
    ...
}  
            
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
    IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
    int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
    ActivityRecord[] outActivity) {
    ...
    if (mDoResume) {
        mSupervisor.resumeFocusedStackTopActivityLocked();
    }
    ...
}
  1. ActivityStackSupervisor
boolean resumeFocusedStackTopActivityLocked() {
    return resumeFocusedStackTopActivityLocked(null, null, null);
}
    
boolean resumeFocusedStackTopActivityLocked(
    ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
    ...
    if (r == null || r.state != RESUMED) {
        mFocusedStack.resumeTopActivityUncheckedLocked(null, null);
    } 
    ...
}
  1. ActivityStack
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
    ...
    result = resumeTopActivityInnerLocked(prev, options);
    ...
}

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { 
   ....
    if (mResumedActivity != null) {
        //同步等待pause当前Activity的结果
        pausing |= startPausingLocked(userLeaving, false, next, false);
    }
    ....
      //开始启动下一个Activity
      mStackSupervisor.startSpecificActivityLocked(next, true, false);
    ....
}


final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming, boolean pauseImmediately) {
    ....
    //去当前Activity所在应用进程暂停当前activity
     prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
            userLeaving, prev.configChangeFlags, pauseImmediately);
    ....
}
       
  1. ActivityThread$ApplicationThread
public final void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, boolean dontReport) {
    ...
    sendMessage(finished ? H.PAUSE_ACTIVITY_FINISHING: H.PAUSE_ACTIVITY, token,
    (userLeaving ? USER_LEAVING: 0) | (dontReport ? DONT_REPORT: 0), configChanges, seq);
    ...
}
  1. ActivityThread
private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) {
    Message msg = Message.obtain();
    ....
    mH.sendMessage(msg);
}
  1. ActivityThread$H
public void handleMessage(Message msg) {
   ...
    switch (msg.what) {
    case PAUSE_ACTIVITY:
        {
            SomeArgs args = (SomeArgs) msg.obj;
            handlePauseActivity((IBinder) args.arg1, false, (args.argi1 & USER_LEAVING) != 0,
            args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
        }
        break;
    }
    ...
}
  1. ActivityThread
private void handlePauseActivity(IBinder token, boolean finished, 
    boolean userLeaving, int configChanges, boolean dontReport, int seq) {
    ...
    performPauseActivity(token, finished, r.isPreHoneycomb(), "handlePauseActivity");
    ...
    //执行完后通知AMS当前Activity已经pause
    ActivityManager.getService().activityPaused(token);
    ...
}

final Bundle performPauseActivity(ActivityClientRecord r, boolean finished,
     boolean saveState, String reason) {
         ...
         performPauseActivityIfNeeded(r, reason);
         ...
}


private void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) {
    ...
     mInstrumentation.callActivityOnPause(r.activity);
    ...
}
  1. Instrumentation
public void callActivityOnPause(Activity activity) {
    activity.performPause();
}
  1. Activity
final void performPause() {
    ...
    onPause();
    ...
}

由于在ActivityThread中handlePauseActivity的方法里,在pause成功后,需要通知AMS已经pause成功,所以接着分析ActivityManagerService.activityPaused方法。

  1. ActivityManagerService
public final void activityPaused(IBinder token) {
    final long origId = Binder.clearCallingIdentity();
    synchronized(this) {
        ActivityStack stack = ActivityRecord.getStackLocked(token);
        if (stack != null) {
            stack.activityPausedLocked(token, false);
        }
    }
    Binder.restoreCallingIdentity(origId);
}
  1. ActivityStack
final void activityPausedLocked(IBinder token, boolean timeout) {
    ...
    mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
    ...
    completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
    ...
}

private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
    ...
    mStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null);
    ...
}
  1. ActivityStackSupervisor
boolean resumeFocusedStackTopActivityLocked(ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
    //如果启动Activity和要启动的Activity在同一个ActivityStack中,调用targetStack对象的方法
    if (targetStack != null && isFocusedStack(targetStack)) {
        return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
    }
    //如果不在同一个ActivityStack中,则调用mFocusStack对象的方法
    if (r == null || r.state != RESUMED) {
        mFocusedStack.resumeTopActivityUncheckedLocked(null, null);
    }

    return false;
}
  1. ActivityStatck
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
    ...
    result = resumeTopActivityInnerLocked(prev, options);
    ...
}

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { 
   ...
    if (mResumedActivity != null) {
        //同步等待pause当前Activity的结果,但在pause时,已经把mResumedActivity置为了null,所以不走这里
        pausing |= startPausingLocked(userLeaving, false, next, false);
        ...
    }
    ...
    return true;
    ...
      //开始启动下一个Activity
      mStackSupervisor.startSpecificActivityLocked(next, true, false);
    ...
}
  1. ActivityStackSupervisor
void startSpecificActivityLocked(ActivityRecord r,
    boolean andResume, boolean checkConfig) { 
    if (app != null && app.thread != null) { 
        ...
        realStartActivityLocked(r, app, andResume, checkConfig);
        return;
    }
    ...
    //启动跨进程的Activity需要先开启新的应用进程
    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);
    ...
}
  1. ActivityThread$ApplicationThread
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
    ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
    CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
    int procState, Bundle state, PersistableBundle persistentState,
    List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
    boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
    ...
    sendMessage(H.LAUNCH_ACTIVITY, r);
}
  1. ActivityThread
private void sendMessage(int what, Object obj) {
    sendMessage(what, obj, 0, 0, false);
}
 
 
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
    ...
    mH.sendMessage(msg);
} 
 
  1. ActivityThread$H
public void handleMessage(Message msg) {
    ...
    switch (msg.what) {
    case LAUNCH_ACTIVITY:
        {
            ....
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
            ....
        }
        break;
        ...
    }
}
  1. ActivityThread
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    Activity a = performLaunchActivity(r, customIntent);
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent){ 
    ...
    if (r.isPersistable()) {
        mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    } else {
        mInstrumentation.callActivityOnCreate(activity, r.state);
    }
    ...
}
  1. Instumentation
public void callActivityOnCreate(Activity activity, Bundle icicle) {
    prePerformCreate(activity);
    activity.performCreate(icicle);
    postPerformCreate(activity);
}
  1. Activity
final void performCreate(Bundle icicle) {
    performCreate(icicle, null);
}

final void performCreate(Bundle icicle, PersistableBundle persistentState) {
    ...
    if (persistentState != null) {
        onCreate(icicle, persistentState);
    } else {
        onCreate(icicle);
    }
   ...
}

Activity启动流程

为了叙述方便,我们假设Activity A要启动Activity B。

  1. Activity A向AMS发送一个启动Activity B的进程间通信请求;
  2. AMS会将要启动的Activity B的组件信息保存下来,然后通过Binder通信(ApplicationThread及其接口定义语言),让Activity A执行pause操作;
  3. Activity B完成pause操作后,通过Binder通信(ActivityManagerService及其接口定义语言)通知AMS,可以执行启动Activity B的操作了(要启动的activity信息保存在了栈顶);
  4. 在启动之前,如果发现Activity B的应用程序进程不存在,会先启动一个新的进程(上述调用栈没涉及,同学们可自行查看源码);
  5. AMS执行一系列启动Activity B的操作,并通过Binder通信(ApplicationThread及其接口定义语言)进行跨进程调用,将Activity B启动起来;

关于AMS

  • AMS的启动是在SystemServer(系统进程)中启动的,同其他Android应用一样,也是由Zygote进程fork出来的:
public final class SystemServer {

    /**
     * The main entry point from zygote.
     * 注释写的很清楚了,SystemServer进程是有Zygote进程fork出来的
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }    
    
    private void run() {
        ...
         startBootstrapServices();
        ...
    }
    
    private void startBootstrapServices() {
        ...
        // Activity manager runs the show.
        traceBeginAndSlog("StartActivityManager");
        //开启ActivityManagerService进程
        mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
        mActivityManagerService.setInstaller(installer);
        traceEnd();        
        ...
    }    
}
  • AMS与APP进程的通信:
  1. APP进程通过IActivityManager.aidl接口向AMS进程进行通信。
    ActivityManager.getService()获得AMS的Binder接口,再通过Stub.asInterface的方式,转成IActivityManager的接口,通过IActivityManager与AMS进行通信,实现RPC远程跨进程调用;
    Instrumentation
int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target, requestCode, 0, null, options);

ActivityManager

public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
}
 
private static final Singleton<IActivityManager> IActivityManagerSingleton =
    new Singleton<IActivityManager>() {
        @Override
        protected IActivityManager create() {
            final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
            final IActivityManager am = IActivityManager.Stub.asInterface(b);
            return am;
        }
};

  1. AMS进程通过IApplication.aidl接口向APP进程进行通信。
    在AMS内部持有每个ActivityThread的IApplicatinThread接口实例,用时可以直接调用。同IActivityManager,也是通过Binder进行进程间通信。

 

本文转载自:https://www.jianshu.com/p/6719238ae5f0

天王盖地虎626

天王盖地虎626

粉丝 31
博文 522
码字总数 20708
作品 0
南京
私信 提问
Android系统架构-----Android的系统体系架构

一、Android的系统体系结构 在入门了一个简单的Android的Hello World以后,我们首先来看一下我们Android的整体系统架构图: 这个就是我们Android的整体系统架构图了,我们首先从整体上来看看...

天王盖地虎626
01/11
16
0
Android进阶(四):Activity启动过程(最详细&最简单)

1.前言 最近一直在看 《Android进阶解密》 的一本书,这本书编写逻辑、流程都非常好,而且很容易看懂,非常推荐大家去看看(没有收广告费,单纯觉得作者写的很好)。 上一篇简单的介绍了And...

天王盖地虎626
06/20
7
0
(Android 9.0)Activity启动流程源码分析

前言 熟悉Activity的启动流程和运行原理是一个合格的应用开发人员所应该具备的基本素质,其重要程度就不多做描述了。同时,知识栈应该不断的更新,最新发布的Android 9.0版本相较于之前的几个...

天王盖地虎626
06/21
28
0
Android插件化快速入门与实例解析(VirtualApk)必须了解一下

 集成一个第三方相册功能,只需集成一个插件APK到项目中,无需集成额外代码,并且支持随时更新相册功能,无需发布版本更新,无需AndroidManifest中声明四大组件,这就是插件化。   插件化...

android自学
2018/07/22
0
0
Android 面试必备 - 系统、App、Activity 启动过程

前言 最近准备更新 Android 面试必备基础知识系列,有兴趣的可以关注我的微信公众号 stormjun94,有更新时,第一时间会在微信公众号上面发布,同时,也会同步在 GitHub 上面更新,如果觉得对...

stormjun94
08/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

64.监控平台介绍 安装zabbix 忘记admin密码

19.1 Linux监控平台介绍 19.2 zabbix监控介绍 19.3/19.4/19.6 安装zabbix 19.5 忘记Admin密码如何做 19.1 Linux监控平台介绍: 常见开源监控软件 ~1.cacti、nagios、zabbix、smokeping、ope...

oschina130111
昨天
64
0
当餐饮遇上大数据,嗯真香!

之前去开了一场会,主题是「餐饮领袖新零售峰会」。认真听完了餐饮前辈和新秀们的分享,觉得获益匪浅,把脑子里的核心纪要整理了一下,今天和大家做一个简单的分享,欢迎感兴趣的小伙伴一起交...

数澜科技
昨天
26
0
DNS-over-HTTPS 的下一代是 DNS ON BLOCKCHAIN

本文作者:PETER LAI ,是 Diode 的区块链工程师。在进入软件开发领域之前,他主要是在做工商管理相关工作。Peter Lai 也是一位活跃的开源贡献者。目前,他正在与 Diode 团队一起开发基于区块...

红薯
昨天
43
0
CC攻击带来的危害我们该如何防御?

随着网络的发展带给我们很多的便利,但是同时也带给我们一些网站安全问题,网络攻击就是常见的网站安全问题。其中作为站长最常见的就是CC攻击,CC攻击是网络攻击方式的一种,是一种比较常见的...

云漫网络Ruan
昨天
27
0
实验分析性专业硕士提纲撰写要点

为什么您需要研究论文的提纲? 首先当您进行研究时,您需要聚集许多信息和想法,研究论文提纲可以较好地组织你的想法, 了解您研究资料的流畅度和程度。确保你写作时不会错过任何重要资料以此...

论文辅导员
昨天
44
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部