Android基础复习:Service组件详解

news2025/1/20 10:52:32

Android基础复习:Service组件详解

在这里插入图片描述

概况

Service组件是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。

挑重点来说,Service组件的特点包括:

  1. 在后台运行:Service组件可以在后台长时间运行,即使用户关闭了与应用程序的所有交互界面。
  2. 没有UI:Service组件没有用户界面,它们通常用于执行不需要用户交互的任务。
  3. 可以与Activity组件交互:Service组件可以与Activity组件交互,例如通过Broadcast、AIDL等机制实现Activity与Service之间的通信。
  4. 生命周期:Service组件有自己的生命周期,包括onCreate()、onStartCommand()、onBind()和onDestroy()方法,这些方法的调用顺序与Activity组件的生命周期不同。

疑问

相信已经接触到多线程技术的读者一开始肯定会有和我相同的疑问:已经有了多线程技术可以让我们在后台执行任务,那为什么还需要Service组件呢?

这和Android系统对多线程技术的限制有关。在Android中,如果我们需要在后台长时间运行的任务,比如下载文件、播放音乐等等,使用多线程技术可能会受到一些限制,比如系统可能会因为内存不足而杀死线程,或者在某些情况下,应用程序可能会被系统挂起,导致线程无法正常运行。

而Service组件的出现就是为了解决这个问题。Service组件是在后台运行的组件,它可以独立于应用程序的界面进行运行。与多线程技术相比,Service组件的生命周期更加稳定,它拥有自己独立的生命周期,可以在后台长时间运行,即使应用程序被系统挂起,Service组件也可以继续运行。

而且Service组件还可以实现多线程技术无法实现的IPC通信,可以扮演一个本地服务端的角色。

选择服务还是线程

正如我们之前提到的,线程和服务有一定的相似性,那我们在不同场景下该如何选择呢?官方提到了:

“服务是一种即使用户未与应用交互也可在后台运行的组件,因此,只有在需要服务时才应创建服务。”

所以说,如果必须在主线程之外执行操作,但只在用户与您的应用交互时执行此操作,则应创建新线程。毕竟Service组件可以在没有和应用程序交互时继续保持运行。

Service组件的基础使用

由于Service组件也属于四大组件之一,因此其具体创建实际上和Activity相似,创建一个类拓展Service的基类然后在manifest清单文件中声明。Android Studio可以很好地帮助我们解决这个问题。

Service组件的生命周期

前面我们提到,Service有其独立的生命周期。接下来就来看Service的生命周期:
在这里插入图片描述
上面这幅图是从官网上截下来的Service的生命周期图,可以看到我们可以通过两种方法来启动Service的生命周期:startService方法和bindService方法。具体的两种启动方式的细节,我们将在后面详细介绍,这里我们只要知道如果是通过startService启动的,那么Service就会先调用onCreate方法,然后执行onStartCommand回调方法;如果是通过bindService启动的,那么就会先执行onCreate方法接着执行onBind方法,当帮顶接触后则会执行onUnbind方法。

创建我们的Service类

上文提到,我们需要拓展Service的基类来创建Service,实际上Service存在两种基类,分别是IntentService类和Service类,这里我们从简单的先入手,先拓展IntentService类。

拓展IntentService

这是 Service 的子类,其使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,此类为最佳选择。实现 onHandleIntent(),该方法会接收每个启动请求的 Intent,以便您执行后台工作。

上文是官方的简介,从中我们可以知道IntentService的使用场景和特点之一就是串行处理启动请求,无需考虑并发情况,这也是比较简单的情况。接下来我们开始拓展IntentService类:

public class MyIntentService extends IntentService {

    private static final String TAG = "MyIntentService";

    private static final String EVENT_A = "com.example.myService.event_a";
    private static final String EVENT_B = "com.example.myService.event_b";
    private static final String PARAM = "myService.Param";


