Android Service 启动流程

在早些年学习Android的时候,对Service有过总结,但是主要是如何去使用,注意事项,startService和bindService的区别。

Android Service_public int onstartcommand(intent intent, int flags-CSDN博客

但是今天从源码来总结下framework层的启动流程大致是什么样的。

一、startService()

平时,在我们的activity里,我们通过startService去启动一个service服务。

1.context.startService

使用例子:

//testActivity
    @Override
    public void onClick(View v) {
        Intent it=new Intent(this, SimpleService.class);
        switch (v.getId()){
            case R.id.startService:
                startService(it);
                break;
            case R.id.stopService:
                stopService(it);
                break;
        }

这儿,实际上是调用的context上下文去调用的这个方法,那我们去看下里面的代码

@Override
public ComponentName startService(Intent service) {
    warnIfCallingFromSystemProcess();
    return startServiceCommon(service, false, mUser);
}

private ComponentName startServiceCommon(Intent service, boolean requireForeground,
        UserHandle user) {
    try {
        validateServiceIntent(service);
        service.prepareToLeaveProcess(this);
        ComponentName cn = ActivityManager.getService().startService(
            mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                        getContentResolver()), requireForeground,
                        getOpPackageName(), user.getIdentifier());
       ...
        return cn;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

2.ActivityManager.getService().startService()

可以看到:核心代码为ActivityManager.getService().startService()

在 Activity 中使用的 startService 方法是定义在 Context 的抽象类中,它的真正实现类是 ContextImpl,所以先进入 ContextImpl 类。先从startService开始,然后进入本类的startServiceCommon方法,并最终调用ActivityManagerNative.getDefault()对象的 startService 方法。ActivityManager.getService()获取到IActivityManager对象,并且是通过单利模式创建的。

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

看下ActivityManager.getService()这个如果看过app启动流程就知道,这玩意儿在创建application和activity时也看到过。这儿用到了binder实现进程间通信,最后走到了AMS里面

看下ActivityManagerService中startService()代码

这里面走到了startServiceLocked()方法,其中mServices是ActiveServices。

ActiveServices这里面进行了以下步骤:

1.通过 retrieveServiceLocked 方法来解析 service 这个 Intent,就是解析前面我们在 AndroidManifest.xml 定义的 Service 标签的 intent-filter 相关内容,然后将解析结果放在 res.record 中。

2.调用 startServiceInnerLocked 方法。

在startServiceInnerLocked 方法中会调用 bringUpServiceLocked 方法。

3.bringUpServiceLocked方法中,当 Service 所在的进程存在时,将调用realStartServiceLocked 方法来启动 Service,否则的话调用 startProcessLocked 方法来启动新进程。

情况一:realStartServiceLocked()

这里面会调用 app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState);

这里会走到ApplicationThread去sendMessage,最后去创建service,执行onCreate方法,后面有详细说明。因为它运行的进程已存在,就直接去创建了,如果不存在,接着往下走

情况二:startProcessLocked() 

 4.startProcessLocked()是ActivityManagerSevice中的方法,

//在ActivityManagerService类中
private final void startProcessLocked(ProcessRecord app, String hostingType, 
	String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {

    boolean isActivityProcess = (entryPoint == null);
    if (entryPoint == null) 
        entryPoint = "android.app.ActivityThread";
    checkTime(startTime, "startProcess: asking zygote to start proc");
    //通过 processName,uid 等启动新进程
    Process.start(entryPoint, 
			app.processName, uid, uid, gids, debugFlags, mountExternal, 
			app.info.targetSdkVersion, app.info.seinfo, requiredAbi, 
			instructionSet, app.info.dataDir, entryPointArgs);
}

//在Process类中
public static final ProcessStartResult start(final String processClass,
                              final String niceName,
                              int uid, int gid, int[] gids,
                              int debugFlags, int mountExternal,
                              int targetSdkVersion,
                              String seInfo,
                              String abi,
                              String instructionSet,
                              String appDataDir,
                              String invokeWith,
                              String[] zygoteArgs) {
    return zygoteProcess.start(processClass, niceName, uid, gid, gids,
                debugFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
}

从这儿,启动了一个新的进程,创建一个新的进程过后就会走到熟悉的ActivityThread类的main方法里面去

3.启动新进程去ActivityThread

熟悉的源码

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    CloseGuard.setEnabled(false);
    // 初始化应用中需要使用的系统路径
    Environment.initForCurrentUser();
    Looper.prepareMainLooper();
    //创建ActivityThread 对象
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

大致流程:

  • 1.绑定应用进程到ActivityManagerService
    • 在 Android 应用程序中,每一个进程对应一个 ActivityThread 实例,然后这里创建了 ActivityThread 对象并调用了其 attach 方法
  • 2.主线程Handler消息处理
    • 启动looper轮询器,所以在activity或者service创建handler对象时,不需要手动调用looper。原因就是在这里
    • 首先Looper.prepareMainLooper();是为主线程创建了Looper,然后thread.getHandler();是保存了主线程的Handler,最后Looper.loop();进入消息循环。

其余的不用看了,主要看thread.attach(false)

  • main()方法通过thread.attach(false)绑定应用进程。ActivityManagerNative通过getDefault()方法返回ActivityManagerService实例,ActivityManagerService通过attachApplication将ApplicationThread对象绑定到ActivityManagerService,而ApplicationThread作为Binder实现ActivityManagerService对应用进程的通信和控制
  • 在ActivityManagerService内部,attachApplication实际是通过调用attachApplicationLocked实现的,这里采用了synchronized关键字保证同步。
//ActivityThread.java
private void attach(boolean system) {
    final IActivityManager mgr = ActivityManagerNative.getDefault();
    try {
        //这里调用了 ActivityManagerProxy.attachApplication 方法。
        mgr.attachApplication(mAppThread);
    } catch (RemoteException ex) {
        // Ignore
    }
}

//ActivityManagerService.java 然后看看attachApplication方法
@Override
public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid);
        Binder.restoreCallingIdentity(origId);
    }
}

往里面走,里面的代码很多,这儿根据我看过的源码,分为三个方向

1.关于application,他会走到 thread.bindApplication去绑定application,执行后续操作(创建application,执行onCreate生命周期)

2.关于activity,他会走到mStackSupervisor.attachApplicationLocked(app)执行有关activity的操作(走到scheduleLaunchActivity)

上面两个步骤,具体可看我之前的博客 Android App启动流程和源码详解-CSDN博客

3.关于service,他会走到 didSomething |= mServices.attachApplicationLocked(app, processName);执行service的后续操作。

关键代码:

private final boolean attachApplicationLocked(IApplicationThread thread,int pid) {
    if (app.instr != null) {//app的
        thread.bindApplication(processName, appInfo, providers,
                app.instr.mClass,
                profilerInfo, app.instr.mArguments,
                app.instr.mWatcher,
                app.instr.mUiAutomationConnection, testMode,
                mBinderTransactionTrackingEnabled, enableTrackAllocation,
                isRestrictedBackupMode || !normalMode, app.persistent,
                new Configuration(getGlobalConfiguration()), app.compat,
                getCommonServicesLocked(app.isolated),
                mCoreSettingsObserver.getCoreSettingsLocked(),
                buildSerial);
    } else {
        thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                null, null, null, testMode,
                mBinderTransactionTrackingEnabled, enableTrackAllocation,
                isRestrictedBackupMode || !normalMode, app.persistent,
                new Configuration(getGlobalConfiguration()), app.compat,
                getCommonServicesLocked(app.isolated),
                mCoreSettingsObserver.getCoreSettingsLocked(),
                buildSerial);
    }
    
    // See if the top visible activity is waiting to run in this process...
    if (normalMode) {
        try {//activity的
            if (mStackSupervisor.attachApplicationLocked(app)) {
                didSomething = true;
            }
        } catch (Exception e) {
            Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
            badApp = true;
        }
    }

    // Find any services that should be running in this process...
    if (!badApp) {
        try {//sevice的
            didSomething |= mServices.attachApplicationLocked(app, processName);
            checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
        } catch (Exception e) {
            Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
            badApp = true;
        }
    }
    
}

