当前位置: 首页 > news >正文

面试题:android中A Activity 打开B Activity,为什么A Activity的onStop()方法最后被调用

如下是一段典型的Activity间切换的日志,从A Activity切换到B Activity:

10-17 20:54:42.247: I/com.example.servicetest.AActivity(5817): onCreate() 1166919192 taskID=66  
10-17 20:54:42.263: I/com.example.servicetest.AActivity(5817): onStart() 1166919192 taskID=66  
10-17 20:54:42.263: I/com.example.servicetest.AActivity(5817): onResume() 1166919192 taskID=66  
10-17 20:54:46.997: I/com.example.servicetest.AActivity(5817): onPause() 1166919192 taskID=66  
10-17 20:54:47.021: I/com.example.servicetest.BActivity(5817): onCreate() 1166971824 taskID=66  
10-17 20:54:47.028: I/com.example.servicetest.BActivity(5817): onStart() 1166971824 taskID=66  
10-17 20:54:47.028: I/com.example.servicetest.BActivity(5817): onResume() 1166971824 taskID=66  
10-17 20:54:47.099: I/com.example.servicetest.AActivity(5817): onStop() 1166919192 taskID=66 

当触发从AActivity切换到BActivity时的日志如下:

10-17 20:54:46.997: I/com.example.servicetest.AActivity(5817): onPause() 1166919192 taskID=66
10-17 20:54:47.021: I/com.example.servicetest.BActivity(5817): onCreate() 1166971824 taskID=66
10-17 20:54:47.028: I/com.example.servicetest.BActivity(5817): onStart() 1166971824 taskID=66
10-17 20:54:47.028: I/com.example.servicetest.BActivity(5817): onResume() 1166971824 taskID=66
10-17 20:54:47.099: I/com.example.servicetest.AActivity(5817): onStop() 1166919192 taskID=66

先AActivity的onPause()被调用,然后是BActivity的初始化流程(onCreate() --> onStart() --> onResume()),再然后是AActivity的onStop()被调用。

问题来了,为什么不是先AActivity的onPause()、onStop()被调用,然后再BActivity的初始化流程(onCreate() --> onStart() --> onResume())?

或者又为什么不是先BActivity的初始化流程(onCreate() --> onStart() --> onResume()),再AActivity的onPause()、onStop()被调用?

如果所有的初始化都在onCreate()中实现,会有什么问题?

首先,Activity的onCreate()被调用时,Activity还不可见,如果要做一些动画,既然视图还不存在,在onCreate中来启动动画,明显有问题;

其次,AActivity 切换到 BActivity,再切换到 AActivity(我们假定是AActivity的同一个实例),由于实例已经存在,所以onCreate不会再被调用,那AActivity从后台切换至前台时,有可能需要一些初始化,那就没法再被调用到了,也有问题;

如果所有的初始化都在onStart()中实现,会有什么问题?

首先,onCreate()注释中,是明确建议 setContentView()、findViewById() 要在 onCreate() 中被调用,但我实测了一下,在onStart()中调用 setContentView()、findViewById() 功能也是正常的;

其次,onStart() 被调用时,Activity可能是可见了,但还不是可交互的,onResume()的注释中都明确地说了这不是Activity对用户是可见的最好的指示器,onStart() 在这之前被调用,那有一些特殊的初始化相关的逻辑在这里被调用也会有问题。

如果把所有的去初始化都在onStop()中实现,会有什么问题?

1、 在 onResume() 的注释中,建议是在onResume()中打开独占设备(比如相机),与onResume()对应的是onPause(),所以所有的去初始化操作放在onStop()中执行,可能会引出新的问题;

2、onStop() 的注释中明确地写了,在内存不足而导致系统无法保留此进程的情况下,onStop() 可能都不会被执行

Activity间跳转时,为什么是先AActivity的onPause()被调用,然后是BActivity的初始化流程(onCreate() --> onStart() --> onResume()),再然后是AActivity的onStop()被调用?

1、在 onResume() 的注释中,建议是在onResume()中打开独占设备(比如相机),与onResume()对应的是onPause(),关闭相机的操作也应该在此方法中被调用;否则,考虑一下如下场景:

