Android基础四大组件之Activity的启动过程源码解析

前言

Activity是Android中一个很重要的概念,堪称四大组件之首,关于Activity有很多内容,比如生命周期和启动Flags,这二者想要说清楚,恐怕又要写两篇长文,更何况分析它们的源码呢。不过本文的侧重点不是它们,我要介绍的是一个Activity典型的启动过程,本文会从源码的角度对其进行分析。我们知道,当startActivity被调用的时候,可以启动一个Activity,但是你知道这个Activity是如何被启动的吗?每个Activity也是一个对象,你知道这个对象是啥时候被创建的吗(也就是说它的构造方法是什么时候被调用的)?为什么onCreate是Activity的执行入口?所有的这一切都被系统封装好了,对我们来说是透明的,我们使用的时候仅仅是传递一个intent然后startActivity就可以达到目的了,不过,阅读了本文以后,你将会了解它的背后到底做了哪些事情。在分析之前,我先介绍几个类:

  • Activity:这个大家都熟悉,startActivity方法的真正实现在Activity中
  • Instrumentation:用来辅助Activity完成启动Activity的过程
  • ActivityThread(包含ApplicationThread + ApplicationThreadNative +IApplicationThread):真正启动Activity的实现都在这里

源码分析

首先看入口

code:Activity#startActivity

@Override 
public void startActivity(Intent intent) { 
 startActivity(intent, null); 
} 
 
@Override 
public void startActivity(Intent intent, 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(Intent intent, int requestCode) { 
 startActivityForResult(intent, requestCode, null); 
} 

说明:显然,从上往下,最终都是由startActivityForResult来实现的

接着看

code:Activity#startActivityForResult

public void startActivityForResult(Intent intent, int requestCode, Bundle options) { 
 //一般的Activity其mParent为null,mParent常用在ActivityGroup中,ActivityGroup已废弃 
 if (mParent == null) { 
  //这里会启动新的Activity,核心功能都在mMainThread.getApplicationThread()中完成 
  Instrumentation.ActivityResult ar = 
   mInstrumentation.execStartActivity( 
    this, mMainThread.getApplicationThread(), mToken, this, 
    intent, requestCode, options); 
  if (ar != null) { 
   //发送结果,即onActivityResult会被调用 
   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; 
  } 
 
  final View decor = mWindow != null ? mWindow.peekDecorView() : null; 
  if (decor != null) { 
   decor.cancelPendingInputEvents(); 
  } 
  // TODO Consider clearing/flushing other event sources and events for child windows. 
 } else { 
  //在ActivityGroup内部的Activity调用startActivity的时候会走到这里,内部处理逻辑和上面是类似的 
  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); 
  } 
 } 
} 

说明:上述代码关键点都有注释了,可以发现,真正打开activity的实现在Instrumentation的execStartActivity方法中,去看看

code:Instrumentation#execStartActivity

public ActivityResult execStartActivity( 
  Context who, IBinder contextThread, IBinder token, Activity target, 
  Intent intent, int requestCode, Bundle options) { 
 //核心功能在这个whoThread中完成,其内部scheduleLaunchActivity方法用于完成activity的打开 
 IApplicationThread whoThread = (IApplicationThread) contextThread; 
 if (mActivityMonitors != null) { 
  synchronized (mSync) { 
   //先查找一遍看是否存在这个activity 
   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++; 
     //如果目标activity无法打开,直接return 
     if (am.isBlocking()) { 
      return requestCode  = 0 ? am.getResult() : null; 
     } 
     break; 
    } 
   } 
  } 
 } 
 try { 
  intent.migrateExtraStreamToClipData(); 
  intent.prepareToLeaveProcess(); 
  //这里才是真正打开activity的地方,核心功能在whoThread中完成。 
  int result = ActivityManagerNative.getDefault() 
   .startActivity(whoThread, who.getBasePackageName(), intent, 
     intent.resolveTypeIfNeeded(who.getContentResolver()), 
     token, target != null ? target.mEmbeddedID : null, 
     requestCode, 0, null, null, options); 
  //这个方法是专门抛异常的,它会对结果进行检查,如果无法打开activity, 
  //则抛出诸如ActivityNotFoundException类似的各种异常 
  checkStartActivityResult(result, intent); 
 } catch (RemoteException e) { 
 } 
 return null; 
} 