    //一般我们不需要重写,如果重写了返回值一般情况下也一定是super.onStartCommand
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.d(TAG, "运行在线程: "+Thread.currentThread().getName());
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind: "+"Success");
        return super.onBind(intent);
    }

    //留给外部调用的接口
    public static void startEventA(Context context,String param){
        Intent intent = new Intent(context,MyIntentService.class);
        intent.setAction(EVENT_A);
        intent.putExtra(PARAM,param);
        context.startService(intent);
    }

    public static void startEventB(Context context,String param){
        Intent intent = new Intent(context,MyIntentService.class);
        intent.setAction(EVENT_B);
        intent.putExtra(PARAM,param);
        context.startService(intent);
    }


    public MyIntentService() {
        super("MyIntentService");
    }



    @Override //在onHandleIntent处理事件时会自动创建子线程执行耗时任务
    protected void onHandleIntent(Intent intent) {
        String action = intent.getAction();
        String param = intent.getStringExtra(PARAM);
        switch (action){
            case EVENT_A:
                onHandleEventA(param);
                Log.d(TAG, "onHandleIntentA运行在: "+Thread.currentThread().getName());
                break;
            case EVENT_B:
                onHandleEventB(param);
                Log.d(TAG, "onHandleIntentB运行在: "+Thread.currentThread().getName());
                break;
            default:
                break;
        }
        return ;
    }

    private void onHandleEventA(String para){
        Log.d(TAG, "onHandleEventA: "+para + "运行在:"+Thread.currentThread().getName());
    }

    private void onHandleEventB(String para){
        Toast.makeText(this, para+" 运行在:"+Thread.currentThread().getName(), Toast.LENGTH_SHORT).show();
    }

}

这里我们只是通过startService方法来启动的,因为这种方式比较简单,bindService的方法我们会在后面介绍。

在这个IntentService中,我们必须要实现的方法只有它的构造方法和onHandleIntent方法,构造方法中必须调用它超类的构造方法并传入一个字符串用于指定IntentService的工作线程的名称。onIntentService方法则是用来处理各种请求的回调方法。

在上面的示例中我主要是通过Intent的Action值来区分需要处理的事件,并留出了两个静态的公共方法用于给外部进行调用,这里还打印出了执行方法所在的回调:
在这里插入图片描述
通过打印出来的信息可以发现其他方法仍将运行在主线程中,但是onHandleIntent方法将会单独运行在一个子线程中,IntentService会自动创建一个工作线程来处理耗时的任务,具体来说,IntentService类会执行以下操作:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
  • 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
  • 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。
  • 提供 onBind() 的默认实现(返回 null)。
  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。

除了构造方法还有onHandleIntent方法之外,我们也可以实现别的回调方法,比如说onCreate,onStartCommand,onDestroy等,但是一定要确保调用超类实现,这可以防止出现一些不必要的错误,就拿onStartCommand方法来说,我们最后就一定要返回它的超类实现,即如何将Intent传递给onHandleIntent:

    //一般我们不需要重写,如果重写了返回值一般情况下也一定是super.onStartCommand
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.d(TAG, "运行在线程: "+Thread.currentThread().getName());
        return super.onStartCommand(intent, flags, startId);
    }

另外这里还有一个小小的补充:onHandleEventB方法中使用了Toast,而该方法是在IntentService的子线程中执行的,但是Toast本身只能在主线程中显示,因此这段代码在运行时可能会崩溃。但实际上在Android 9.0(API 28)之后,Google增加了对Toast的线程检查,如果在非主线程中使用Toast,系统会自动将其转移到主线程中进行显示,从而避免了崩溃问题。

拓展Service类

拓展IntentService类的话,我们要做的工作就会相对少一点,但是IntentService只是支持串行执行任务,如果我们有一些多线程实现的要求的话,就要拓展Service类。接下来我们来拓展Service类:

public class MyService extends Service {