上面第三步,mServices.attachApplicationLocked看源码,会走到app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState);方法,哎,你看他又回去了,走到了ApplicationThread里去了,ApplicationThread是Activity的内部类(上面不创建进程也是调用的词方法),我们点进去看下源码:

public final void scheduleCreateService(IBinder token,  ServiceInfo info, 
	CompatibilityInfo compatInfo, int processState) {
    updateProcessState(processState, false);
    CreateServiceData s = new CreateServiceData();
    s.token = token;
    s.info = info;
    s.compatInfo = compatInfo;

    sendMessage(H.CREATE_SERVICE, s);
}

哎哟喂,殊途同归啊,和application和activity一样,最后都用到了用handler去处理消息。

来嘛,看下handler的handleMessage()方法

...
case CREATE_SERVICE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
                    handleCreateService((CreateServiceData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
...

继续往里走:handleCreateService((CreateServiceData)msg.obj);

1.通过类加载器 ClassLoader 来加载 Service 对象

2.创建一个 ContextImpl 对象,每个 Activity 和 Service 都有一个 Context 对象。

private void handleCreateService(CreateServiceData data) {
    Service service = null;
    try {
        //(1)通过类加载器来加载 Service 对象
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = (Service) cl.loadClass(data.info.name).newInstance();
    } catch (Exception e) {
        //......
    }
    //(2)这里创建 ContextImpl 对象
    ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
    context.setOuterContext(service);
    Application app = packageInfo.makeApplication(false, mInstrumentation);
    service.attach(context, this, data.info.name, data.token, app,ActivityManagerNative.getDefault());
    //(3)这里调用 Service 的 onCreate 方法
    service.onCreate();
    mServices.put(data.token, service);
}

哎,这不就来了吗,最后执行了service的onCreate()生命周期。

二、bindService()

1.ContextImpl类中bindService()

具体使用:

TestActivity.java
private void test(){
    Intent intent = new Intent(this, XXXService.class);
    // bindService 的具体实现在 ContextImpl
    // BIND_AUTO_CREATE 参数具体使用的代码 ActivityServices
    bindService(intent, conn, BIND_AUTO_CREATE);
}

private ServiceConnection conn = new ServiceConnection() {  
    @Override  
    public void onServiceConnected(ComponentName name, IBinder service) {  
       // 绑定成功
    }  
    @Override  
    public void onServiceDisconnected(ComponentName name) { 
      // 绑定结束 
    }
}

bindService源码:

@Override
public boolean bindService(Intent service, ServiceConnection conn,
        int flags) {
    // mMainThread.getHandler(),传入的 handle 是主线程的 Handle
    return bindServiceCommon(service, conn, flags, mMainThread.getHandler(),
            Process.myUserHandle());
}

private boolean bindServiceCommon(Intent service, ServiceConnection conn, 
	int flags, Handler handler, UserHandle user) {
    IServiceConnection sd;
    if (mPackageInfo != null) {
        // 1,将传入的 ServiceConnection 转化为 IServiceConnection 返回
        // mPackgeInfo 是 LoadedApk
        sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);
    }
    validateServiceIntent(service);
    try {
        IBinder token = getActivityToken();
        ...
        // 2,Binder 调用 AMS 的 bindService 方法,下面具体分析
        int res = ActivityManagerNative.getDefault().bindService(
            mMainThread.getApplicationThread(), getActivityToken(), service,
            service.resolveTypeIfNeeded(getContentResolver()),
            sd, flags, getOpPackageName(), user.getIdentifier());
        return res != 0;
    } 
    //...
}

getServiceDispatcher方法

public final IServiceConnection getServiceDispatcher(ServiceConnection c, 
	Context context, Handler handler, int flags) {

    synchronized (mServices) {
        LoadedApk.ServiceDispatcher sd = null;
        // private final ArrayMap<Context,
        // ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
        // 根据当前的 Context 获取 ArrayMap<ServiceConnection,  LoadedApk.ServiceDispatcher>
        ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
        if (map != null) {
            // 如果存在,尝试根据当前的 ServiceConnection 获取 ServiceDispatcher
            sd = map.get(c);
        }
        if (sd == null) {
            // 如果与 ServiceConnection 对应的 ServiceDispatcher 不存在,创建一个保存了当前 
			// ServiceConnection 的 ServiceDispatcher 对象,
            // 并将之前传入的主线的 Handle 保存,同时创建一个 InnerConnection 对象保存
            sd = new ServiceDispatcher(c, context, handler, flags);
            if (map == null) {
                map = new ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
                mServices.put(context, map);
            }
            // 将该 ServiceConnection 与 ServiceDispatcher 关系保存
            map.put(c, sd);
        } else {
            // 如果最开始就获取到 ServiceDispatcher,比如多次 bindService,
            // 就会调用 ServiceDispatcher 的 validate 判断此次 bindService 是否合法
            // validate 的判断逻辑比较简单:
			// 1.判断当前的 context 是否和之前 bindService 的一样 
			// 2.判断当前 handler 是否是主线程的 handle
            // 以上两个条件都满足的情况下正常执行,反之抛出相应的异常
            sd.validate(context, handler);
        }
        return sd.getIServiceConnection();
    }
}

主要看ActivityManagerService.bindService()

2.ActivityManagerService.bindService()

public int bindService(IApplicationThread caller, IBinder token, Intent service, 
	String resolvedType, IServiceConnection connection, 
	int flags, String callingPackage, int userId) throws TransactionTooLargeException {

    //...
    synchronized(this) {
        // 调用 ActiveServices 的 bindServiceLocked 方法
        return mServices.bindServiceLocked(caller, token, service,
                resolvedType, connection, flags, callingPackage, userId);
    }
}

看到这里面调用了ActiveServices 的 bindServiceLocked,往里走:这里面代码长,但是主要调用是这样的:ActiveServices.bindServiceLocked() -> bringUpServiceLocked() -> realStartServiceLocked()

看realStartServiceLocked方法,刚才在我们startService的时候也调用了这个方法,但是因为它是bind的,所以有些许不同

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, 
	boolean execInFg) throws RemoteException {
    //...
    try {
        // 第一步,调用 ApplicationThread 的 scheduleCreateService 方法,
		// 之后会实例化 Service 并调用 Service 的 onCreate 方法,这里的过程跟上面 startService 中一样。
        // 不会调用 onStartCommand
        app.thread.scheduleCreateService(r, r.serviceInfo, 
				mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
    } 
    //...
    // 第二步,调用 requestServiceBindingLocked
    requestServiceBindingLocked(r, execInFg);
    updateServiceClientActivitiesLocked(app, null, true);

    // 第三步
    // If the service is in the started state, and there are no
    // pending arguments, then fake up one so its onStartCommand() will
    // be called.
    if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, 
			r.makeNextStartId(), null, null));
    }
    // StartItem 的 taskRemoved 如果是 false 的话,
	// 调用下面方法会调用 Service 的 onStartCommand
    sendServiceArgsLocked(r, execInFg, true);
}

