Android 跨进程通信aidl及binder机制详解(二)

跨进程通信流程

通过上文可发现,要实现跨进程通信,需要客户端、服务端、客户端与服务端通信规约也就是通过aidl生成的java接口。下面用一个图来表述:

在这里插入图片描述
对于上图的调用过程,我们做一下解释:上图中列了几个对象的关联关系,其中有客户端aidl、binder驱动程序、server端aidl。

在客户端aidl文件中有asInterface方法,这个方法最终是客户端程序绑定服务端service返回的服务端代理对象,并派生出了Proxy对象给客户端调用。所以在这之前服务端需要向Binder驱动程序注册服务,并产生一个代理保存到binder中,等待客户端调用。所以需要在服务端建立一个Service的子类来完成getFile 与sendData方法的实现,这个过程就是实现接口方法并向客户端公开接口的过程。其中IMyservice 服务端的service,并在此service中实现getFile与sendData方法。

当客户端拿到Proxy代理对象后,会调用具体的方法比如getFile,调用getFile时,会向binder驱动程序中查找此方法,找到了则由binder驱动程序转发给服务端,服务端在Stub中的OnTransact方法中,根据方法code找到本地的binder线程池中的实体,并调用IMyservice中的getFile方法,调用完成后,并将结果返回binder驱动,再由binder驱动程序唤醒客户端接收服务端返回的结果。

到此,整个客户端和服务端的大概的通信过程就结束了。下面结合上图的分析,我们先来创建服务端示例。

服务端实现及向客户端公开接口

服务端的实现过程很简单,就是在服务端创建一个Serivce的派生类,如要实现 .aidl 生成的接口,就必须扩展生成的 Binder 接口(例如,IMyAidlInterface.Stub),并实现继承自 .aidl 文件的方法。

下面的案例,为了模拟跨进程的情况,我们先在同一个应用程序中开启一个新的进程来进行测试。

在Android studio中创建一个Service,并通过匿名的方式创建一个IMyAidlInterface.Stub实例,并将此实例返回给客户端了,以便客户端能与服务进行交互。代码如下所示:

public class MyAidlBinderService extends Service {

    private static final String TAG = "MyAidlBinderService" ;
    private static int count = 1 ;
    private static int rcount = 1 ;
    public MyAidlBinderService() {
    }

    private final IMyAidlInterface.Stub sBinder =  new IMyAidlInterface.Stub() {
        //此方法是创建aidl文件时自动生成的,这里可忽略。
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public String getFile() throws RemoteException {
            Log.i(TAG ,"客户端调用了getFile方法");
            return "file - " + count;
        }

        @Override
        public String sendData(String message) throws RemoteException {
            Log.i(TAG ,"客户端调用了sendData方法,其中传来的信息为 "+message+ " 当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlBinderService.this));
            return "服务端收到消息" + rcount + "次";
        }
    };

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

上述代码中,sBinder 是 Stub 类的一个实例(一个 Binder),其定义了服务的远程过程调用 (RPC) 接口。会向客户端公开此实例。在为服务实现接口后,您需要向客户端公开该接口,以便客户端进行绑定。为了您的服务公开该接口,扩展 Service 并实现 onBind(),从而返回实现生成的 Stub 的类实例(如前文所述)。

在实现 AIDL 接口时,您应注意遵守以下规则:

  • 由于无法保证在主线程上执行传入调用,因此您一开始便需做好多线程处理的准备,并对您的服务进行适当构建,使其达到线程安全的标准。
  • 默认情况下,RPC 调用是同步调用。如果您知道服务完成请求的时间不止几毫秒,则不应从 Activity 的主线程调用该服务,因为这可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 通常,您应从客户端内的单独线程调用服务。
  • 您引发的任何异常都不会回传给调用方。

服务注册

为了跨进程调用服务端,需要在AndroidMenifest清单文件中注册service

       <service android:name=".MyAidlBinderService"
            android:enabled="true"
            android:exported="true"
            android:process=":myAidl"/>

为了让service运行在不同的进程中,添加了process属性,进程名称为包名+myAidl 。

客户端调用

上述服务端代码已经写好了,现在要做的是在当前app中写一个客户端调用方法来远程调用服务端的方法:这个非常简单,创建一个测试activity即可,如下所示

public class MyAidlBinderActivity extends AppCompatActivity {

    private IMyAidlInterface iMyAidlInterface;

    private static final String TAG = MyAidlBinderActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_aidl_binder);
        Button bindService = (Button)findViewById(R.id.aidlBindService);
        bindService.setOnClickListener(binderService);
        Button getFile = (Button)findViewById(R.id.aidl_getFile);
        getFile.setOnClickListener(c_getFile);
        Button sendData = (Button)findViewById(R.id.aidl_sendData);
        sendData.setOnClickListener(c_sendData);
    }