如果AActivity打开了相机,我们点击某按钮要跳转到BActivity中,BActivity也想打开相机;假设AActivity的onPause() 在 BActivity启动后再被调用,那BActivity根本就无法再正常启动相机。

2、onPause() 的注释中,也明确地说了,在这个方法中执行停止动画等比较耗CPU的操作,如果不先执行这些操作,就先启动新应用,然后再来执行此操作,确实是不合逻辑;

当用户触发某事件切换到新的Activity,用户肯定是想尽快进入新的视图进行操作,上面已经说了,在onResume()一般会打开独占设备,开启动画等,当需要从AActivity切换到BActivity时,先执行AActivity中的与onResume()相对应的onPause()操作,比如关闭独占设备,关闭动画,或其它耗费cpu的操作;以防止BActivity也需要使用这些资源,关闭耗CPU的操作,也有利于BActivity运行的流畅。

底层执行AActivity的onPause()时,有一定的时间限制的,当ActivityManagerService通知应用进程暂停指定的Activity时,如果对应的onPause()在500ms内还没有执行完,ActivityManagerService就会强制关闭这个Activity。如下就是对应的onPause()执行超时常量定义:

// How long we wait until giving up on the last activity to pause.  This  
// is short because it directly impacts the responsiveness of starting the  
// next activity.  
static final int PAUSE_TIMEOUT = 500;  // 定义在ActivityStack.java中  

AActivity中比较消耗资源的部分关闭后,再切换到BActivity中执行BActivity的初始化,显示BActivity中的View。

当BActivity已经执行显示出来了,用户可以交互,后台再去执行AActivity的onStop()操作,即使这里面有些比较耗时的操作,也没有关系,这是在后台执行所以也不影响用户的体验。

开发中可能会遇到个别问题,如发现onStop的调用时机受下一个页面的影响,本页面的onStop是在下一个页面onResume,onWindowFocusChanged等之后才会调用,如果在onResume和onWindowFocusChanged中进行了耗时的操作,会导致前一个页面的onStop不能被回调。这个细节会被大部分人忽略,但有时会触发意想不到的bug。

上面说了这么多,其实还没有解释为什么AActivity的onStop()方法会在BActivity的OnResume方法之后执行,下面我们需要借助源码来找下原因。

Activity启动源码分析-- onStop,onDestroy探寻

1.目的

这个是这个启动的最后一篇,讲Activity生命周期里面回调的最后几个方法。前面已经讲到了onCreate,onStart,onResume,onPause。还差onStop,onDestroy,onRestart。看一下Activity启动过程中,他们究竟藏在哪里。

这篇结束后,Activity的几个主要生命周期就都介绍了。当然,读者也可以继续拓展其他方法。

onStop还能算在这个启动过程中,onDestroy,onRestart其实就没多大关系了。下面会分开讲。

2.OnStop

还是先放图。

接着上篇,