private final boolean requestServiceBindingLocked(ServiceRecord r, 
	IntentBindRecord i, boolean execInFg, boolean rebind) 
	throws TransactionTooLargeException {
    if ((!i.requested || rebind) && i.apps.size() > 0) {
        try {
            // 调用 ApplicationThread 的 scheduleBindService 方法
            r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                    r.app.repProcState);
        } 
    }
    return true;
}

3.ApplicationThread.scheduleBindService()方法

来来来,看下ApplicationThread.scheduleBindService()方法

  • 调用 ApplicationThread 的 scheduleBindService,scheduleBindService 通过 mH 发送一个 H.BIND_SERVICE 消息,mH 收到该消息调用 handleBindService(BindServiceData data)。

 

 private void handleBindService(BindServiceData data) {
        Service s = mServices.get(data.token);
        if (DEBUG_SERVICE)
            Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind);
        if (s != null) {
            try {
                data.intent.setExtrasClassLoader(s.getClassLoader());
                data.intent.prepareToEnterProcess();
                try {
                    if (!data.rebind) {
                   // 调用 Service 的 onBind,返回给客户端调用的 Binder
                        IBinder binder = s.onBind(data.intent);
                // 调用 AMS 的 publishService,进而通知客户端连接成功
                        ActivityManager.getService().publishService(
                                data.token, data.intent, binder);
                    } else {
                        s.onRebind(data.intent);
                        ActivityManager.getService().serviceDoneExecuting(
                                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                    }
                    ensureJitEnabled();
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to bind to service " + s
                                    + " with " + data.intent + ": " + e.toString(), e);
                }
            }
        }
    }

 

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

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