   private ServiceConnection mServiceConnection =  new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            /**
             *    //当与服务建立连接时调用,为我们提供了可用于与服务交互的服务对象。
             *     // 我们通过AIDL接口与服务通信,因此从原始服务对象获得该接口的客户端表示。
             */
            iMyAidlInterface =  IMyAidlInterface.Stub.asInterface(service);

        }
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    /**
     * 这里创建一个点击事件才触发绑定服务:
     * 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
     */
   private View.OnClickListener binderService = new View.OnClickListener(){

       @Override
       public void onClick(View v) {
           Intent intent = new Intent(MyAidlBinderActivity.this, MyAidlBinderService.class);
           bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
       }
   };

    /**
     * 这里创建一个点击事件才触发绑定服务:
     * 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
     */
    private View.OnClickListener c_getFile = new View.OnClickListener(){

        @Override
        public void onClick(View v) {
            if (iMyAidlInterface!=null){

                try {
                    String res = iMyAidlInterface.getFile();
                    Log.i(TAG,"服务端调用getFile返回的值为  " + res);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };

    /**
     * 这里创建一个点击事件才触发绑定服务:
     * 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
     */
    private View.OnClickListener c_sendData = new View.OnClickListener(){

        @Override
        public void onClick(View v) {
            if (iMyAidlInterface!=null){

                try {
                    String res = iMyAidlInterface.sendData("Hello Sever!");
                    Log.i(TAG,"服务端调用sendData返回的值为  " + res + "  当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlBinderActivity.this)) ;
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };
}

上述代码的调用过程非常简单,下面贴出来一个案例效果图:
在这里插入图片描述
日志打印效果,为了区分跨进程,打印日志中专门打印出了进程的名字,如下所示

2023-03-22 17:21:21.443 6883-6899/com.cop.ronghw.study_android_exact I/MyAidlBinderService: 客户端调用了getFile方法
2023-03-22 17:21:21.443 6830-6830/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 服务端调用getFile返回的值为  file - 1

2023-03-22 17:21:28.894 6883-6899/com.cop.ronghw.study_android_exact I/MyAidlBinderService: 客户端调用了 sendData方法,其中传来的信息为 Hello Sever!当前进程com.cop.ronghw.study_android_exact:myAidl
2023-03-22 17:21:28.899 6830-6830/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 服务端调用sendData返回的值为  服务端收到消息1次当前进程com.cop.ronghw.study_android_exact

到此,我们就完成了客户端与服务端的简单通信功能。对于上述代码我们来总结一下:

  1. 当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法所返回的 binder 实例。

  2. 客户端还必须拥有接口类的访问权限,因此如果客户端和服务在不同应用内,则客户端应用的 src/ 目录内必须包含 .aidl 文件(该文件会生成 android.os.Binder 接口,进而为客户端提供 AIDL 方法的访问权限)的副本。

  3. 当客户端在 onServiceConnected() 回调中收到 IBinder 时,它必须调用 IMyAidlInterface.Stub.asInterface(service),以将返回的参数转换成 IMyAidlInterface 类型。

通过 IPC 传递对象

内容:如何传递aidl自定义类型如对象实体,

客户端服务端对象的传递

上文的案例中,讲述的是在客户端服务器之间传递的数据都是aidl语法中的基本类型数据,那么如何跨进程传递自定义类型的数据呢?比如传递一个对象。假设现在有一个这样的场景,如果在客户端想把车辆的信息传输给服务端保存,客户端能查询到所有服务端车辆的信息列表,针对这两个方法我们来使用代码模拟一下。

具体步骤如下:

您可以通过 IPC 接口,将某个类从一个进程发送至另一个进程。不过,您必须确保 IPC 通道的另一端可使用该类的代码,并且该类必须支持 Parcelable 接口。支持 Parcelable 接口很重要,因为 Android 系统能通过该接口将对象分解成可编组至各进程的原语。

如要创建支持 Parcelable 协议的类,您必须执行以下操作:

  • 让您的类实现 Parcelable 接口。

  • 实现 writeToParcel,它会获取对象的当前状态并将其写入 Parcel。

  • 为您的类添加 CREATOR 静态字段,该字段是实现 Parcelable.Creator 接口的对象。

  • 最后,创建声明 Parcelable 类的 .aidl 文件。
    如果您使用的是自定义编译进程,请勿在您的构建中添加 .aidl 文件。此 .aidl 文件与 C 语言中的头文件类似,并未经过编译。

  • AIDL 会在其生成的代码中使用这些方法和字段,以对您的对象进行编组和解编。

1、定义车辆实体类实现Parcelable接口

根据场景描述,我们需要定义一个类Car,来存放车辆的信息并实现Parcelable接口,这样做的目的就是为了能够序列化与反序列化。Car类如下所示:

public class Car implements Parcelable {

    //定义车的属性(品牌、颜色、价格)
    private String brand;
    private String color;
    private int price;

    public Car(Parcel in) {
        //一开始忘记写这句话,sendData后导致服务端接收的车辆信息为空
        readFromParcel(in);
    }
    public Car(){}

    public static final Creator<Car> CREATOR = new Creator<Car>() {
        @Override
        public Car createFromParcel(Parcel in) {
            return new Car(in);
        }

        @Override
        public Car[] newArray(int size) {
            return new Car[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    // 客户端或服务端序列化时使用
    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeString(brand);
        out.writeString(color);
        out.writeInt(price);
    }

    //客户端或服务端反序列化使用
    public void readFromParcel(Parcel in) {
        brand=in.readString();
        color=in.readString();
        price=in.readInt();
    }

    public String getBrand() {
        return brand;
    }

    public String getColor() {
        return color;
    }

    public int getPrice() {
        return price;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", color='" + color + '\'' +
                ", price=" + price +
                '}';
    }
}
2、创建Car.aidl文件

ICar.aidl 文件的作用是将 Car 类引入使得其他的 AIDL 文件其中可以使用 Car 对象。

// Car.aidl
package com.cop.ronghw.study_android_exact;

//声明Car,这样AIDL就可以找到它并知道它实现了parcelable协议。
parcelable Car;
3、创建ICarInterface.aidl文件

定义接口方法的aidl文件,并在里面使用import关键字引入Car类对象

// ICarInterface.aidl
package com.cop.ronghw.study_android_exact;

// Declare any non-default types here with import statements
import com.cop.ronghw.study_android_exact.Car;

interface ICarInterface {

   //发送车辆信息给服务端
   void sendData(in Car car);
   //从服务端获取所有的车辆信息
   List<Car> getCars();
}
4、编写服务端代码

上一步编写完aidl文件后,通过项目构建生成ICarInterface.java文件。接下来编写服务端代码,服务端代码与本文的第一个案例大同小异,基本处理逻辑是一致的,下面贴代码:

public class MyAidlCarBinderService extends Service {
    private static final String TAG =  MyAidlCarBinderService.class.getSimpleName();
    private static List<Car> cars ;
    public MyAidlCarBinderService() {
    }


    private final ICarInterface.Stub sBinder =  new ICarInterface.Stub() {

        @Override
        public void sendData(Car car) throws RemoteException {
            Log.i(TAG,"本次客户端存车=="+ car.toString());
            if (cars == null){
                cars = new ArrayList<>();
            }
            if (car != null) {
                String brand =  car.getBrand();
                cars.add(car);
            }
        }

        @Override
        public List<Car> getCars() throws RemoteException {
            Log.i(TAG,"客户端调用获取车辆信息列表,当前库存有"+cars.size()
                    +"车,车辆信息列表如下"+cars.toString() +
                    "当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlCarBinderService.this));
            return cars;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return sBinder;
    }
}
5、编写客户端代码

代码写到了第一个案例的代码文件中了,下面贴一下代码案例

public class MyAidlBinderActivity extends AppCompatActivity {

    //服务端进程1
    private IMyAidlInterface iMyAidlInterface;

    //服务端进程2
    private ICarInterface iCarInterface;

    private static final String TAG = MyAidlBinderActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_aidl_binder);
        Button bindService = (Button)findViewById(R.id.aidlBindService);
        bindService.setOnClickListener(binderService);
        Button getFile = (Button)findViewById(R.id.aidl_getFile);
        getFile.setOnClickListener(c_getFile);
        Button sendData = (Button)findViewById(R.id.aidl_sendData);
        sendData.setOnClickListener(c_sendData);

        //测试通过IPC传递对象
        Button sendCar = (Button)findViewById(R.id.aidl_Car_sendData);
        sendCar.setOnClickListener(c_car_send);
        Button getCar = (Button)findViewById(R.id.aidl_Car_getCars);
        getCar.setOnClickListener(C_getCars);

    }



   private ServiceConnection mServiceConnection =  new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            /**
             *    //当与服务建立连接时调用,为我们提供了可用于与服务交互的服务对象。
             *     // 我们通过AIDL接口与服务通信,因此从原始服务对象获得该接口的客户端表示。
             *     //
             */
            iMyAidlInterface =  IMyAidlInterface.Stub.asInterface(service);
            iCarInterface = ICarInterface.Stub.asInterface(service);


        }
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    /**
     * 这里创建一个点击事件才触发绑定服务:
     * 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
     * 同时会开启两个进程
     */
   private View.OnClickListener binderService = new View.OnClickListener(){

       @Override
       public void onClick(View v) {
           Intent intent = new Intent(MyAidlBinderActivity.this, MyAidlBinderService.class);
           bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);

           Intent intentCar = new Intent(MyAidlBinderActivity.this, MyAidlCarBinderService.class);
           bindService(intentCar,mServiceConnection, Context.BIND_AUTO_CREATE);

       }
   };

    /**
     * 这里创建一个点击事件才触发绑定服务:
     * 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
     */
    private View.OnClickListener c_getFile = new View.OnClickListener(){

        @Override
        public void onClick(View v) {
            if (iMyAidlInterface!=null){

                try {
                    String res = iMyAidlInterface.getFile();
                    Log.i(TAG,"服务端调用getFile返回的值为  " + res);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };


    /**
     * 这里创建一个点击事件才触发绑定服务:
     * 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
     */
    private View.OnClickListener c_sendData = new View.OnClickListener(){

        @Override
        public void onClick(View v) {
            if (iMyAidlInterface!=null){

                try {
                    String res = iMyAidlInterface.sendData("Hello Sever!");
                    Log.i(TAG,"服务端调用sendData返回的值为  " + res + "  当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlBinderActivity.this)) ;
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };


    /**
     * 这里创建一个点击事件才触发绑定服务:
     * 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
     * 存车
     */
    private View.OnClickListener c_car_send = new View.OnClickListener(){

        @Override
        public void onClick(View v) {
            if (iCarInterface!=null){

                try {
                    Car car = new Car();
                    car.setBrand("xfl-tjz");
                    car.setColor("火山灰");
                    car.setPrice(200000);
                    iCarInterface.sendData(car);
                    Log.i(TAG,"客户端已发送数据"+car.toString()+"当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlBinderActivity.this)) ;
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };

    /**
     * 这里创建一个点击事件才触发绑定服务:
     * 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
     * 取车
     */
    private View.OnClickListener C_getCars = new View.OnClickListener(){

        @Override
        public void onClick(View v) {
            if (iCarInterface!=null){
                try {
                    List<Car> res = iCarInterface.getCars();
                    Log.i(TAG,"服务端调用getCars返回的值为  " + res.size() + res.toString());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };


}
6、测试,并日志输出

点击并进行多次测试,日志输出如下:

23-03-23 17:23:37.469 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000} 
2023-03-23 17:23:37.472 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:14.200 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 客户端调用获取车辆信息列表,当前库存有1车,车辆信息列表如下[Car{brand='xfl-tjz', color='火山灰', price=200000}]当前进程为com.cop.ronghw.study_android_exact:carAidl
2023-03-23 17:24:14.201 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 服务端调用getCars返回的值为  1[Car{brand='xfl-tjz', color='火山灰', price=200000}]
2023-03-23 17:24:14.202 17100-17100/com.cop.ronghw.study_android_exact I/Choreographer: Skipped 184 frames!  The application may be doing too much work on its main thread.
2023-03-23 17:24:21.129 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000}
2023-03-23 17:24:21.130 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:23.425 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000}
2023-03-23 17:24:23.428 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:24.302 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000}
2023-03-23 17:24:24.303 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:24.540 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000}
2023-03-23 17:24:24.547 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:25.406 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 客户端调用获取车辆信息列表,当前库存有5车,车辆信息列表如下[Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}]当前进程为com.cop.ronghw.study_android_exact:carAidl
2023-03-23 17:24:25.408 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 服务端调用getCars返回的值为  5[Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}]

总结

至此,通过IPC传递对象的案例就演示完成了,在尝试的过程中需要注意的问题:

  • 创建Car类及Car.aidl文件时,要保证二者的路径一致。
  • Car.aidl 及Car.java二者的名字必须一致,即不能出现Car.java和Car1.aidl配对方式的命名,否则会出现Import 时找不到Car类的错误。

本篇总结

本篇文章主要通过案例代码的形式来模拟客户端和服务端的通信过程。到目前为止,大家看了几篇文章可能基本上了解了如何编写一个aidl文件,并完成客户端与服务端的通信,但是内部的binder原理我们并没有介绍,只是知道都是binder驱动在起决定性的作用。那么binder的分层是如何的?在java中如何是调用到native层实现的客户端与服务端的通信的?服务端又是如何将信息返回给客户端的?在下一篇文章中,我们会通过源码的形式来为大家解读,让大家争取搞清楚。

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

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

相关文章

windows安装部署node.js并搭建Vue项目

一、官网下载安装包 官网地址&#xff1a;https://nodejs.org/zh-cn/download/ 二、安装程序 1、安装过程 如果有C/C编程的需求&#xff0c;勾选一下下图所示的部分&#xff0c;没有的话除了选择一下node.js安装路径&#xff0c;直接一路next 2、测试安装是否成功 【winR】…

Windows系统x86机器安装(麒麟、统信)ARM系统详细教程

本次介绍在window系统x86机器上安装国产系统 arm 系统的详细教程。 注:ubuntu 的arm系统安装是一样的流程。 1.安装环境准备。 首先,你得有台电脑,配置别太差,至少4核8G内存,安装window10或者11都行(为啥不能是Window7,你要用也不是不行,你先解决win7补丁更新问题)。…

目标检测——车辆障碍物数据集

检测道路上障碍物对于道路安全、自动驾驶技术的发展以及交通流畅性都具有重要性和意义。以下是这些重要性和意义的详细解释&#xff1a; 道路安全 从道路安全的角度来看&#xff0c;小障碍物可能给行驶中的车辆带来潜在风险。例如&#xff0c;一个丢弃在道路上的轮胎或纸箱可能…

36.云原生之SpringCloud+k8s实践

云原生专栏大纲 文章目录 SpringCloudk8s介绍spring-cloud-kubernetes服务发现配置管理负载均衡选主 spring-cloud-bookinfo案例构建项目环境配置namespace部署与验证productpagegatewaybookinfo-admindetailsratingsreviewsreviews-v1reviews-v2 总结 SpringCloudk8s介绍 ht…

配置Windows和Linux之间的WireGuard对接

正文共&#xff1a;1197 字 20 图&#xff0c;预估阅读时间&#xff1a;2 分钟 今天简单测试一下WireGuard在Windows系统和Linux系统之间的对接情况。首先下载Windows安装包&#xff0c;这个安装包的轻量化程度让我大为震惊&#xff0c;可以说是第一次看见这么小的安装包&#…

Flask学习笔记

不论POST请求还是GET请求都支持在 URL 中添加变量&#xff0c;可以选择性的加上一个转换器&#xff0c;为变量指定数据类型。 history_alarm.route(/test/<int:post_id>, methods[POST]) def test(post_id):print(f"参数类型为&#xff1a;{type(post_id)}")i…

语音编码的区别和使用场景

语音编码标准各自在音质、数据压缩率、对带宽的需求、计算复杂性、延迟、鲁棒性以及专利许可费用等方面有所不同。这些差异决定了它们在不同场景下的使用。那常见语音编码标准的区别和典型使用场景&#xff1a; 1. G.711&#xff1a; 区别&#xff1a;使用脉冲编码调制&#…

IDEA开发环境热部署

开发环境热部署 在实际的项目开发调试过程中会频繁地修改后台类文件&#xff0c;导致需要重新编译重新启动&#xff0c;整个过程非常麻烦&#xff0c;影响开发效率。Spring Boot提供了spring-boot-devtools组件&#xff0c;使得无须手动重启SpringBoot应用即可重新编译、启动项…

水电表远程集中抄表管理系统

水电表远程集中抄表管理系统是当前水电行业智能化发展的关键技术之一&#xff0c;为水电企业和用户提供了便捷、高效的抄表管理解决方案。该系统结合了远程监控、自动抄表、数据分析等多种功能&#xff0c;实现了水电抄表的智能化和精准化&#xff0c;为用户节省了大量人力物力…

【自然语言处理三-self attention自注意是什么】

自然语言处理三-自注意力 self attention 自注意力是什么&#xff1f;自注意力模型出现的原因是什么&#xff1f;词性标注问题解决方法1-扩展window&#xff0c;引用上下文解决方法2-运用seq2seq架构新问题来了&#xff1a;参数量增加、无法并行的顽疾 自注意力self attention模…

JDK安装及环境变量配置(保姆级教程)

什么是JDK&#xff1f; JDK&#xff08;Java Development Kit&#xff09;是Java开发工具包的缩写 它是Java开发人员必备的软件包之一。JDK包含了用于编译、调试和运行Java程序的各种工具和库。通过安装JDK&#xff0c;开发人员可以开始编写、编译和运行Java应用程序、Applet和…

UE5 UE4 自定义插件自动开启关联插件(plugin enable)

在我们自己编写UE4、UE5的插件时&#xff0c;常常需要开启相关联的插件进行功能编写。 例如&#xff1a;UE4/5 批量进行贴图Texture压缩、修改饱和度_ue4批量修改纹理大小-CSDN博客 而让插件使用者每次使用时&#xff0c;依次进行开启其他相关联插件确实有些麻烦。 如何只需要…

【医学影像】LIDC-IDRI数据集的无痛制作

LIDC-IDRI数据集制作 0.下载0.0 链接汇总0.1 步骤 1.合成CT图reference 0.下载 0.0 链接汇总 LIDC-IDRI官方网址&#xff1a;https://www.cancerimagingarchive.net/nbia-search/?CollectionCriteriaLIDC-IDRINBIA Data Retriever 下载链接&#xff1a;https://wiki.canceri…

麒麟银河操作系统V10部署ffmpeg

麒麟银河操作系统V10部署ffmpeg 部署ffmpeg用来处理视频的各种操作 想使用ffmpeg&#xff0c;要先安装nasm&#xff0c;yasm&#xff0c;x264之后&#xff0c;否则会报错 nkvers 查看麒麟操作系统版本 cat /proc/version #查看linux版本信息 uname -a #查看linux版本和内核…

Seawater resistant ADS-B Antenna for off-shore use

目录 Introduction Technical data Introduction This ADS-B antenna, made of V4A (1.4571 316Ti) stainless special steel, is suitable for off-shore use and includes mounting kit. Condensation in the antenna itself is excluded by a hermetically sealed seal. …

弱结构化日志 Flink SQL 怎么写?SLS SPL 来帮忙

作者&#xff1a;潘伟龙&#xff08;豁朗&#xff09; 背景 日志服务 SLS 是云原生观测与分析平台&#xff0c;为 Log、Metric、Trace 等数据提供大规模、低成本、实时的平台化服务&#xff0c;基于日志服务的便捷的数据接入能力&#xff0c;可以将系统日志、业务日志等接入 …

Spring Boot到底是如何进行自动配置的?

【1】从 spring.factories 配置文件中加载 EnableAutoConfiguration 自动配置类&#xff09;,获取的自动配 置类如图所示。 【2】若 EnableAutoConfiguration 等注解标有要 exclude 的自动配置类&#xff0c;那么再将这个自动配置类 排除掉&#xff1b; 【3】排除掉要 exclude …

【Azure 架构师学习笔记】-Azure Synapse -- Link for SQL 实时数据加载

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Synapse】系列。 前言 Azure Synapse Link for SQL 可以提供从SQL Server或者Azure SQL中接近实时的数据加载。通过这个技术&#xff0c;使用SQL Server/Azure SQL中的新数据能够几乎实时地传送到Synapse&#xff08;…

猫头虎分享:Element UI Element Plus组件的安装及使用

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

FinalShell控制远程Linux服务器(首先得自己已购买好Linux服务器并安装了对应的系统,这里是安装的centos系统)

1、电脑上需要安装FinalShell软件 可以到分享的链接中下载软件&#xff0c;然后双击点击下一步安装即可 链接&#xff1a;https://share.weiyun.com/Y6TrdDHp 密码&#xff1a;gbvyg62、建立远程连接 3、输入连接信息 4、显示连接主机成功&#xff0c;表示远程进入 5、输入…