ActivityThread

        public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {...//onResume执行完后,会在空闲时执行IdlerLooper.myQueue().addIdleHandler(new Idler());}

IdleHandler是MessageQueue类里的一个借口,messages为空的时候就会执行IdleHandler。看下Idler是怎么实现queueIdle()的。

ActivityThread

    private class Idler implements MessageQueue.IdleHandler {@Overridepublic final boolean queueIdle() {...mNewActivities = null;//这就相当眼熟了,我们直接往AMS去找IActivityManager am = ActivityManager.getService();ActivityClientRecord prev;do {if (localLOGV) Slog.v(TAG, "Reporting idle of " + a +" finished=" +(a.activity != null && a.activity.mFinished));if (a.activity != null && !a.activity.mFinished) {try {am.activityIdle(a.token, a.createdConfig, stopProfiling);a.createdConfig = null;} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}}prev = a;a = a.nextIdle;prev.nextIdle = null;} while (a != null);}...return false;}}

ActivityManagerService

    @Overridepublic final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {final long origId = Binder.clearCallingIdentity();synchronized (this) {ActivityStack stack = ActivityRecord.getStackLocked(token);if (stack != null) {ActivityRecord r =mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */,false /* processPausingActivities */, config);if (stopProfiling) {if ((mProfileProc == r.app) && mProfilerInfo != null) {clearProfilerLocked();}}}}Binder.restoreCallingIdentity(origId);}

ActivityStackSupervisor

    @GuardedBy("mService")final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,boolean processPausingActivities, Configuration config) {...//找到当前不在界面上为Pause状态的Activity// Atomically retrieve all of the other things to do.final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r,true /* remove */, processPausingActivities);NS = stops != null ? stops.size() : 0;if ((NF = mFinishingActivities.size()) > 0) {finishes = new ArrayList<>(mFinishingActivities);mFinishingActivities.clear();}if (mStartingUsers.size() > 0) {startingUsers = new ArrayList<>(mStartingUsers);mStartingUsers.clear();}// Stop any activities that are scheduled to do so but have been// waiting for the next one to start.for (int i = 0; i < NS; i++) {r = stops.get(i);final ActivityStack stack = r.getStack();if (stack != null) {//Pause的Activitiy正在finish状态,这里自然不是if (r.finishing) {stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false,"activityIdleInternalLocked");} else {//走你stack.stopActivityLocked(r);}}}//onDestory会在这调用正在fininshing的Activity,是mFinishingActivities维护的。但是在前面没有看到往mFinishingActivities添加成员的地方// Finish any activities that are scheduled to do so but have been// waiting for the next one to start.for (int i = 0; i < NF; i++) {r = finishes.get(i);final ActivityStack stack = r.getStack();if (stack != null) {activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");}}...}final ArrayList<ActivityRecord> processStoppingActivitiesLocked(ActivityRecord idleActivity,boolean remove, boolean processPausingActivities) {ArrayList<ActivityRecord> stops = null;final boolean nowVisible = allResumedActivitiesVisible();for (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx) {...if (remove) {final ActivityStack stack = s.getStack();final boolean shouldSleepOrShutDown = stack != null? stack.shouldSleepOrShutDownActivities(): mService.isSleepingOrShuttingDownLocked();//非显示状态if (!waitingVisible || shouldSleepOrShutDown) {//处于Pauseif (!processPausingActivities && s.isState(PAUSING)) {// Defer processing pausing activities in this iteration and reschedule// a delayed idle to reprocess it againremoveTimeoutsForActivityLocked(idleActivity);scheduleIdleTimeoutLocked(idleActivity);continue;}if (DEBUG_STATES) Slog.v(TAG, "Ready to stop: " + s);if (stops == null) {stops = new ArrayList<>();}//添加返回stops.add(s);mStoppingActivities.remove(activityNdx);}}}return stops;}

ActivityStack

    private boolean adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf) {final ActivityStack stack =mStackSupervisor.getNextFocusableStackLocked(this, !allowFocusSelf);final String myReason = reason + " adjustFocusToNextFocusableStack";if (stack == null) {return false;}final ActivityRecord top = stack.topRunningActivityLocked();if (stack.isActivityTypeHome() && (top == null || !top.visible)) {// If we will be focusing on the home stack next and its current top activity isn't// visible, then use the move the home stack task to top to make the activity visible.return mStackSupervisor.moveHomeStackTaskToTop(reason);}stack.moveToFront(myReason);return true;}final void stopActivityLocked(ActivityRecord r) {...//再眼熟不过,让ClientHandler去执行StopActivityItem了。mService.getLifecycleManager().scheduleTransaction(r.app.thread, r.appToken,StopActivityItem.obtain(r.visible, r.configChangeFlags));...}

这里从LifecycleManagerApp传递的过程前面已描述两次,这里不再赘述。

StopActivityItem

    @Overridepublic void execute(ClientTransactionHandler client, IBinder token,PendingTransactionActions pendingActions) {Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions,true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);}

ActivityThread

    @Overridepublic void handleStopActivity(IBinder token, boolean show, int configChanges,PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {...performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,reason);...}private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown,boolean saveState, boolean finalStateRequest, String reason) {...if (!keepShown) {callActivityOnStop(r, saveState, reason);}...}private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {// Before P onSaveInstanceState was called before onStop, starting with P it's// called after. Before Honeycomb state was always saved before onPause.final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null&& !r.isPreHoneycomb();final boolean isPreP = r.isPreP();if (shouldSaveState && isPreP) {callActivityOnSaveInstanceState(r);}try {//这里可以看到Stop方法的调用了r.activity.performStop(false /*preserveWindow*/, reason);} catch (SuperNotCalledException e) {throw e;} catch (Exception e) {if (!mInstrumentation.onException(r.activity, e)) {throw new RuntimeException("Unable to stop activity "+ r.intent.getComponent().toShortString()+ ": " + e.toString(), e);}}r.setState(ON_STOP);if (shouldSaveState && !isPreP) {callActivityOnSaveInstanceState(r);}}

可以从上面看到,onStop实在MessageQueue空闲才会调用。不像onPuase,onResume一定会被调用。

3.OnDestroy

从之前见到的方法里,唯一好像与onDestroy相关的。是ActivityStackSupervisor.mFinishingActivities。但是启动流程里没有见到正常赋值的地方啊,所以全局搜索一下,猜测一下是否有DestroyActivityItem这个类,果然是有的。再往回推调用链,可以看到正常调用有找到两处,这里单列一下System_server端的调用关系:

对应App手动调用finish方法:
->ActivityManagerService:finishActivity
->ActivityStack:requestFinishActivityLocked
->ActivityStack:finishActivityLocked
->ActivityStack:finishCurrentActivityLocked
->ActivityStack:destroyActivityLocked

还有一个是

调用处可以参考第三篇Activity启动源码分析(3)-- 新app进程创建过程,在ActivityThread.attach有一个GcWatcher,内存占用大于3/4就会触发
->ActivityManagerService:releaseSomeActivities
->ActivityStackSupervisor:releaseSomeActivitiesLocked
->ActivityStack:releaseSomeActivitiesLocked
->ActivityStack:destroyActivityLocked

这篇的话,有看过Pause过程的话,是相当容易理解的。因为这里调用方式都一致。不会有上一篇app进程创建过程设计过程那么复杂。当然,我的文章只能描述其中一部分,还有大部分需要读者再从相关文章拓展。

其他地方看着都是异常时调用,还有系统强杀。所以onDestroy和onCreate一定会成对调用吗?并不是的,onDestroy并不一定会被调用。正常的流程下,只有在调用了finish后3/4内存占用后触发GC才调用了。所以反注册一定需要很是小心。

推荐的一种写法是

    @Overrideprotected void onStop() {super.onStop();if(isFinishing()){//unRegister}}

3.OnRestart

在上一篇,我们有把cycleToPath具体分析。其实在这个里面

TransactionExecutorHelper

    public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) {...//如果是从大的生命周期往小的生命周期变化,如onStop到onResumeelse { // finish < start, can't just cycle downif (start == ON_PAUSE && finish == ON_RESUME) {// Special case when we can just directly go to resumed state.mLifecycleSequence.add(ON_RESUME);} else if (start <= ON_STOP && finish >= ON_START) {// Restart and go to required state.//这里看到,最大也就是到onStop,并没有调用onDestroy方法// Go to stopped state first.for (int i = start + 1; i <= ON_STOP; i++) {mLifecycleSequence.add(i);}//然后调用了Restart// RestartmLifecycleSequence.add(ON_RESTART);// Go to required statefor (int i = ON_START; i <= finish; i++) {mLifecycleSequence.add(i);}} else {// Relaunch and go to required state// Go to destroyed state first.for (int i = start + 1; i <= ON_DESTROY; i++) {mLifecycleSequence.add(i);}// Go to required statefor (int i = ON_CREATE; i <= finish; i++) {mLifecycleSequence.add(i);}}}//移除了onResume// Remove last transition in case we want to perform it with some specific params.if (excludeLastState && mLifecycleSequence.size() != 0) {mLifecycleSequence.remove(mLifecycleSequence.size() - 1);}return mLifecycleSequence;}

一言蔽之,就是onDestroy没有调用的时候,Activity的状态由大变小,就会走onRestart。


上面提到onStop实在MessageQueue空闲才会调用,这个MessageQueue就是onResume的时候添加的Idler。

Looper.myQueue().addIdleHandler(new Idler());

IdleHandler 详解

IdleHandler 是 MessageQueue 内定义的一个接口,一般可用于做性能优化。当消息队列内没有需要立即执行的 message 时,会主动触发 IdleHandler 的 queueIdle 方法。返回值为 false,即只会执行一次;返回值为 true,即每次当消息队列内没有需要立即执行的消息时,都会触发该方法。

public final class MessageQueue {public static interface IdleHandler {boolean queueIdle();}
}

使用方式

通过获取 looper 对应的 MessageQueue 队列注册监听。

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {// doSomething()return false;}
});

源码解析

IdleHandler 的执行源码很短。

Message next() {// 隐藏无关代码...int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (; ; ) {// 隐藏无关代码...// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {// No idle handlers to run.  Loop and wait some more.mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.nextPollTimeoutMillis = 0;
}
  1. 在 MessageQueue 里 next 方法的 for 死循环内,获取 mIdleHandlers 的数量 pendingIdleHandlerCount;

  1. 通过 mMessages == null || now < mMessages.when 判断当前消息队列为空或者目前没有需要执行的消息时,给 pendingIdleHandlerCount 赋值;

  1. 当数量大于 0,遍历取出数组内的 IdleHandler,执行 queueIdle() ;

  1. 返回值为 false 时,主动移除监听 mIdleHandlers.remove(idler);

使用场景

  1. 如果启动的 Activity、Fragment、Dialog 内含有大量数据和视图的加载,导致首次打开时动画切换卡顿或者一瞬间白屏,可将部分加载逻辑放到 queueIdle() 内处理。例如引导图的加载和弹窗提示等;

  1. 系统源码中 ActivityThread 的 GcIdler,在某些场景等待消息队列暂时空闲时会尝试执行 GC 操作;

  1. 系统源码中 ActivityThread 的 Idler,在 handleResumeActivity() 方法内会注册 Idler(),等待 handleResumeActivity 后视图绘制完成,消息队列暂时空闲时再调用 AMS 的 activityIdle 方法,检查页面的生命周期状态,触发 activity 的 stop 生命周期等。
    这也是为什么我们 BActivity 跳转 CActivity 时,BActivity 生命周期的 onStop() 会在 CActivity 的 onResume() 后

  1. 一些第三方框架 Glide 和 LeakCanary 等也使用到 IdleHandler,感兴趣的朋友可以看看源码;

相关文章:

面试题:android中A Activity 打开B Activity,为什么A Activity的onStop()方法最后被调用

如下是一段典型的Activity间切换的日志&#xff0c;从A Activity切换到B Activity&#xff1a;10-17 20:54:42.247: I/com.example.servicetest.AActivity(5817): onCreate() 1166919192 taskID66 10-17 20:54:42.263: I/com.example.servicetest.AActivity(5817): onStart()…...

百度版本gactgpt即将来临,gpt人工智能机器横空出世

百度版本gactgpt即将来临&#xff0c;gpt人工智能机器横空出世&#xff0c;“一言”为定&#xff01;百度版ChatGPT确认&#xff01;李彦宏OKR曝光&#xff0c;率先应用于收索业务 gactCBT 大获&#xff0c;当下极有可能成为人工智能的 iPhone 时刻。为了在这场人工智能竞赛中…...

【python--networkx】函数说明+代码讲解

【Python–NetworkX】函数说明代码讲解 文章目录【Python--NetworkX】函数说明代码讲解1. 介绍1.1 前言1.2 图的类型&#xff08;Graph Types&#xff09;1.3 常用方法2. 代码示例1. 介绍 1.1 前言 NetworkX是复杂网络研究领域中的常用Python包。 1.2 图的类型&#xff08;G…...

【Jqgrid分页勾选保存】三步实现表格分页勾选(取消勾选)保存(附源码)

目录1、创建临时存储数组&#xff0c;初始化赋值2、单行选中与取消&#xff0c;调整数组3、全选与取消全选&#xff0c;调整数组4、输出数组保存5、片尾彩蛋【写在前面】表格可以说是在我们的web页面中是最常见的&#xff0c;之前我们介绍过layui表格翻页勾选的实现过程&#x…...

Appium移动自动化测试——app控件获取之uiautomatorviewer

下载手机YY http://yydl.duowan.com/mobile/yymobile_client-android/5.4.2/yymobile_client-5.4.2-881.apk 若链接失效&#xff0c;请自行百度 新建maven空白工程 前置条件&#xff1a;安装eclipse&#xff0c;及其maven插件&#xff0c;请自行百度 新建的工程如下&#xf…...

webpack、vite、vue-cli、create-vue 的区别

webpack、vite、vue-cli、create-vue 的区别 首先说结论 Rollup更适合打包库&#xff0c;webpack更适合打包项目应用&#xff0c;vite基于rollup实现了热更新也适合打包项目。 功能工具工具脚手架vue-clicreate-vue构建项目vite打包代码webpackrollup 脚手架:用于初始化&#…...

数据结构——TreeMap、TreeSet与HashMap、HashSet

目录 一、Map 1、定义 2、常用方法 3、注意 二、TreeMap 三、HashMap 1、定义 2、冲突定义 3、冲突避免方法——哈希函数设计 &#xff08;1&#xff09;、直接定制法(常用) &#xff08;2&#xff09;、除留余数法(常用) &#xff08;3&#xff09;、平方取中法 &…...

Spring Boot学习篇(十三)

Spring Boot学习篇(十三) shiro安全框架使用篇(五) 1 准备工作 1.1 在SysUserMapper.xml中书写自定义标签 <select id"findRoles" resultType"string">select name from sys_role where id (select roleid from sys_user_role where userid (S…...

微软Bing的AI人工只能对话体验名额申请教程

微软Bing 免费体验名额申请教程流程ChatGPT这东西可太过火了。国外国内&#xff0c;圈里圈外都是人声鼎沸。微软&#xff0c;谷歌&#xff0c;百度这些大佬纷纷出手。连看个同花顺都有GPT概念了&#xff0c;搞技术&#xff0c;做生意的看来都盯上了 流程 下面就讲一下如何申…...

怎么打造WhatsApp Team?SaleSmartly(ss客服)告诉你

关键词&#xff1a;WhatsApp Team SaleSmartly&#xff08;ss客服&#xff09; 您是否正在寻找一种让您的团队能够在 WhatsApp协作消息传递的解决方案?拥有了 WhatsApp Team&#xff0c;不仅效率提升&#xff0c;还可以在智能聊天工具中比如SaleSmartly&#xff08;ss客服&…...

IPV4地址的原理和配置

第三章&#xff1a;IP地址的配置 IPv4&#xff08;Internet Protocol Version 4&#xff09;协议族是TCP/IP协议族中最为核心的协议族。它工作在TCP/IP协议栈的网络层&#xff0c;该层与OSI参考模型的网络层相对应。网络层提供了无连接数据传输服务&#xff0c;即网络在发送分…...

软件测试面试准备——(一)Selenium(1)基础问题及自动化测试

滴滴面试&#xff1a;1. 自己负责哪部分功能&#xff1f;农餐对接系统分为了两大子系统&#xff0c;一个是个人订餐系统&#xff0c;二是餐馆、个人与农产品供应商进行农产品交易系统。我主要负责组织测试人员对该系统进行测试。我们测试分为两个阶段&#xff1a;一、功能测试阶…...

AcWing 1230.K倍区间

AcWing 1230. K倍区间 题目描述 给定一个长度为 NNN 的数列&#xff0c;A1,A2,…ANA_1, A_2, … A_NA1​,A2​,…AN​ &#xff0c;如果其中一段连续的子序列 Ai,Ai1,…AjA_i, A_{i1}, … A_jAi​,Ai1​,…Aj​ 之和是 KKK 的倍数&#xff0c;我们就称这个区间 [i,j][i,j][i,…...

kubernetes集群部署springcloud项目【AL】【未写完】

kubernetes集群部署springcloud项目【AL】 &#xff08;先手工做&#xff0c;非自动化&#xff09; #环境&#xff1a; 192.168.73.138 master 192.168.73.139 node1 192.168.73.140 node2 192.168.73.137 harbor、mysqlgit clone https://github.com/lizhenliang/simple-…...

各种音频接口比较

时间 参考&#xff1a;https://www.bilibili.com/video/BV1SL4y1q7GZ/?spm_id_from333.337.search-card.all.click&vd_source00bd76f9d6dc090461cddd9f0deb2d51&#xff0c; https://blog.csdn.net/weixin_43794311/article/details/128941346 接口名字时间公司支持格式…...

软件测试面试理论(超详细)

【面试理论知识】1、你的测试职业发展是什么? 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做好测试工程师的要点去要求自己…...

c++学习笔记-二进制文件操作(哔站-黑马程序员c++教学视频)

一、基本概念 以二进制的方式对文件进行读写操作 打开方式指定为 ios::binary 优点&#xff1a;可以写入自己定义的数据类型 1、写文件 二进制方式写文件&#xff1a;流对象调用成员write 函数原型&#xff1a;ostream& write(const char * buffer,int len);参数解释…...

内网渗透(二十三)之Windows协议认证和密码抓取-Mimikatz介绍和各种模块使用方法

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…...

Nginx if的使用教程

if指令该指令用来支持条件判断&#xff0c;并根据条件判断结果选择不同的Nginx配置。语法if (condition){...}默认值—位置server、locationcondition为判定条件&#xff0c;可以支持以下写法&#xff1a;1. 变量名。如果变量名对应的值为空字符串或"0"&#xff0c;i…...

备考蓝桥杯【快速排序和归并排序】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…...

Taro使用微信OCR插件无法调用onSuccess回调问题

Taro使用微信插件无法调用onSuccess回调问题小程序后台添加插件在开放社区购买相应的套餐详细步骤1.在app.config.js中添加如下代码2.在页面的page.config.js添加插件3.使用ocr-navigator识别身份证小程序后台添加插件 在开放社区购买相应的套餐 购买地址 详细步骤 1.在app.…...

【Java】代码块的细节你搞懂了吗(基础知识七)

希望像唠嗑一样&#xff0c;one step one futher。 目录 &#xff08;1&#xff09;代码块的应用场景 &#xff08;2&#xff09;代码块的细节 1.static 代码块只加载一次 2.当调用类的静态成员时&#xff0c;类会加载 3. 使用类的静态成员时&#xff0c;static代码块会被执…...

设计模式C++实现12:抽象工厂模式

参考大话设计模式&#xff1b; 详细内容参见大话设计模式一书第十五章&#xff0c;该书使用C#实现&#xff0c;本实验通过C语言实现。 抽象工厂模式&#xff08;Abstract Factory&#xff09;&#xff0c;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们…...

目标检测论文阅读:GraphFPN算法笔记

标题&#xff1a;GraphFPN: Graph Feature Pyramid Network for Object Detection 会议&#xff1a;ICCV2021 论文地址&#xff1a;https://ieeexplore.ieee.org/document/9710561/ Abstract 特征金字塔已经被证明在需要多尺度特征的图像理解任务中是强大的。SOTA的多尺度特征…...

实测2023款哪吒U-II,智驾功能对女司机很友好

最近&#xff0c;我们受邀试驾了2023款哪吒U-II。这是一款A级新能源SUV&#xff0c;是哪吒U的改款车型。哪吒U系列自2020年3月上市到2023年1月&#xff0c;累计销售数量达76688台&#xff0c;也因此被称为15万级智能天花板。2023款哪吒U-II的一大亮点是&#xff1a;针对以往哪吒…...

Python自动化测试【软件测试最全教程(附笔记、学习路线)】,看完即就业

最近看到很多粉丝在后台私信我&#xff0c;叫我做一期Python自动化测试的教程&#xff0c;其实关于这个问题&#xff0c;我也早就在着手准备了&#xff0c;我录制了一整套完整的Python自动化测试的教程&#xff0c;上传到网盘里了&#xff0c;大家有兴趣的可以去文末交流群免费…...

2023/2/13总结

今天主要学习了哈夫曼树。 哈夫曼树 哈夫曼树是二叉树的一种&#xff0c;它是一种WPL最优二叉树。 叶子结点&#xff08;也称叶节点&#xff09;&#xff1a;指的是自己下面不再连接有节点的节点&#xff08;即末端&#xff09;&#xff0c;称为叶子节点&#xff08;又称为终…...

webSock前端

1.什么是webSocket WebSocket是一种在单个TCP连接上进行全双工通信的协议。允许服务端主动向客户端推送数据。 2.如何使用webSocket WebSocket 构造函数WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。 代码如下: let ws = new WebSocket(网址); 2.websock事件: …...

AcWing 3956. 截断数组(每日一题)

AcWing 3956. 截断数组 题目描述 给定一个长度为 nnn 的数组 a1,a2,…,ana_1, a_2, …, a_na1​,a2​,…,an​ 。 现在&#xff0c;要将该数组从中间截断&#xff0c;得到三个非空子数组。 要求&#xff0c;三个子数组内各元素之和都相等。 请问&#xff0c;共有多少种不同…...

Android 一体机研发之修改系统设置————屏幕亮度

Android 一体机研发之修改系统设置————屏幕亮度 Android 一体机研发之修改系统设置————声音 Android 一体机研发之修改系统设置————自动锁屏 前言 最近工作略微有点儿空闲&#xff0c;抽空给大家总结一下&#xff1a;近期一直搞得一体机app研发&#xff0c;适用…...

长沙中小企业网站建设/电商软文范例

dojo jquery该帖子现在已过时&#xff0c;仅保存在此处仅供娱乐之用。 总览 jQuery和DOJO都是JavaScript开发框架/程序包&#xff0c;它们提供的功能使JavaScript代码的编写更加轻松&#xff0c;快捷和高效。 两者都有优点和缺点&#xff0c;优点和缺点&#xff0c;但是在jQu…...

做特产网站的原因/百度竞价广告代理

在 ES5 中&#xff0c;我们可以使用如下方式解决继承的问题 function Super() {} Super.prototype.getNumber function() {return 1 }function Sub() {} let s new Sub() Sub.prototype Object.create(Super.prototype, {constructor: {value: Sub,enumerable: false,writab…...

wordpress 付款插件/夫唯seo培训

摘要&#xff1a;就计算机辅助教学在解剖学、组织学、病理学、微生物学中的教学应用进行探讨&#xff0c;论述了计算机辅助教学在医学形态学科中理论教学、实验教学及考试中的应用。关键词&#xff1a;医学&#xff1b;形态学科&#xff1b;CAI计算机辅助教学(CAI)系统是以计算…...

东莞网站制作建设公司/怎么做推广

阿里云负载均衡SLB怎么收费&#xff1f;码笔记发现付费模式分为包年包月和按量付费&#xff0c;负载均衡按固定带宽和按使用流量计费也不同。负载均衡按固定带宽收费需要支付两项费用&#xff0c;SLB实例租用费和带宽费&#xff1b;如果按使用流量计费&#xff0c;需要支付SLB实…...

做网站 要学 什么语言/旅游搜索量环比增188%

求一个整数的平方根&#xff0c;如果该整数的平方根不是整数的话&#xff0c;返回平方根取整。 二分搜索&#xff0c;开始区间是1&#xff0c;终止区间是x。 class Solution { public:int mySqrt(int x) {if(x < 0)return 0;if(x 1)return 1;int begin 1;int end x;int …...

专业营销网站建设/网推平台

路由条目进入路由表的前提条件&#xff1a;路由条目的“下一跳”&#xff0c;必须可达&#xff1b;即路由条目中的网段后面的端口和IP地址&#xff0c;必须是可以访问的&#xff1b;如果是端口&#xff0c;则必须得是 up / up 的&#xff1b; 如果是IP地址&#xff0c;则必须得…...