相关文章

六西格玛培训的讲师应该具备哪些能力?

六西格玛培训的讲师作为专业知识的传授者和实践经验的分享者&#xff0c;其能力水平的高低直接决定了培训效果的好坏。那么&#xff0c;一个优秀的六西格玛培训讲师应该具备哪些能力呢&#xff1f;深圳天行健企业管理咨询公司解析如下&#xff1a; 首先&#xff0c;六西格玛培训…

HT46R002 贴片 SOP8 经济型AD型OTP MCU单片机芯片

HT46R002在智能家居中的具体应用案例可以包括以下几个方面&#xff1a; 1. 智能照明控制&#xff1a;可以用于控制LED灯的亮度和色温&#xff0c;甚至可以通过手机APP远程控制开关和调节灯光效果。 2. 环境监测&#xff1a;用于监测室内温度、湿度、空气质量等&#xff0c;当检…

四川易点慧电商抖音小店:引领潮流,打造电商新标杆

在数字化浪潮席卷全球的今天&#xff0c;电子商务以其独特的魅力和优势&#xff0c;正逐渐成为推动经济发展的重要力量。四川易点慧电子商务有限公司抖音小店&#xff0c;作为电商领域的一股新生力量&#xff0c;以其创新的经营理念和卓越的服务品质&#xff0c;迅速赢得了市场…