    private static final String TAG = "MyService";
    private Looper serviceLooper;
    private ServiceHandler serviceHandler;
    private static final int SIT_A = 1;
    private static final int SIT_B = 2;
    private static final String SIT_A_STRING = "com.situationA";
    private static final String SIT_B_STRING = "com.situationB";

    private final class ServiceHandler extends Handler{
        public ServiceHandler(Looper looper){
            super(looper);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case SIT_A:
                    Toast.makeText(MyService.this, "Situation A", Toast.LENGTH_SHORT).show();
                    break;
                case SIT_B:
                    Toast.makeText(MyService.this, "Situation B", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }

            stopSelf(msg.arg1);//根据启动的Id值来决定是否结束Service
        }
    }

    @Override
    public void onCreate() {
        //设置工作线程的优先级
        HandlerThread handlerThread = new HandlerThread("NormalServiceThread", Process.THREAD_PRIORITY_BACKGROUND);
        handlerThread.start();//启动工作线程

        //绑定Looper和Handler
        serviceLooper = handlerThread.getLooper();
        serviceHandler = new ServiceHandler(serviceLooper);

    }

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
       return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "服务已启动", Toast.LENGTH_SHORT).show();
        Message message = serviceHandler.obtainMessage();
        message.arg1 = startId;//设置arg1为启动Id
        switch (intent.getAction()){
            case SIT_A_STRING:
                message.what = SIT_A;
                break;
            case SIT_B_STRING:
                message.what = SIT_B;
                break;
            default:
                break;
        }
        serviceHandler.sendMessage(message);//发送Message

        return START_STICKY;
    }

    public static void StartSITA(Context context){
        Intent intent = new Intent(context,MyService.class);
        intent.setAction(SIT_A_STRING);
        context.startService(intent);
    }

    public static void StartSITB(Context context){
        Intent intent = new Intent(context,MyService.class);
        intent.setAction(SIT_B_STRING);
        context.startService(intent);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: ");
    }
}

这个示例是根据官方的示例改的,由于Service和IntentService类不同,Service类并不是自带工作线程的,它的任务默认是执行在主线程中, 所以我们需要自己创建工作线程,这里我们用HandleThread来用作工作线程,这个类与普通Thread的区别就是其内置了MessageQueue和Looper,使用起来会比较方便一点。具体的事件处理是交由Handler在工作线程中执行的。这里虽然我们需要自建工作线程,看起来比较麻烦,但是它也赋予了我们一定的自由性,我们可以在Service中创建多个线程来达到并行处理的效果。

其次我们重写了onStartCommand方法,这里需要特别说明的就是onStartCommand方法的返回值,该方法必须要返回一个整型数,整型数是一个值,用于描述系统应如何在系统终止服务的情况下继续运行服务。IntentService 的默认实现会为您处理此情况,但我们可以对其进行修改。从 onStartCommand() 返回的值必须是以下常量之一:

  1. START_NOT_STICKY:如果系统杀死了服务,那么除非有新的 Intent 请求启动此服务,否则系统不会重启此服务。
  2. START_STICKY:如果系统杀死了服务,那么系统会尝试重新启动此服务,但不会重新传递最后一个 Intent, 直到调用了 stopSelf() 或 stopService() 方法停止服务。
  3. START_REDELIVER_INTENT:如果系统杀死了服务,那么系统会重新启动服务,并将之前的 Intent 对象重新传递给服务的 onStartCommand() 方法,让服务继续处理之前的请求。

开启服务

上面已经提到过开启服务的生命周期主要是两种方式:启动服务和绑定服务,接下来我们就来介绍这两种方法。

启动服务

启动服务主要使用显示Intent启动Service(也不允许隐式启动Service就是了),具体的启动方法和启动Activity是一样的。我们可以将Intent传递给startService或者startForegroundService(启动前台服务,我们后面再讲),从Activity或其他应用组件启动服务。

