Android源码解析四大组件系列(八)---广播几个问题的深入理解

本文主要是介绍Android源码解析四大组件系列(八)---广播几个问题的深入理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

接上篇文章,这篇文章主要是总结前面知识,并且了解一些细节问题,加深对广播机制的理解,比如有播有序是怎么保证有序的?广播拦截机制是怎么实现的?广播发送超时了是怎么处理的?registerReceiver方法发返回值有什么用?粘性广播等等。

Android源码解析四大组件系列(五)—广播的注册过程

Android源码解析四大组件系列(六)—广播的处理过程

Android源码解析四大组件系列(七)—广播的发送过程

1、广播相关数据结构的再次理解

  • ReceiverDispatcher: 客户端广播分发者对象,第一篇讲的很清楚了,ReceiverDispatcher的内部类InnerReceiver为binder对象,用于与AMS的传递与通信。

  • ReceiverList: 继承自ArrayList,存放了Receiver的binder对象以及其注册的BroadcastFilter列表。AMS中定义了
    final HashMap

2、有序广播是怎么保证有序的

上一篇文章中说了processNextBroadcast()只会处理一个BroadcastRecord的一个receiver,那怎么将广播传递给下一个receiver呢?广播接受者有“动态”和“静态”之分,广播消息也有“串行”和“并行”之分,或者叫“有序”和“无序”之分。广播的处理方式跟广播的接收者和广播消息类型有关系。有序广播是怎么保证有序的这个问题,得分情况讨论,对于动态注册的receiver,先回到最终onReceive回调的地方,分析如下:

 static final class ReceiverDispatcher {.....final class Args extends BroadcastReceiver.PendingResult implements Runnable {.....public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,boolean ordered, boolean sticky, int sendingUser) {//mRegistered传进来的是truesuper(resultCode, resultData, resultExtras,mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,sticky, mIIntentReceiver.asBinder(), sendingUser, intent.getFlags());mCurIntent = intent;mOrdered = ordered;}public void run() {.....try {ClassLoader cl =  mReceiver.getClass().getClassLoader();intent.setExtrasClassLoader(cl);intent.prepareToEnterProcess();setExtrasClassLoader(cl);receiver.setPendingResult(this);//广播的onReceive方法回调receiver.onReceive(mContext, intent);} catch (Exception e) {if (mRegistered && ordered) {if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,"Finishing failed broadcast to " + mReceiver);sendFinished(mgr);}if (mInstrumentation == null ||!mInstrumentation.onException(mReceiver, e)) {Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);throw new RuntimeException("Error receiving broadcast " + intent+ " in " + mReceiver, e);}}if (receiver.getPendingResult() != null) {finish();}Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);}}}

因为在调用onReceive之前,执行了 receiver.setPendingResult(this),所以在下面receiver.getPendingResult()就不是null,则就进入BroadcastReceiver的内部类PendingResult的finish方法。

 public final void finish() {if (mType == TYPE_COMPONENT) {final IActivityManager mgr = ActivityManagerNative.getDefault();if (QueuedWork.hasPendingWork()) {......QueuedWork.singleThreadExecutor().execute( new Runnable() {@Override public void run() {if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,"Finishing broadcast after work to component " + mToken);sendFinished(mgr);}});} else {if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,"Finishing broadcast to component " + mToken);sendFinished(mgr);}} else if (mOrderedHint && mType != TYPE_UNREGISTERED) {if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,"Finishing broadcast to " + mToken);final IActivityManager mgr = ActivityManagerNative.getDefault();sendFinished(mgr);}}

finish方法中根据mType的值有两个分支。mType是PendingResult的成员变量,在PendingResult的构造函数中进行赋值的。

     public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,boolean ordered, boolean sticky, IBinder token, int userId, int flags) {mResultCode = resultCode;mResultData = resultData;mResultExtras = resultExtras;mType = type;mOrderedHint = ordered;mInitialStickyHint = sticky;mToken = token;mSendingUser = userId;mFlags = flags;}