说明:我想再说一下这个方法checkStartActivityResult,它也专业抛异常的,看代码,相信大家对下面的异常信息不陌生吧,就是它干的,其中最熟悉的非Unable to find explicit activity class莫属了,如果你在xml中没有注册目标activity,此异常将会抛出。

/*package*/ static void checkStartActivityResult(int res, Object intent) { 
 if (res  = ActivityManager.START_SUCCESS) { 
  return; 
 } 
  
 switch (res) { 
  case ActivityManager.START_INTENT_NOT_RESOLVED: 
  case ActivityManager.START_CLASS_NOT_FOUND: 
   if (intent instanceof Intent && ((Intent)intent).getComponent() != null) 
    throw new ActivityNotFoundException( 
      "Unable to find explicit activity class " 
      + ((Intent)intent).getComponent().toShortString() 
      + "; have you declared this activity in your AndroidManifest.xml?"); 
   throw new ActivityNotFoundException( 
     "No Activity found to handle " + intent); 
  case ActivityManager.START_PERMISSION_DENIED: 
   throw new SecurityException("Not allowed to start activity " 
     + intent); 
  case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: 
   throw new AndroidRuntimeException( 
     "FORWARD_RESULT_FLAG used while also requesting a result"); 
  case ActivityManager.START_NOT_ACTIVITY: 
   throw new IllegalArgumentException( 
     "PendingIntent is not an activity"); 
  default: 
   throw new AndroidRuntimeException("Unknown error code " 
     + res + " when starting " + intent); 
 } 
} 

接下来我们要去看看IApplicationThread,因为核心功能由其内部的scheduleLaunchActivity方法来完成,由于IApplicationThread是个接口,所以,我们需要找到它的实现类,我已经帮大家找到了,它就是ActivityThread中的内部类ApplicationThread,看下它的继承关系:

private class ApplicationThread extends ApplicationThreadNative;

public abstract class ApplicationThreadNative extends Binder implements IApplicationThread;

可以发现,ApplicationThread还是间接实现了IApplicationThread接口,先看下这个类的结构

在这里插入图片描述
看完ApplicationThread的大致结构,我们应该能够猜测到,Activity的生命周期中的resume、newIntent、pause、stop等事件都是由它触发的,事实上,的确是这样的。这里,我们为了说明问题,仅仅看scheduleLaunchActivity方法

code:ApplicationThread#scheduleLaunchActivity

public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, 
  ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, 
  int procState, Bundle state, List<ResultInfo  pendingResults, 
  List<Intent  pendingNewIntents, boolean notResumed, boolean isForward, 
  String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { 
 
 updateProcessState(procState, false); 
 
 ActivityClientRecord r = new ActivityClientRecord(); 
 
 r.token = token; 
 r.ident = ident; 
 r.intent = intent; 
 r.activityInfo = info; 
 r.compatInfo = compatInfo; 
 r.state = state; 
 
 r.pendingResults = pendingResults; 
 r.pendingIntents = pendingNewIntents; 
 
 r.startsNotResumed = notResumed; 
 r.isForward = isForward; 
 
 r.profileFile = profileName; 
 r.profileFd = profileFd; 
 r.autoStopProfiler = autoStopProfiler; 
 
 updatePendingConfiguration(curConfig); 
 
 queueOrSendMessage(H.LAUNCH_ACTIVITY, r); 
} 

说明:上述代码很好理解,构造一个activity记录,然后发送一个消息,所以,我们要看看Handler是如何处理这个消息的,现在转到这个Handler,它有个很短的名字叫做H

code:ActivityThread#H

//这个类太长,我只帖出了我们用到的部分 
private class H extends Handler { 
 