Android系统会调用服务的onStartCommand方法,并向其传递Intent,从而指定要启动的服务。
其实我们在上面的示例中也演示到了:

  Intent intent = new Intent(context,MyIntentService.class);
  intent.setAction(EVENT_B);
  intent.putExtra(PARAM,param);
  context.startService(intent);

startService() 方法会立即返回,并且 Android 系统会调用服务的 onStartCommand() 方法。如果服务尚未运行,则系统首先会调用 onCreate(),然后调用 onStartCommand()。多个服务启动请求会导致多次对服务的 onStartCommand() 进行相应的调用。但是,如要停止服务,只需一个服务停止请求(使用 stopSelf() 或 stopService())即可。

绑定服务

绑定服务是客户端-服务器接口中的服务器。借助绑定服务,组件(例如 Activity)可以绑定到服务、发送请求、接收响应,以及执行进程间通信 (IPC)。绑定服务通常只在为其他应用组件提供服务时处于活动状态,不会无限期在后台运行。

绑定服务中用到的方法就是bindService方法,bindService方法需要接受三个参数,分别是指定了服务的Intent。还有实现了ServiceConnection接口的一个对象,该对象是用来管理Service的连接情况的,ServiceConnection有两个必须要实现的方法:分别是onServiceConnected方法和onServiceDisconnection方法,前者是当连接成功建立起来时会执行的回调方法,后者是当连接丢失时要执行的回调方法。

我们先通过IntentService来实现我们的绑定服务类:

public class MyBindService extends IntentService {


    private static final String TAG = "MyBindService";
    public MyBindService() {
        super("MyBindService");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "绑定成功---Service");
        return new MyBinder();//返回业务接口给客户端使用
    }

    @Override
    protected void onHandleIntent(Intent intent) {

    }

    public static class MyBinder extends Binder{
        //一些服务端的业务接口

        public void Show(Context context){
            Toast.makeText(context, "测试接口Show", Toast.LENGTH_SHORT).show();
        }
    }

    public static class MyConnection implements ServiceConnection{

        public MyBinder mBinder = null;
        public Context mContext = null;

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected: "+"已经成功连接服务");
            mBinder = (MyBinder) service;
            mBinder.Show(mContext);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected: "+"服务连接已丢失");
        }
    }


}

当其他组件调用了bindService方法就会执行onBind回调方法,在这个类中我还实现了两个内部类,一个就是之前说的ServiceConnection的实现类,另一个就是Binder的子类。这个Binder类就是用于进行Service和其绑定组件的通信的,我们可以实现自己的Binder类,并在这个类中将一些业务接口给暴露出来给与其绑定的组件来使用,那具体是在哪里将这个Binder类传递给与其绑定的组件呢?一般是在ServiceConnection实现类的onServiceConnected回调方法中处理的。我们可以看上文的onServiceConnected方法中有一个IBinder参数,这个参数来自于哪里呢?实际上是来自于Service的onBind方法中返回的Binder类。

在这里插入图片描述
接下来给出在被绑定组件里的调用代码:

   binding.btBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyBindService.class);
                MyBindService.MyConnection connection = new MyBindService.MyConnection();
                connection.mContext = MainActivity.this;
                bindService(intent,connection,Context.BIND_AUTO_CREATE);
            }
        });

bindService的第三个方法是一个标志位,用于指定绑定服务的行为,具体有以下几种取值:

  • Context.BIND_AUTO_CREATE: 如果服务不存在,则系统会自动创建一个服务并绑定,当所有绑定的客户端都解除绑定后,系统会销毁服务。
  • Context.BIND_DEBUG_UNBIND: 表示如果客户端和服务端连接出现异常情况,例如客户端崩溃或服务端崩溃等,会自动调用onUnbind()方法来解绑服务,并会调用ServiceConnection的onBindingDied()方法通知客户端连接出现了异常。
  • Context.BIND_NOT_FOREGROUND: 表示服务不应该是一个前台服务。这是一个暗示,通知系统这个服务不应该在前台运行,因此系统会在内存不足时更容易终止它。
  • Context.BIND_ABOVE_CLIENT: 表示绑定时服务应该处于客户端之上,即服务的优先级应该高于客户端。
  • Context.BIND_ALLOW_OOM_MANAGEMENT: 表示服务可以被系统进行OOM内存管理,当系统内存不足时,系统会根据LRU算法来回收服务进程的内存。