解析智慧物流园区系统的多方位优势

智慧物流园区系统是基于物联网、大数据、人工智能等先进技术的应用系统&#xff0c;旨在实现物流园区的高效、智能化管理。随着物流行业的快速发展&#xff0c;传统物流园区已经无法满足日益增长的需求。智慧物流园区系统的出现填补了现有物流园区管理的空白&#xff0c;带来了…

香橙派AI Pro测评--ROS及激光SLAM

文章目录 前言一、外形与质感二、软件测评1. 系统界面2. ROS安装3. ROS节点测试4. SLAM算法测试 总结 前言 今天刚收到了官方寄来的“香橙派 AI Pro”开发板&#xff0c;这篇文章将会对香橙派 AI Pro的外形、质感、运行速度进行一个测评&#xff0c;然后我会在这个开发板上安装…

0基础学习小红书博主IP特训营,37天 教你从小白到KOL(13节)

课程内容&#xff1a; 1 第一课:如何做好博主账号定位 .mp4 2 第一课作业,html 3 第二课:如何打造小红书爆款笔记(一)_.mp4 4 第二课:如何打造小红书爆款笔记(二).mp4 5 第二课作业,html 6 第三课:如何高效搭建选题库 .mp4 7 第三课作业,html 8 第四课:破解流量玄学&am…

新零售数据中台:打造智能商业运营的核心引擎_光点科技

随着数字化转型的浪潮席卷全球&#xff0c;新零售行业正在经历一场前所未有的革新。在这一过程中&#xff0c;“新零售数据中台”逐渐成为企业构建智能商业运营的核心引擎。本文将重点介绍新零售数据中台的概念、其在新零售中的作用&#xff0c;以及如何通过数据中台实现商业价…

UIAbility的使用

UIAbility概述 UIAbility是一种包含用户界面的应用组件&#xff0c;主要用于和用户进行交互。UIAbility也是系统调度的单元&#xff0c;为应用提供一系列的窗口&#xff0c;应用在这些窗口里绘制用户交互界面。 每一个UIAbility实例&#xff0c;都对应于一个最近任务列表中的任…

【外汇天眼】市场如战场:交易中的攻防艺术

交易的成功如同生活&#xff0c;急功近利反而有害无益。在交易中&#xff0c;许多投资者常常面临亏损&#xff0c;急于挽回损失&#xff0c;频繁操作&#xff0c;结果却往往是越亏越多。交易需要耐心&#xff0c;不能急于一时&#xff0c;更不能与市场赌气。交易和生活一样&…

832. 翻转图像 - 力扣