 public void handleMessage(Message msg) { 
  if (DEBUG_MESSAGES) Slog.v(TAG, "    handling: " + codeToString(msg.what)); 
  switch (msg.what) { 
   //这里处理LAUNCH_ACTIVITY消息类型 
   case LAUNCH_ACTIVITY: { 
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); 
    ActivityClientRecord r = (ActivityClientRecord)msg.obj; 
 
    r.packageInfo = getPackageInfoNoCheck( 
      r.activityInfo.applicationInfo, r.compatInfo); 
    //这里处理startActivity消息 
    handleLaunchActivity(r, null); 
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 
   } break; 
   case RELAUNCH_ACTIVITY: { 
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); 
    ActivityClientRecord r = (ActivityClientRecord)msg.obj; 
    handleRelaunchActivity(r); 
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 
   } break; 
   case PAUSE_ACTIVITY: 
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); 
    handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2); 
    maybeSnapshot(); 
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 
    break; 
   ... 
  } 
} 

说明:看来还要看handleLaunchActivity

code:ActivityThread#handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { 
// If we are getting ready to gc after going to the background, well 
// we are back active so skip it. 
unscheduleGcIdler(); 
if (r.profileFd != null) { 
mProfiler.setProfiler(r.profileFile, r.profileFd); 
mProfiler.startProfiling(); 
mProfiler.autoStopProfiler = r.autoStopProfiler; 
} 
// Make sure we are running with the most recent config. 
handleConfigurationChanged(null, null); 
if (localLOGV) Slog.v( 
TAG, "Handling launch of " + r); 
//终于到底了,大家都有点不耐烦了吧,从方法名可以看出, 
//performLaunchActivity真正完成了activity的调起, 
//同时activity会被实例化,并且onCreate会被调用 
Activity a = performLaunchActivity(r, customIntent); 
if (a != null) { 
r.createdConfig = new Configuration(mConfiguration); 
Bundle oldState = r.state; 
//看到没,目标activity的onResume会被调用 
handleResumeActivity(r.token, false, r.isForward, 
!r.activity.mFinished && !r.startsNotResumed); 
if (!r.activity.mFinished && r.startsNotResumed) { 
// The activity manager actually wants this one to start out 
// paused, because it needs to be visible but isn't in the 
// foreground. We accomplish this by going through the 
// normal startup (because activities expect to go through 
// onResume() the first time they run, before their window 
// is displayed), and then pausing it. However, in this case 
// we do -not- need to do the full pause cycle (of freezing 
// and such) because the activity manager assumes it can just 
// retain the current state it has. 
try { 
r.activity.mCalled = false; 
//同时,由于新activity被调起了,原activity的onPause会被调用 
mInstrumentation.callActivityOnPause(r.activity); 
// We need to keep around the original state, in case 
// we need to be created again. But we only do this 
// for pre-Honeycomb apps, which always save their state 
// when pausing, so we can not have them save their state 
// when restarting from a paused state. For HC and later, 
// we want to (and can) let the state be saved as the normal 
// part of stopping the activity. 
if (r.isPreHoneycomb()) { 
r.state = oldState; 
} 
if (!r.activity.mCalled) { 
throw new SuperNotCalledException( 
"Activity " + r.intent.getComponent().toShortString() + 
" did not call through to super.onPause()"); 
} 
} catch (SuperNotCalledException e) { 
throw e; 
} catch (Exception e) { 
if (!mInstrumentation.onException(r.activity, e)) { 
throw new RuntimeException( 
"Unable to pause activity " 
+ r.intent.getComponent().toShortString() 
+ ": " + e.toString(), e); 
} 
} 
r.paused = true; 
} 
} else { 
// If there was an error, for any reason, tell the activity 
// manager to stop us. 
try { 
ActivityManagerNative.getDefault() 
.finishActivity(r.token, Activity.RESULT_CANCELED, null); 
} catch (RemoteException ex) { 
// Ignore 
} 
} 
} 

说明:关于原activity和新activity之间的状态同步,如果大家感兴趣可以自己研究下,因为逻辑太复杂,我没法把所有问题都说清楚,否则就太深入细节而淹没了整体逻辑,研究源码要的就是清楚整体逻辑。下面看最后一个方法,这个方法是activity的启动过程的真正实现。