一般情况下,我们使用Context.BIND_AUTO_CREATE来指定绑定服务的行为,表示在客户端与服务端进行绑定时,如果服务不存在则会自动创建服务。

另外,关于绑定服务,还有AIDL和Messenger的使用,但是由于这两个内容涉及到IPC的内容,我会在其他文章里单独总结。

使用前台服务

实际上服务可以分为两种类型,第一种就是我们之前提到的普通服务,还有一种就是前台服务。前台服务是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。前台服务必须为状态栏提供通知,将其放在运行中的标题下方。这意味着除非将服务停止或从前台移除,否则不能清除该通知。

具体来说什么是前台服务呢?拿QQ音乐来说,当我们返回桌面时QQ音乐仍可以实现在后台播放,并且在通知栏里会有一项专门显示QQ音乐,我们还可以通过这个通知栏来切换歌曲。

在这里插入图片描述

限制前台服务

由于前台服务收到系统的限制较小,所以我们在使用时需要自己对其加以限制,避免消耗过大的内存造成卡顿:

只有当应用执行的任务需供用户查看(即使该任务未直接与应用交互)时,您才应使用前台服务。因此,前台服务必须显示优先级为 PRIORITY_LOW 或更高的状态栏通知,这有助于确保用户知道应用正在执行的任务。如果某操作不是特别重要,因而您希望使用最低优先级通知,则可能不适合使用服务;相反,您可以考虑使用计划作业。

每个运行服务的应用都会给系统带来额外负担,从而消耗系统资源。如果应用尝试使用低优先级通知隐藏其服务,则可能会降低用户正在主动交互的应用的性能。因此,如果某个应用尝试运行拥有最低优先级通知的服务,则系统会在抽屉式通知栏的底部调用出该应用的行为。

创建前台服务

在创建前台服务之前我们可能需要申请一下相关的权限:如果应用面向 Android 9(API 级别 28)或更高版本并使用前台服务,则其必须请求 FOREGROUND_SERVICE 权限。这是一种普通权限,因此,系统会自动为请求权限的应用授予此权限。

如果面向 API 级别 28 或更高版本的应用试图创建前台服务但未请求 FOREGROUND_SERVICE,则系统会抛出 Security-Exception。

所以我们需要在Manifest清单文件中添加一下权限:

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

第一行代码是申请前台服务的权限,第二行代码是申请发送通知的权限。接着给出完整的代码:

public class NotificationService extends Service {

    private static final String TAG = "NotificationService";
    private static final int NOTIFICATION_ID = 1;
    private static final String CHANNEL_ID = "MyNotificationService";
    NotificationManager manager;
    NotificationChannel channel;
    @Override
    public void onCreate() {
        manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            channel = new NotificationChannel(CHANNEL_ID,CHANNEL_ID,NotificationManager.IMPORTANCE_HIGH);
            manager.createNotificationChannel(channel);//创建通知渠道
            Toast.makeText(this, "创建完毕", Toast.LENGTH_SHORT).show();
        }
    }

    public NotificationService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    Log.d(TAG, "run: ");
                }
            }
        }).start();

        Intent intent1 = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent1,PendingIntent.FLAG_IMMUTABLE);

        Notification notification = new NotificationCompat.Builder(this,CHANNEL_ID)
                .setContentTitle("前台服务正在执行")
                .setContentText("测试样例一")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setContentIntent(pendingIntent)
                .build();

        startForeground(NOTIFICATION_ID,notification);//提升为前台服务

        return START_STICKY;
    }


}