1. 题目 给定一个 n x n 的二进制矩阵 image &#xff0c;先 水平 翻转图像&#xff0c;然后 反转 图像并返回 结果 。 水平翻转图片就是将图片的每一行都进行翻转&#xff0c;即逆序。 例如&#xff0c;水平翻转 [1,1,0] 的结果是 [0,1,1]。 反转图片的意思是图片中的 0 全部被…

软件项目管理 - 作业集合

软件项目管理 - 作业集合 作业一 1、项目与日常运作的主要区别有哪些&#xff1f; 项目&#xff1a;为提供一项独特产品、服务或成果所做的临时性努力 运作&#xff1a;连续不断周而复始的活动 项目是一次性的&#xff0c;日常运作是重复进行的&#xff1b; 项目是以目标为导…

揭秘成绩等级背后的逻辑:小明的语文分数转换记

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、成绩等级转换规则 三、小明的语文成绩转换过程 四、总结与展望 一、引言 在…

2024 年“泰迪杯”A 题:生产线的故障自动识别与人员配置--第四题(用遗传算法解决生产线排班问题--matlab代码)

问题背景&#xff1a; 问题四&#xff1a;根据实际情况&#xff0c;现需要扩大生产规模&#xff0c;将生产线每天的运行时间从 8 小时增加 到 24 小时不间断生产&#xff0c;考虑生产线与操作人员的搭配&#xff0c;制定最佳的操作人员排班方案&#xff0c;要求满足以下条件&am…

香橙派Orange AI Pro 初体验

什么是香橙派 &#xff1f; 香橙派&#xff08;Orange Pi&#xff09;是深圳市迅龙软件有限公司旗下的开源产品品牌。它专注于为全球个人和企业提供高性价比的开源硬件、开源软件以及OEM/ODM服务。香橙派已经迭代了30多款产品&#xff0c;形成了涵盖开源硬件、开源软件、开源芯…

vue3主题切换按钮与功能实现

代码: <template><div class"slideThree"><label class"theme-switch"><inputtype"checkbox"class"checkbox"v-model"isChecked"change"setTheme"id"slideThree"name"check…

三品软件:打造高效安全的图文档管理体系

在数字化转型的浪潮中&#xff0c;工程设计单位和企业设计部门面临着电子图文档管理的巨大挑战。随着电子图纸和文档数量的激增&#xff0c;如何有效组织、管理和共享这些资源&#xff0c;成为提升工作效率和保障信息安全的关键。本文将探讨当前图文档管理面临的问题&#xff0…

html+CSS部分基础运用7

项目1 设计简易灯箱画廊 1.实验所需素材 在trees文件夹中提供一个MP3文件和18个JPG文件&#xff0c;设计页面时可以使用。 2.编程实现简易灯箱画廊&#xff0c;鼠标单击任一个图像超链接&#xff0c;在底部浮动框架中显示大图像&#xff0c;效果如图4-1所示的页面。 图4-1 简…

香橙派 AIpro开发板开箱测评(代码开源)

前言&#xff1a;有幸能够收到一块梦寐以求的 AI 边缘计算开发板 OrangePi AIpro&#xff0c;非常感谢官方大大给予的宝贵机会。OrangePi AIpro是香橙派官方跟华为昇腾合作的新一代边缘计算产品&#xff0c;其使用华为昇腾 AI 技术路线&#xff0c;搭配集成图像处理器&#xff…

颈椎引起的头晕,背后的秘密震惊你!

颈椎引起的头晕相对来说还是比较少见的。国外呢一些文献基本上你就见不到颈性眩晕这个词。 有一个病叫弓猎人综合征&#xff0c;可能和颈椎有关系&#xff0c;就是在军队上&#xff0c;军人在拉弓的时候出现眩晕这种情况&#xff0c;后来发现在旋转颈部的时候&#xff0c;压迫到…

今日好料推荐(Altium Designer + 仿真器驱动)

今日好料推荐&#xff08;Altium Designer 仿真器驱动&#xff09; 参考资料在文末获取&#xff0c;关注我&#xff0c;获取优质资源。 Altium Designer Altium Designer 是一种高度集成的电子设计自动化 (EDA) 软件工具&#xff0c;广泛应用于电子电路和印刷电路板 (PCB) …