code:ActivityThread#performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { 
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); 
ActivityInfo aInfo = r.activityInfo; 
if (r.packageInfo == null) { 
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, 
Context.CONTEXT_INCLUDE_CODE); 
} 
//首先从intent中解析出目标activity的启动参数 
ComponentName component = r.intent.getComponent(); 
if (component == null) { 
component = r.intent.resolveActivity( 
mInitialApplication.getPackageManager()); 
r.intent.setComponent(component); 
} 
if (r.activityInfo.targetActivity != null) { 
component = new ComponentName(r.activityInfo.packageName, 
r.activityInfo.targetActivity); 
} 
Activity activity = null; 
try { 
java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); 
//用ClassLoader(类加载器)将目标activity的类通过类名加载进来并调用newInstance来实例化一个对象 
//其实就是通过Activity的无参构造方法来new一个对象,对象就是在这里new出来的。 
activity = mInstrumentation.newActivity( 
cl, component.getClassName(), r.intent); 
StrictMode.incrementExpectedActivityCount(activity.getClass()); 
r.intent.setExtrasClassLoader(cl); 
if (r.state != null) { 
r.state.setClassLoader(cl); 
} 
} catch (Exception e) { 
if (!mInstrumentation.onException(activity, e)) { 
throw new RuntimeException( 
"Unable to instantiate activity " + component 
+ ": " + e.toString(), e); 
} 
} 
try { 
Application app = r.packageInfo.makeApplication(false, mInstrumentation); 
if (localLOGV) Slog.v(TAG, "Performing launch of " + r); 
if (localLOGV) Slog.v( 
TAG, r + ": app=" + app 
+ ", appName=" + app.getPackageName() 
+ ", pkg=" + r.packageInfo.getPackageName() 
+ ", comp=" + r.intent.getComponent().toShortString() 
+ ", dir=" + r.packageInfo.getAppDir()); 
if (activity != null) { 
Context appContext = createBaseContextForActivity(r, activity); 
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); 
Configuration config = new Configuration(mCompatConfiguration); 
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " 
+ r.activityInfo.name + " with config " + config); 
activity.attach(appContext, this, getInstrumentation(), r.token, 
r.ident, app, r.intent, r.activityInfo, title, r.parent, 
r.embeddedID, r.lastNonConfigurationInstances, config); 
if (customIntent != null) { 
activity.mIntent = customIntent; 
} 
r.lastNonConfigurationInstances = null; 
activity.mStartedActivity = false; 
int theme = r.activityInfo.getThemeResource() 
if (theme != 0) { 
activity.setTheme(theme); 
} 
activity.mCalled = false; 
//目标activity的onCreate被调用了,到此为止,Activity被启动了,接下来的流程就是Activity的生命周期了, 
//本文之前已经提到,其生命周期的各种状态的切换由ApplicationThread内部来完成 
mInstrumentation.callActivityOnCreate(activity, r.state); 
if (!activity.mCalled) { 
throw new SuperNotCalledException( 
"Activity " + r.intent.getComponent().toShortString() + 
" did not call through to super.onCreate()"); 
} 
r.activity = activity; 
r.stopped = true; 
if (!r.activity.mFinished) { 
activity.performStart(); 
r.stopped = false; 
} 
if (!r.activity.mFinished) { 
if (r.state != null) { 
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); 
} 
} 
if (!r.activity.mFinished) { 
activity.mCalled = false; 
mInstrumentation.callActivityOnPostCreate(activity, r.state); 
if (!activity.mCalled) { 
throw new SuperNotCalledException( 
"Activity " + r.intent.getComponent().toShortString() + 
" did not call through to super.onPostCreate()"); 
} 
} 
} 
r.paused = true; 
mActivities.put(r.token, r); 
} catch (SuperNotCalledException e) { 
throw e; 
} catch (Exception e) { 
if (!mInstrumentation.onException(activity, e)) { 
throw new RuntimeException( 
"Unable to start activity " + component 
+ ": " + e.toString(), e); 
} 
} 
return activity; 
} 

总结

相信当你看到这里的时候,你对Activity的启动过程应该有了一个感性的认识。Activity很复杂,特性很多,本文没法对各个细节进行深入分析,而且就算真的对各个细节都进行了深入分析,那文章要有多长啊,还有人有耐心看下去吗?希望本文能够给大家带来一些帮助,谢谢大家阅读。 也希望大家多多支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/8155.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