这里需要一些Notification的知识,具体可以看我的关于notification的博客→Notification的使用

既然想要显示通知的话还是得先按照创建通知的流程来,我们在Service的创建时来初始化通知渠道,以便为后面的显示通知做准备。接下来我们创建一个Intent和PendingIntent,这主要是为了我们后面点击前台服务的通知时能够跳转到主Activity的界面。

接着创建Notification,设置需要显示的标题和正文等内容。最后最重要的就是startForeground方法,这个方法将会接受两个参数,第一个为需要显示通知的通知渠道ID,第二个就是我们之前创建的Notification。此通知必须拥有 PRIORITY_LOW 或更高的优先级。此方法会创建后台服务,但它会向系统发出信号,表明服务会将自行提升至前台。创建服务后,该服务必须在五秒内调用自己的 startForeground() 方法

至此,Service的基础内容就比较详细地介绍完了,所有的示例demo的代码就放在我的github里了:→示例代码✿

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【动手学深度学习】现代卷积神经网络汇总

文章目录 1 LeNet2 AlexNet3 VGG4 NiN5 GoogLeNet6 ResNet7 DenseNet 本文为作者阅读学习李沐老师《动手学深度学习》一书的阶段性读书总结&#xff0c;原书地址为&#xff1a;Dive into Deep Learning。 1 LeNet 网络结构 实现代码 net nn.Sequential(nn.Conv2d(1, 6, kern…

springboot+vue社区医院管理服务系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的社区医院管理服务系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1…

从C出发 32 --- 自定义数据类型(上)

字节 指的就是 byte , 而一个 byte 占用 8 位&#xff0c; 在 C 语言里面有没有 直接提供 表示 8 位的数据类型&#xff1f; char 最小的整型&#xff0c;就可以表示 8 位的数据类型 char 的取值范围 -128 - 127 一个字节的取值范围是 0 - 25…

RK3308B部署mobilenetv2_ssdlite

目录 1. 在PC端运行mobilenetv2_ssdlite模型1.1 安装NCNN和Opencv1.1.1 安装NCNN1.1.2 安装Opencv 1.2 运行mobilenetv2_ssdlite模型 2. 交叉编译部署到RK3308B板子上并运行模型2.1 交叉编译NCNN和Opencv2.1.1 交叉编译Opencv2.1.2 交叉编译ONNX 2.2 交叉编译mobilenetv2_ssdli…

总结850

学习目标&#xff1a; 月目标&#xff1a;5月&#xff08;张宇强化前10讲&#xff0c;背诵15篇短文&#xff0c;熟词僻义300词基础词&#xff09; 周目标&#xff1a;张宇强化前3讲并完成相应的习题并记录&#xff0c;英语背3篇文章并回诵 每日必复习&#xff08;5分钟&#…

深度学习环境配置系列文章(五):配置Docker深度学习开发环境

深度学习环境配置系列文章目录 第一章 专业名称和配置方案介绍 第二章 Anaconda配置Python和PyTorch 第三章 配置VS Code和Jupyter的Python环境 第四章 配置Windows11和Linux双系统 第五章 配置Docker深度学习开发环境 第五章文章目录 深度学习环境配置系列文章目录前言一, Do…

AI工具分享第二期:11款国内外AI绘画提示词工具整理

工具整理自未来百科AI工具箱&#xff0c;更多提示词工具可自行寻找 Midjourney中文教程 Midjourney 学习导航 PromptHero 描述 通过 DALL-E、Stable Diffusion、Midjourney 等 AI 模型搜索数以百万计的艺术图像… PromptDen AI 在线社区促使爱好者联系、协作和分享想法。 …

支付系统设计三:渠道网关设计07-后置处理