这个构造方法是在BroadcastReceiver.PendingResult的子类Args中调用的

 final class Args extends BroadcastReceiver.PendingResult implements Runnable {private Intent mCurIntent;private final boolean mOrdered;private boolean mDispatched;public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,boolean ordered, boolean sticky, int sendingUser) {super(resultCode, resultData, resultExtras,mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,sticky, mIIntentReceiver.asBinder(), sendingUser, intent.getFlags());mCurIntent = intent;mOrdered = ordered;}
}

由于mRegistered是动态注册广播接收者传进来的,值是true,所以上面mType的值是TYPE_REGISTERED,由于是有序广播ordered值是true,那么mOrderedHint为true,所以要走第二个分支:

   if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,"Finishing broadcast to " + mToken);final IActivityManager mgr = ActivityManagerNative.getDefault();sendFinished(mgr);

BroadcastReceiver的sendFinished方法如下:

 public void sendFinished(IActivityManager am) {synchronized (this) {if (mFinished) {throw new IllegalStateException("Broadcast already finished");}mFinished = true;try {if (mResultExtras != null) {mResultExtras.setAllowFds(false);}if (mOrderedHint) {am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,mAbortBroadcast, mFlags);} else {// This broadcast was sent to a component; it is not ordered,// but we still need to tell the activity manager we are done.am.finishReceiver(mToken, 0, null, null, false, mFlags);}} catch (RemoteException ex) {}}}

有序广播mOrderedHint值为true,所以进入到AMS的finishReceiver方法。

public void finishReceiver(IBinder who, int resultCode, String resultData,Bundle resultExtras, boolean resultAbort, int flags) {if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + who);// Refuse possible leaked file descriptorsif (resultExtras != null && resultExtras.hasFileDescriptors()) {throw new IllegalArgumentException("File descriptors passed in Bundle");}final long origId = Binder.clearCallingIdentity();try {boolean doNext = false;BroadcastRecord r;synchronized(this) {BroadcastQueue queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0? mFgBroadcastQueue : mBgBroadcastQueue;r = queue.getMatchingOrderedReceiver(who);if (r != null) {doNext = r.queue.finishReceiverLocked(r, resultCode,resultData, resultExtras, resultAbort, true);}}if (doNext) {//再次执行processNextBroadcast处理广播r.queue.processNextBroadcast(false);}trimApplications();} finally {Binder.restoreCallingIdentity(origId);}}

上面是分析了动态的广播接收者是怎么按照一个接着一个处理的。在看看静态注册的receiver,回到静态广播回调onReceive方法的地方。

private void handleReceiver(ReceiverData data) {....IActivityManager mgr = ActivityManagerNative.getDefault();BroadcastReceiver receiver;try {java.lang.ClassLoader cl = packageInfo.getClassLoader();data.intent.setExtrasClassLoader(cl);data.intent.prepareToEnterProcess();data.setExtrasClassLoader(cl);//反射出BroadcastReceiverreceiver = (BroadcastReceiver)cl.loadClass(component).newInstance();} catch (Exception e) {....}try {Application app = packageInfo.makeApplication(false, mInstrumentation);....ContextImpl context = (ContextImpl)app.getBaseContext();sCurrentBroadcastIntent.set(data.intent);receiver.setPendingResult(data);//回调广播的onReceive方法receiver.onReceive(context.getReceiverRestrictedContext(),data.intent);} catch (Exception e) {....}} finally {sCurrentBroadcastIntent.set(null);}if (receiver.getPendingResult() != null) {data.finish();}}

在回调onReceiver方法之前, 执行了 receiver.setPendingResult(data),所以下面receiver.getPendingResult() != null成立,走 data.finish(),data是ReceiverData对象,handleReceiver方法传进来的,在scheduleReceiver方法中初始化。

  public final void scheduleReceiver(Intent intent, ActivityInfo info,CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,boolean sync, int sendingUser, int processState) {updateProcessState(processState, false);ReceiverData r = new ReceiverData(intent, resultCode, data, extras,sync, false, mAppThread.asBinder(), sendingUser);r.info = info;r.compatInfo = compatInfo;sendMessage(H.RECEIVER, r);}

我们看 data.finish()方法

  public final void finish() {if (mType == TYPE_COMPONENT) {final IActivityManager mgr = ActivityManagerNative.getDefault();if (QueuedWork.hasPendingWork()) {QueuedWork.singleThreadExecutor().execute( new Runnable() {@Override public void run() {if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,"Finishing broadcast after work to component " + mToken);sendFinished(mgr);}});} else {if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,"Finishing broadcast to component " + mToken);sendFinished(mgr);}} else if (mOrderedHint && mType != TYPE_UNREGISTERED) {if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,"Finishing broadcast to " + mToken);final IActivityManager mgr = ActivityManagerNative.getDefault();sendFinished(mgr);}}

此时mType分析后值是TYPE_COMPONENT,同样会走sendFinished,后面AMS的处理逻辑是一样的,不赘述。

3、广播超时是怎么处理的?

AMS维护了两个广播队列BroadcastQueue,mFgBroadcastQueue,前台队列的超时时间是10秒,mBgBroadcastQueue,后台队列的超时时间是60秒,如果广播没有在规定的时间内处理完就会发生ANR,如果你想你的广播进入前台广播队列,那么在发送的时候,在intent中加入Intent.FLAG_RECEIVER_FOREGROUND标记,如果不加,系统默认是后台广播。mFgBroadcastQueue会有更高的权限,被优先处理。

在processNextBroadcast方法中有下面一段代码,与广播超时有关系,一旦超时就会出现ANR。

do {int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;if (mService.mProcessesReady && r.dispatchTime > 0) {long now = SystemClock.uptimeMillis();//广播消息的第一个ANR监测机制if ((numReceivers > 0) &&(now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {Slog.w(TAG, "Hung broadcast ["+ mQueueName + "] discarded after timeout failure:"+ " now=" + now+ " dispatchTime=" + r.dispatchTime+ " startTime=" + r.receiverTime+ " intent=" + r.intent+ " numReceivers=" + numReceivers+ " nextReceiver=" + r.nextReceiver+ " state=" + r.state);broadcastTimeoutLocked(false); // 超时处理forceReceive = true;r.state = BroadcastRecord.IDLE;}}//判断广播有没有处理完毕if (r.receivers == null || r.nextReceiver >= numReceivers|| r.resultAbort || forceReceive) {// No more receivers for this broadcast!  Send the final// result if requested...if (r.resultTo != null) {try {     performReceiveLocked(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode r.resultData, r.resultExtras, false, false, r.userId);    r.resultTo = null;} catch (RemoteException e) {......}}} while (r == null);

广播的超时机制是针对有序广播来说的,无序广播一次性全部处理了,肯定不会超时,超时的这段逻辑都在broadcastTimeoutLocked中,首先判断是否超时,公式:r.dispatchTime + 2×mTimeoutPeriod×numReceivers,现在解释一下这几个时间:
- dispatchTime的意义是标记实际处理BroadcastRecord的起始时间,有序广播是一个接着一个进行处理的,第一次dispatchTime=0,并不会进入该条件判断

  • mTimeoutPeriod由当前BroadcastQueue的类型决定(mFgBroadcastQueue为10秒,mBgBroadcastQueue为60秒)
   // How long we allow a receiver to run before giving up on it.static final int BROADCAST_FG_TIMEOUT = 10*1000;static final int BROADCAST_BG_TIMEOUT = 60*1000;mFgBroadcastQueue = new BroadcastQueue(this, mHandler,  "foreground", BROADCAST_FG_TIMEOUT, false);mBgBroadcastQueue = new BroadcastQueue(this, mHandler,  "background", BROADCAST_BG_TIMEOUT, true);

所以上面公式翻译过来就是:实际处理BroadcastRecord的起始时间+广播默认的超时时间*广播接收者的数量。话说回来,这个公式为什么要这么设计呢?如果一个前台的广播消息有两个接收者,那么在20秒(2 x 10)之内搞定就可以了,也可能第一个消息执行了15秒,第二个消息执行4.99秒,即使第一消息超过了10秒的规定,也不会出现ANR。但是系统任务繁忙,可能有其他活要干,我们要尽可能的减少ANR的发生,所以前面乘以2倍。

假设现在广播超时还没处理,满足if条件,就会进入,打印Hung broadcast [“+ mQueueName + “] discarded after timeout failure….的log,然后执行 broadcastTimeoutLocked(false)强制停止广播,broadcastTimeoutLocked相关代码代码如下:

    final void broadcastTimeoutLocked(boolean fromMsg) {.....long timeoutTime = r.receiverTime + mTimeoutPeriod;if (timeoutTime > now) {if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Premature timeout ["+ mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "+ timeoutTime);setBroadcastTimeoutLocked(timeoutTime);return;}}.....}

内部调用setBroadcastTimeoutLocked()设置一个延迟消息

 final void setBroadcastTimeoutLocked(long timeoutTime) {if (! mPendingBroadcastTimeoutMessage) {Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);mHandler.sendMessageAtTime(msg, timeoutTime);mPendingBroadcastTimeoutMessage = true;}}

如果广播消息能够处理完毕,就会执行cancelBroadcastTimeoutLocked,将超时的Message移除掉。

final void cancelBroadcastTimeoutLocked() {if (mPendingBroadcastTimeoutMessage) {mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);mPendingBroadcastTimeoutMessage = false;}

如果广播消息没有在timeout时间内处理掉,下面BroadcastHandler发送的消息就会执行。

private final class BroadcastHandler extends Handler {.....@Overridepublic void handleMessage(Message msg) {switch (msg.what) {.....case BROADCAST_TIMEOUT_MSG: {synchronized (mService) {broadcastTimeoutLocked(true);}} break;.....}}}

再次进入broadcastTimeoutLocked方法里面

 final void broadcastTimeoutLocked(boolean fromMsg) {//传进来是ture if (fromMsg) {mPendingBroadcastTimeoutMessage = false;}//队列没有广播处理了,返回if (mOrderedBroadcasts.size() == 0) {return;}long now = SystemClock.uptimeMillis();BroadcastRecord r = mOrderedBroadcasts.get(0);if (fromMsg) {//正在执行dexopt,返回if (mService.mDidDexOpt) {// Delay timeouts until dexopt finishes.mService.mDidDexOpt = false;long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;setBroadcastTimeoutLocked(timeoutTime);return;}//系统还没有进入ready状态if (!mService.mProcessesReady) {// Only process broadcast timeouts if the system is ready. That way// PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended// to do heavy lifting for system up.return;}//如果当前正在执行的receiver没有超时,则重新设置广播超时long timeoutTime = r.receiverTime + mTimeoutPeriod;if (timeoutTime > now) {// We can observe premature timeouts because we do not cancel and reset the// broadcast timeout message after each receiver finishes.  Instead, we set up// an initial timeout then kick it down the road a little further as needed// when it expires.if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Premature timeout ["+ mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "+ timeoutTime);setBroadcastTimeoutLocked(timeoutTime);return;}}//当前正在执行的receiver没有超时,则重新设置广播超时,处理下一条广播BroadcastRecord br = mOrderedBroadcasts.get(0);if (br.state == BroadcastRecord.WAITING_SERVICES) {// In this case the broadcast had already finished, but we had decided to wait// for started services to finish as well before going on.  So if we have actually// waited long enough time timeout the broadcast, let's give up on the whole thing// and just move on to the next.Slog.i(TAG, "Waited long enough for: " + (br.curComponent != null? br.curComponent.flattenToShortString() : "(null)"));br.curComponent = null;br.state = BroadcastRecord.IDLE;processNextBroadcast(false);return;}Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r. receiver+ ", started " + (now - r.receiverTime) + "ms ago");r.receiverTime = now;r.anrCount++;// Current receiver has passed its expiration date.if (r.nextReceiver <= 0) {Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0");return;}ProcessRecord app = null;String anrMessage = null;Object curReceiver = r.receivers.get(r.nextReceiver-1);r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;Slog.w(TAG, "Receiver during timeout: " + curReceiver);logBroadcastReceiverDiscardLocked(r);if (curReceiver instanceof BroadcastFilter) {BroadcastFilter bf = (BroadcastFilter)curReceiver;if (bf.receiverList.pid != 0&& bf.receiverList.pid != ActivityManagerService.MY_PID) {synchronized (mService.mPidsSelfLocked) {app = mService.mPidsSelfLocked.get(bf.receiverList.pid);}}} else {app = r.curApp;}//进程存在,anrMessage赋值if (app != null) {anrMessage = "Broadcast of " + r.intent.toString();}if (mPendingBroadcast == r) {mPendingBroadcast = null;}// Move on to the next receiver.finishReceiverLocked(r, r.resultCode, r.resultData,r.resultExtras, r.resultAbort, false);//处理下一条广播scheduleBroadcastsLocked();if (anrMessage != null) {// Post the ANR to the handler since we do not want to process ANRs while// potentially holding our lock.mHandler.post(new AppNotResponding(app, anrMessage));}}

所以当一个receiver超时后,系统会放弃继续处理它给出ANR提示,并再次调用scheduleBroadcastsLocked(),尝试处理下一个receiver,

private final class AppNotResponding implements Runnable {private final ProcessRecord mApp;private final String mAnnotation;public AppNotResponding(ProcessRecord app, String annotation) {mApp = app;mAnnotation = annotation;}@Overridepublic void run() {//内部创建ANR显示的DialogmService.mAppErrors.appNotResponding(mApp, null, null, false, mAnnotation);}}

4、广播拦截处理分析

广播消息可以有多个接收者,对于有序广播是一个接着一个处理的,优先级高的接收者可以优先执行,并且可以调用BroadcastReceiver的abortBroadcast()方法拦截广播,如果我们在receiver的onReceive()中调用这个方法,那么它后面的接收者就不会收到广播。

public abstract class BroadcastReceiver {private PendingResult mPendingResult;public final void abortBroadcast() {checkSynchronousHint();mPendingResult.mAbortBroadcast = true;}}

把BroadcastReceiver::PendingResult的成员变量mAbortBroadcast设置成true,

 final class Args extends BroadcastReceiver.PendingResult implements Runnable {private Intent mCurIntent;private final boolean mOrdered;private boolean mDispatched;public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,boolean ordered, boolean sticky, int sendingUser) {super(resultCode, resultData, resultExtras,mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,sticky, mIIntentReceiver.asBinder(), sendingUser, intent.getFlags());mCurIntent = intent;mOrdered = ordered;}public void run() {.....try {ClassLoader cl =  mReceiver.getClass().getClassLoader();intent.setExtrasClassLoader(cl);intent.prepareToEnterProcess();setExtrasClassLoader(cl);//设置PendingResult,这个PendingResult中mAbortBroadcast为truereceiver.setPendingResult(this);receiver.onReceive(mContext, intent);} catch (Exception e) {.....}if (receiver.getPendingResult() != null) {//告知AMS处理下一个广播finish();}}}

finish()会告知AMS处理下一个广播,在第一小节已经分析过,最终进入AMS的finishReceiver方法

  public void finishReceiver(IBinder who, int resultCode, String resultData,Bundle resultExtras, boolean resultAbort, int flags) {.....try {boolean doNext = false;BroadcastRecord r;synchronized(this) {BroadcastQueue queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0? mFgBroadcastQueue : mBgBroadcastQueue;r = queue.getMatchingOrderedReceiver(who);if (r != null) {//resultAbort传进来是true,doNext = r.queue.finishReceiverLocked(r, resultCode,resultData, resultExtras, resultAbort, true);}}//调用processNextBroadcast处理广播if (doNext) {r.queue.processNextBroadcast(false);}trimApplications();} finally {Binder.restoreCallingIdentity(origId);}}```  
processNextBroadcast方法中有一个检查广播有没有发送完毕的逻辑。```  do {.....r = mOrderedBroadcasts.get(0);//检查广播有没有发送完,resultAbort为=tureif (r.receivers == null || r.nextReceiver >= numReceivers|| r.resultAbort || forceReceive) {.....//mOrderedBroadcasts里删除广播消息mOrderedBroadcasts.remove(0);r = null;looped = true;continue;}} while (r == null);

当resultAbort为=ture时候,广播消息从mOrderedBroadcasts删除,后面也就收不到广播了。

5、理解粘性广播

sticky广播通过Context.sendStickyBroadcast()函数来发送,用此函数发送的广播会一直滞留,当有匹配此广播的广播接收器被注册后,该广播接收器就会收到此条信息。使用此函数需要发送广播时,需要获得BROADCAST_STICKY权限。粘性广播可以使用广播接收器进行接收,但是正确的接收方式是调用registerReceiver能接受广播,信息将在调用registerReceiver的返回值中给出。对于粘性广播的发送,和普通广播的发送方式是一致的,例子来自与Android 粘性广播StickyBroadcast的使用

private void sendStickyBroadcast(){Intent i = new Intent(); i.setAction(StickyBroadcastReceiver.Action); i.putExtra("info", "sticky broadcast has been receiver"); sendStickyBroadcast(i);Log.i("Other","sticky broadcast send ok!"); 
}

可以使用BroadcastReceiver来接收

public class StickyBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {//收到广播}
}
<!--使用粘性广播发送权限-->
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
IntentFilter intentFilter = new IntentFilter(StickyBroadcastReceiver.Action);Intent data = registerReceiver(null, intentFilter);if(data!=null&&StickyBroadcastReceiver.Action.equals(data.getAction()))  {Toast.makeText(this, data.getStringExtra("info"), Toast.LENGTH_SHORT).show();
}

好了广播的四篇文章写完了,准备在分析一波Service吧

这篇关于Android源码解析四大组件系列(八)---广播几个问题的深入理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130280

相关文章

Python Jupyter Notebook导包报错问题及解决

《PythonJupyterNotebook导包报错问题及解决》在conda环境中安装包后,JupyterNotebook导入时出现ImportError,可能是由于包版本不对应或版本太高,解决方... 目录问题解决方法重新安装Jupyter NoteBook 更改Kernel总结问题在conda上安装了

pip install jupyterlab失败的原因问题及探索

《pipinstalljupyterlab失败的原因问题及探索》在学习Yolo模型时,尝试安装JupyterLab但遇到错误,错误提示缺少Rust和Cargo编译环境,因为pywinpty包需要它... 目录背景问题解决方案总结背景最近在学习Yolo模型,然后其中要下载jupyter(有点LSVmu像一个

解决jupyterLab打开后出现Config option `template_path`not recognized by `ExporterCollapsibleHeadings`问题

《解决jupyterLab打开后出现Configoption`template_path`notrecognizedby`ExporterCollapsibleHeadings`问题》在Ju... 目录jupyterLab打开后出现“templandroidate_path”相关问题这是 tensorflo

如何解决Pycharm编辑内容时有光标的问题

《如何解决Pycharm编辑内容时有光标的问题》文章介绍了如何在PyCharm中配置VimEmulator插件,包括检查插件是否已安装、下载插件以及安装IdeaVim插件的步骤... 目录Pycharm编辑内容时有光标1.如果Vim Emulator前面有对勾2.www.chinasem.cn如果tools工

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

关于最长递增子序列问题概述

《关于最长递增子序列问题概述》本文详细介绍了最长递增子序列问题的定义及两种优化解法:贪心+二分查找和动态规划+状态压缩,贪心+二分查找时间复杂度为O(nlogn),通过维护一个有序的“尾巴”数组来高效... 一、最长递增子序列问题概述1. 问题定义给定一个整数序列,例如 nums = [10, 9, 2

Spring AI Alibaba接入大模型时的依赖问题小结

《SpringAIAlibaba接入大模型时的依赖问题小结》文章介绍了如何在pom.xml文件中配置SpringAIAlibaba依赖,并提供了一个示例pom.xml文件,同时,建议将Maven仓... 目录(一)pom.XML文件:(二)application.yml配置文件(一)pom.xml文件:首