面试官:你可以用 for of 遍历 Object 吗?

本文以 用 for of遍历 Object 为引 来聊聊 迭代器模式。 什么是迭代器模式 迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不暴露该对象的内部表示。 ——《设计模式&#xff1a;可复用面向对象软件的基础》 可以说迭代器模式就是为了遍历存在的。提…

HTML5 <body> 标签

HTML <body> 标签 实例 一个简单的 HTML 文档&#xff0c;包含尽可能少的必需的标签&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>文档标题</title> </head><body> 文档内容…

单例设计模式解读

目录 单例设计模式介绍 单例设计模式八种方式 饿汉式&#xff08;静态常量&#xff09; 饿汉式&#xff08;静态代码块&#xff09; 懒汉式(线程不安全) 懒汉式(线程安全&#xff0c;同步方法) 懒汉式(线程安全&#xff0c;同步代码块) 懒汉式(线程安全&#xff0c;同步…

libcurl库的安装及使用说明

目录 一 libcurl库安装 ① 下载网址 ② libcurl库安装步骤 ③ libcurl等第三方库的通用编译方法 二 调用libcurl编程访问百度主页 ① 代码说明 ② 编译说明 ③ 执行说明 三 libcurl的使用说明 ① curl相关函数简介 ② curl_easy_setopt函数部分选项介绍 ③…

【AUTOSAR】【Can通信】CanSM

目录 一、概述 二、限制说明 三、功能说明 3.1 基础需求 3.2 CAN网络的状态机 3.3 错误分类 3.3.1 开发错误 3.3.2 运行错误 3.3.3 拓展生产错误 3.4 ECU在线主动/被动模式 四、API接口 4.1 API定义 4.2 调度接口 一、概述 该文档描述了AUTOSAR基本软件模块CAN状态…

2023年第十四届蓝桥杯Java_大学B组真题

Java_B组试题 A: 阶乘求和试题 B: 幸运数字试题 C: 数组分割试题 D: 矩形总面积试题 E: 蜗牛试题 F: 合并区域试题 G: 买二赠一试题 H: 合并石子试题 I: 最大开支试题 J: 魔法阵【考生须知】 考试开始后&#xff0c;选手首先下载题目&#xff0c;并使用考场现场公布的解压密码解…

编译技术-词法理论

一、文法的种类 1.1 分类定义 Chomsky 文法定义&#xff1a; G(V,Vt,P,Z)G (V, V_t, P, Z)G(V,Vt​,P,Z)VVV&#xff1a;符号集合VtV_tVt​&#xff1a;终结符号集合PPP &#xff1a;有穷规则集合ZZZ&#xff1a;是被符号&#xff0c;不能是终结符 关于不同文法的区别 类型…

【多线程与高并发(锁)】1、锁的概念、分类和状态

1、锁的概念 java当中的锁、是在多线程环境下为保证共享资源健康、线程安全的一种手段。 线程操作某个共享资源之前&#xff0c;先对资源加一层锁&#xff0c;保证操作期间没有其他线程访问资源&#xff0c;当操作完成后&#xff0c;再释放锁。 2、锁的分类 Java中的锁按照…

ChatGPT对我们的影响-ChatGPT能给我们带来什么

ChatGPT日常应用 ChatGPT是一种应用广泛的自然语言处理算法&#xff0c;其可以应用于多种日常场景。以下是一些ChatGPT的日常应用&#xff1a; 聊天机器人&#xff1a;ChatGPT可用于构建聊天机器人&#xff0c;通过与用户进行自然语言交互来提供个性化的服务和支持。 新闻稿和…

MyBatis零基础入门实践

MyBatis一、MyBatis简介1、MyBatis历史2、MyBatis特性3、MyBatis下载4、和其它持久化层技术对比二、搭建MyBatis1、开发环境2、创建maven工程3、创建MyBatis的核心配置文件4、创建mapper接口5、创建MyBatis的映射文件6、通过junit测试功能7、加入log4j日志功能三、核心配置文件…