文章目录 前言一、订单数据更新1. 领域模型更新服务工厂2. 聚合创建工厂2.1 数据库更新服务2.2 聚合创建工厂 二、限流渠道入队三、异步通知1. 判断是否需要通知2. 组装异步通知报文3. 获取异步通知协议类型3. 异步通知 总结 前言 本篇将继业务处理之后的后置处理逻辑进行介绍&…

瑞吉外卖 - 后台系统退出功能(4)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

瑞吉外卖 - 项目介绍(1)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

CSS盒子模型、表格标签(table)、表单标签(form)

盒子&#xff1a;页面中所有的元素&#xff08;标签&#xff09;&#xff0c;都可以看做是一个 盒子&#xff0c;由盒子将页面中的元素包含在一个矩形区域内&#xff0c;通过盒子的视角更方便的进行页面布局 盒子模型组成&#xff1a;内容区域&#xff08;content&#xff09;…

Qt扫盲-QScatterSeries理论总结

QScatterSeries理论总结 一、概述二、使用三、扩展四、扩展使用1.创建描述散点图对象2. 对散点图像添加值3. 自定义散点4. 将绘图设备与散点图对象联系5. 设置坐标轴6. 将绘图设备与GUI控件绑定并显示 一、概述 QScatterSeries 类以散点图的形式呈现数据。散点数据在图表上显示…

基于jdk1.8的Java服务监控和性能调优

JVM的参数类型 X参数 非标准参数-Xint: 解释执行-Xcomp: 第一次使用就编译成本地代码-Xmixed: JVM自己来决定是否编译成本地代码 默认使用的是mixed mode 用的不多, 只需要做了解, 用的比较多的是XX参数 XX参数 非标准化参数相对不稳定主要用来JVM调优和Debug Boolean: …

Vivado综合属性系列之一 ASYNC_REG

目录 一、属性简介 二、示例 2.1 工程说明 ​ ​2.2 工程代码 ​ ​2.3 生效确认 一、属性简介 ASYNC_REG属性的作用对象为寄存器&#xff0c;寄存器添加该属性后&#xff0c;即表明寄存器的数据输入口D接收的是来自异步时钟触发器的数据或是该寄存器在一个同步链中属于…

【CSS系列】第九章 · CSS定位和布局

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

Uni-app 离线打包 apk

Uni-app 离线打包 apk 1. Android Studio 下载 Android Studio官网 2. HBuilderX下载 HBuilderX下载 3. App离线SDK下载 Android 离线SDK - 正式版 下载后解压文件&#xff0c;将 HBuilder-Integrate-AS 重命名 build-template 并拷贝到一个专门打包用的文件夹下作为打包…

一行代码绘制高分SCI限制立方图

一、概述 Restricted cubic splines (RCS)是一种基于样条函数的非参数化模型&#xff0c;它可以可靠地拟合非线性关系&#xff0c;可以自适应地调整分割结点。在统计学和机器学习领域&#xff0c;RCS通常用来对连续型自变量进行建模&#xff0c;并在解释自变量与响应变量的关系…

抑梯度异常初始化参数(防止梯度消失和梯度爆炸)

这里设置3种参数初始化的对比&#xff0c;分别是&#xff1a;全初始化为0、随机初始化、抑梯度异常初始化。 首先是正反向传播、画图、加载数据所需的函数init_utils.py&#xff1a; # -*- coding: utf-8 -*-import numpy as np import matplotlib.pyplot as plt import sklea…

双层优化入门(3)—基于智能优化算法的求解方法(附matlab代码)

前面两篇博客介绍了双层优化的基本原理和使用KKT条件求解双层优化的方法&#xff0c;以及使用yalmip工具箱求解双层优化的方法&#xff1a; 双层优化入门(1)—基本原理与求解方法 双层优化入门(2)—基于yalmip的双层优化求解(附matlab代码) 除了数学规划方法之外&#xff0c;…

springboot+vue大学生体质测试管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的大学生体质测试管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xf…