代码随想录算法训练营第五十天 | 123. 买卖股票的最佳时机 III、188. 买卖股票的最佳时机 IV

123. 买卖股票的最佳时机 III 关键在于至多买卖两次&#xff0c;这意味着可以买卖一次&#xff0c;可以买卖两次&#xff0c;也可以不买卖。 动规五部曲&#xff1a; 1、确定dp数组以及下标的含义 一天一共就有五个状态&#xff0c; 没有股票第一次持有股票第一次不持有股…

SPI协议

SPI协议 物理层 信号线 SCK(Serial Clock)&#xff1a;时钟线 MOSI(Master Output&#xff0c; Slave Input )&#xff1a;主设备输出&#xff0c;从设备输入 MISO(Master Input,&#xff0c; Slave Output)&#xff1a;主设备输入&#xff0c;从设备输出 SSN&#xff08;…

API 测试 | 了解 API 接口测试 | API 接口测试指南

什么是 API&#xff1f; API 是一个缩写&#xff0c;它代表了一个 pplication P AGC 软件覆盖整个房间。API 是用于构建软件应用程序的一组例程&#xff0c;协议和工具。API 指定一个软件程序应如何与其他软件程序进行交互。 例行程序&#xff1a;执行特定任务的程序。例程也…

Redux的基本使用详解(从入门到入土)

Redux的基本使用过程详解 学习文档 中文文档: http://www.redux.org.cn/ 英文文档: https://redux.js.org/ Github: https://github.com/reactjs/redux 一&#xff0c;redux是什么 1&#xff0c;介紹&#xff1a; redux是一个专门用于做状态管理的JS库(不是react插件库)。它…

程序员讨厌的“笔试题”,还有存在的必要性吗?

面试&#xff0c;是我们拿到offer的必经之地&#xff0c;在面试中我们会遇到各种“刁难”&#xff0c;而让程序员最为排斥的&#xff0c;非“笔试题”莫属。 △ 截图来源脉脉&#xff0c;如侵删 为什么程序员越来越排斥做面试题呢&#xff1f;我们先来看看网友们的说法&#x…

【Vue2从入门到精通】深入浅出,带你彻底搞懂Vue2组件通信的9种方式

文章目录Vue组件间通信分类1.props / $emit1.1 父组件向子组件传值1.2 子组件向父组件传值2.$parent / $children3.ref / $refs3.1 ref作用于组件3.2 ref作用于Html标签3.3 $nextTick()4.EventBus &#xff08;$emit / $on&#xff09;4.1 初始化4.2 发送事件4.3 接收事件4.4 移…

博客首页效果

学习来自风宇blog的博客首页效果 全部用的基本上都是原生的html&#xff0c;css&#xff0c;js特别是flex布局的使用&#xff0c;主轴方向可以是横轴&#xff0c;也可以是纵轴&#xff0c;弹性项还可可以使用百分比sticky粘性布局&#xff0c;作为侧边栏&#xff0c;它不会超出…

分享一个国内可用的免费ChatGPT网站

背景 ChatGPT作为一种基于人工智能技术的自然语言处理工具&#xff0c;近期的热度直接沸腾&#x1f30b;。 作为一个程序员&#xff0c;我也忍不住做了一个基于ChatGPT的网站&#xff0c;免费&#xff01;免登陆&#xff01;&#xff01;国内可直接对话ChatGPT&#xff0c;也…

c++类和对象

&#x1f646;&#x1f3fc;关注作者&#xff1a;玺子写代码 ✍️gitee&#xff1a;玺子写代码 目录&#x1f449;&#x1f3fb;类的定义&#x1f449;&#x1f3fd;类的两种定义方式&#x1f449;&#x1f3fc;类的访问限定符及封装&#x1f449;&#x1f3fd;访问限定符&…

ML@sklearn@ML流程Part3@AutomaticParameterSearches

文章目录Automatic parameter searchesdemomodel_selection::Hyper-parameter optimizersGridSearchCVegRandomizedSearchCVegNoteRandomForestRegressorMSEpipeline交叉验证&#x1f388;egL1L2正则Next stepsUser Guide vs TutorialAutomatic parameter searches Automatic p…
最新文章