Android Binder机制浅谈以及使用Binder进行跨进程通信的俩种方式(AIDL以及直接利用Binder的transact方法实现)

news2024/11/24 0:18:26

Binder机制学习

Binder机制是Android进行IPC(进程间通信)的主要方式

Binder跨进程通信机制:基于C/S架构,由Client、Server、ServerManager和Binder驱动组成。 进程空间分为用户空间和内核空间。用户空间不可以进行数据交互;内核空间可以进行数据交互,所有进程共用 一个内核空间 Client、Server、ServiceManager均在用户空间中实现,而Binder驱动程序则是在内核空间中实现的;

·为何新增Binder来作为主要的IPC方式

Android也是基于Linux内核,Linux现有的进程通信手段有管道/消息队列/共享内存/套接字/信号量。

既然有现有的IPC方式,为什么重新设计一套Binder机制呢?

主要是出于以上三个方面的考量:

1、效率:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度 分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷 贝到接收方的缓存区,一共两次拷贝。

一次数据传递需要经历:用户空间 –> 内核缓存区 –> 用户空间,需要2次数据拷贝,这样效率不高。 

而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同 一块物理地址的,节省了一次数据拷贝的过程 : 共享内存不需要拷贝,Binder的性能仅次于共享内存。 
2、稳定性:上面说到共享内存的性能优于Binder,那为什么不采用共享内存呢,因为共享内存需要处理并发同 步问题,容易出现死锁和资源竞争,稳定性较差。 Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较 好。
3、安全性:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制 为每个进程分配了UID/PID,且在Binder通信时会根据UID/PID进行有效性检测。

2. binder是什么?

img

从进程间通信的角度看,Binder 是一种进程间通信的机制;

从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象(Binder类 IBinder);

从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理

从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会自动完成代理对象和本地对象之间 的转换。 从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁

3.Binder 跨进程通信机制 模型

·模型原理图:Binder 跨进程通信机制 模型 基于 Client - Server 模式

img

· 模型组成角色说明

img

4.Binder驱动的作用 & 原理:

img

模型工作原理:

img

模型原理步骤说明

img

注意:

Client进程、Server进程 & Service Manager 进程之间的交互 都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互

原因:Client进程、Server进程 & Service Manager进程属于进程空间的用户空间,不可进行进程间交互
Binder驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互
Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)

所以,在进行跨进程通信时,开发者只需自定义Client & Server 进程 并 显式使用上述3个步骤,最终借助 Android的基本架构功能就可完成进程间通信
# Binder请求的线程管理
Server进程会创建很多线程来处理Binder请求

Binder模型的线程管理 采用Binder驱动的线程池,并由Binder驱动自身进行管理,而不是由Server进程来管理的

一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。

所以,在进程间通信时处理并发问题时,如使用ContentProvider时,它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程同时工作

利用binder进行通信的实现代码Demo:

1. 直接利用Binder的transact实现:

server端:(注意,要在注册清单中注册,并且标注进程号(在其他进程中运行))

public class IPCService extends Service {
private static final String DESCRIPTOR = "IPCService";
private final String[] names = {"B神","艹神","基神","J神","翔神"};
private MyBinder mBinder = new MyBinder();
private class MyBinder extends Binder {
  @Override
  protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
      switch (code){
          case 0x001: {
              Log.d("TAG", "MyBinder Switch块 -----" + android.os.Process.myPid());
              data.enforceInterface( "IPCService");
              int num = data.readInt();
              int num1 = data.readInt();
              int num2 = data.readInt();
              String test = data.readString();
              reply.writeNoException();
              reply.writeString(names[num] + "  " + android.os.Process.myPid() + "    " + num1 + "   " + num2 + "   " + test);
              reply.writeInt(1);
              reply.writeString("收到");
              return true;
          }
      }
      Log.d("TAG", "MyBinder   OnTransact块 ----- " + android.os.Process.myPid());
      return super.onTransact(code, data, reply, flags);
  }
}
@Override
public IBinder onBind(Intent intent) {
  return mBinder;
}
}
/**
 * AndroidManifest.xml
         <service android:name="net.binderlearning.IPCService"
                 android:process=".myservice"/>
*/

client:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private EditText edit_num;
    private Button btn_query;
    private TextView txt_result;
    private IBinder mIBinder;
    private ServiceConnection PersonConnection  = new ServiceConnection()
    {
        @Override
        public void onServiceDisconnected(ComponentName name)
        {
            mIBinder = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            mIBinder =  service;
            Log.d("TAG", "客户端-----" + android.os.Process.myPid());
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindViews();
        //绑定远程Service
        Intent service = new Intent(this,IPCService.class);
        bindService(service, PersonConnection, BIND_AUTO_CREATE);
        btn_query.setOnClickListener(this);
    }
    private void bindViews() {
        edit_num = (EditText) findViewById(R.id.edit_num);
        btn_query = (Button) findViewById(R.id.btn_query);
        txt_result = (TextView) findViewById(R.id.txt_result);
    }
    @Override
    public void onClick(View v) {
        int num = Integer.parseInt(edit_num.getText().toString());
        if (mIBinder == null)
        {
            Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show();
        } else {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            String _result = null;
            try{
                _data.writeInterfaceToken("IPCService");
                _data.writeInt(num);
                _data.writeInt(4);
                mIBinder.transact(0x001, _data, _reply, 0);
                _reply.readException(); //读取异常
                _result = _reply.readString();
                String test = _reply.readString();
                int test1 = _reply.readInt();
                Toast.makeText(this, "我收到的内容是:" + test + "    " + test1, Toast.LENGTH_SHORT).show();
                txt_result.setText(_result);
                edit_num.setText("");
                Log.d("TAG", "客户端-----" + android.os.Process.myPid());
            }catch (RemoteException e)
            {
                e.printStackTrace();
            } finally
            {
                _reply.recycle();
                _data.recycle();
            }
        }
    }
}

运行结果:

在这里插入图片描述

在这里插入图片描述

方法说明及其注意点:

1. Parcel的读写顺序要一致。 比如写的时候先 writeInt ,然后再writeString。 那么读的时候也是要先readInt 然后再writeString。 (原因应该是跟Parcelable差不多,调用的是native层的序列化写入,用的是c/c++的指针顺序写入,如果没有按顺序读取,读取地址的时候读到的内容就会很奇怪了)

例如,服务端写入顺序:

在这里插入图片描述

客户端读取顺序:

在这里插入图片描述

结果:

在这里插入图片描述

收到的数据不理想

2. Parcel的writeInterfaceToken(接口名)以及enforceInterface(接口名)

·writeInterfaceToken以及writeInterfaceToken的接口名要一致(类似于验证要访问的接口是否一致)。

不一致,如果找不到binder找不到要调用的接口,就会报异常

java.lang.SecurityException: Binder invocation to an incorrect interface

例如:服务端

在这里插入图片描述

客户端:

在这里插入图片描述

报错:

在这里插入图片描述

·writeInterfaceToken以及writeInterfaceToken的调用时期必须在读写数据前调用,否则会报错:

java.lang.SecurityException: Binder invocation to an incorrect interface

例如:在写入数据后才调用时:

在这里插入图片描述

结果报错:

在这里插入图片描述

源码对这俩个函数的描述:

    /**
     * Store or read an IBinder interface token in the parcel at the current
     * {@link #dataPosition}. This is used to validate that the marshalled
     * transaction is intended for the target interface. This is typically written
     * at the beginning of transactions as a header.
     * 翻译:
        在包中当前数据位置存储或读取IBinder接口令牌。这用于验证编组的事务是否用于目标接口。这通常在事务开始时作为标头写入。
     */

    public final void writeInterfaceToken(@NonNull String interfaceName) {
        nativeWriteInterfaceToken(mNativePtr, interfaceName);
    }
    /**
     * Read the header written by writeInterfaceToken and verify it matches
     * the interface name in question. If the wrong interface type is present,
     * {@link SecurityException} is thrown. When used over binder, this exception
     * should propagate to the caller.
     * 翻译:
     读取writeInterfaceToken所写的报头,并验证它是否与所讨论的接口名称匹配。如果存在错误的接口类型,则抛出SecurityException。在绑定器上使用时,此异常应传播给调用方。
     */
    public final void enforceInterface(@NonNull String interfaceName) {
        nativeEnforceInterface(mNativePtr, interfaceName);
    }

3.Parcel的readException()

作用是读取异常,如果读写的时候有异常,那么就能获取到改异常(获取异常不catch就会导致程序奔溃)。 上述代码可以选择不加这个,不加的话如果有异常就获取不到,也不会导致程序奔溃,但读不到数据

在这里插入图片描述

4. transact和onTransact

protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)

参数说明: 1.code,服务端和客户端约定的标识码,这样在服务端中就可以根据改标识码来处理不同的事件

​ 2.data, 用于读取和写入 要通信的数据

​ 3.reply,用于读取和写入 返回的数据 4. flags 可以不管

transact在客户端中调用,ontransact在服务端中调用。 (当然一个进程可以同时为服务端和客户端。 也就是binder机制支持"递归化调用",比如A跟B通信,A可以调用B提供的Ibinder对象的transact跟B通信,同时B也可以调用A提供的IBinder对象的transact跟A通信。 然后他们在各自的onTransact方法中处理即可)


上面的Demo只是验证了通信,并没有真正意义上的调用另一个进程的接口方法,因此再测试了一个Demo调用另外一个进程的方法:

定义接口方法:一定要继承IInterface

import android.os.IInterface;

public interface IPlus extends IInterface {
      int add(int a, int b);
}

服务端代码:

public class IPCService extends Service {
    private static final String DESCRIPTOR = "add two int";
    private final String[] names = {"B神","艹神","基神","J神","翔神"};
    private MyBinder mBinder = new MyBinder();

    private IInterface plus = new IPlus() {
        @Override
        public int add(int a, int b) {
            return a + b;
        }

        @Override
        public IBinder asBinder() {
            return null;
        }
    };
    public IPCService(){
        mBinder.attachInterface(plus,"add two int");
    }
    private class MyBinder extends Binder {
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code){
                case 0x001: {
                    Log.d("TAG", "MyBinder Switch块 -----" + android.os.Process.myPid());
                    data.enforceInterface(DESCRIPTOR);
                    int a = data.readInt();
                    int b = data.readInt();
                    int result = ((IPlus)this.queryLocalInterface("add two int")).add(a,b);
                    reply.writeNoException();
                    reply.writeInt(result);
                    return true;
                }
            }
            Log.d("TAG", "MyBinder   OnTransact块 ----- " + android.os.Process.myPid());
            return super.onTransact(code, data, reply, flags);
        }
    }
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

客户端代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private EditText edt_arg1;
    private EditText edt_arg2;
    private Button add;
    private TextView addResult;
    private IBinder mIBinder;
    private ServiceConnection PersonConnection  = new ServiceConnection()
    {
        @Override
        public void onServiceDisconnected(ComponentName name)
        {
            mIBinder = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            mIBinder =  service;
            Log.d("TAG", "客户端-----" + android.os.Process.myPid());
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindViews();
        //绑定远程Service
        Intent service = new Intent(this,IPCService.class);
        bindService(service, PersonConnection, BIND_AUTO_CREATE);
        add.setOnClickListener(this);
    }
    private void bindViews() {
        edt_arg1 = (EditText) findViewById(R.id.arg1);
        edt_arg2 = (EditText) findViewById(R.id.arg2);
        add = (Button) findViewById(R.id.add);
        addResult = (TextView) findViewById(R.id.result);
    }
    @Override
    public void onClick(View v) {
        int arg1 = Integer.parseInt(edt_arg1.getText().toString());
        int arg2 = Integer.parseInt(edt_arg2.getText().toString());
        if (mIBinder == null)
        {
            Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show();
        } else {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            int _result = -1;
            try{
                _data.writeInterfaceToken("add two int");
                _data.writeInt(arg1);
                _data.writeInt(arg2);
                mIBinder.transact(0x001, _data, _reply, 0);
                _reply.readException();
                _result  = _reply.readInt();
                addResult.setText(""+_result );
            }catch (RemoteException e)
            {
                e.printStackTrace();
            } finally
            {
                _reply.recycle();
                _data.recycle();
            }
        }
    }
}

运行结果:

在这里插入图片描述

区别第一个Demo:

这里多使用了IInterface,即IPlus

并且在服务端的代码里多写了:

    public IPCService(){
        mBinder.attachInterface(plus,"add two int");
    }
    /**
     *   void attachInterface(IInterface plus, String descriptor);
          // 作用:
          // 1. 将(descriptor,plus)作为(key,value)对存入到Binder对象中的一个Map<String,IInterface>对象中
          // 2. 之后,Binder对象 可根据descriptor通过queryLocalIInterface()获得对应IInterface对象(即plus)的引用,可依靠该引用完成对请求方法的调用
    *      
    */
                    data.enforceInterface(DESCRIPTOR);
                    int a = data.readInt();
                    int b = data.readInt();
                    int result = ((IPlus)this.queryLocalInterface("add two int")).add(a,b);

/**
 *         IInterface queryLocalInterface(Stringdescriptor) ;
        作用:根据 参数 descriptor 查找相应的IInterface对象(即plus引用)
**/

Binder跨进程的通信AIDL方案:

AIDL使用步骤:

  1. 创建AIDL文件

在这里插入图片描述

  1. 写上服务端需要提供的接口方法

在这里插入图片描述

  1. 重新build一下模块,就会自动生成aidl文件对应的java文件

测试Demo1:(同一模块下时)

AIDL文件代码:

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);  //默认生成,可以删除

    //以下俩个是自定义方法
    void say(String word);
    int tell(String word,int age);
}

服务端代码:

public class MyAidlServer extends Service {
    private String TAG = "MyAidlService";
    //使用生成的java文件中的stub作为binder对象
    private IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            Log.d(TAG, android.os.Process.myPid() + "   basicTypes:  " + anInt + "   " + aLong + "   " + aBoolean + "   "
                  + aFloat + "   " + aDouble + "   " + aString);
        }

        @Override
        public void say(String word) throws RemoteException {
            Log.d(TAG, android.os.Process.myPid() + "   say:  "  + word);
        }

        @Override
        public int tell(String word, int age) throws RemoteException {
            Log.d(TAG, android.os.Process.myPid() +  "   tell:   " + word + "    " + age);
            return 100;
        }
    };
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return stub;
    }
}

客户端代码:

public class MainActivity extends AppCompatActivity {
    private static final  String TAG = "MainActivity";
    TextView bindService;
    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service); //获取到服务端的binder代理对象,也就是上面stub
            try {
                iMyAidlInterface.say("I am handsome boy");
                int result = iMyAidlInterface.tell("我是个靓仔", 20);
                Log.d(TAG, android.os.Process.myPid() +"    " + "onServiceConnected:   " + result);
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService = findViewById(R.id.bind_service);
        bindService.setOnClickListener((v) -> {
            Intent intent = new Intent(MainActivity.this, MyAidlServer.class);
            bindService(intent,serviceConnection,BIND_AUTO_CREATE); //绑定服务
        });
    }
}

结果:不同进程的日志需要指令抓取才可以显示出来

客户端进程:

在这里插入图片描述

服务端进程:

在这里插入图片描述

测试Demo2:(同一模块)

服务端aidl:

interface IServiceAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void setData(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString,byte anByte);   //aidl不支持short类型,但可以考虑把short转换成int解决
    void saySomething(String word);
    String saySomethingAndRespon(String word);
}

服务端Service:

public class AidlService extends Service {
    private String TAG = "AidlService";
    private IServiceAidlInterface.Stub stub = new IServiceAidlInterface.Stub() {
        @Override
        public void setData(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString, byte anByte) throws RemoteException {
            Log.d(TAG,android.os.Process.myPid() +  "   服务端收到客户端调用setData传入的数据为:  " + anInt + "   " + aLong + "   "
            + aBoolean + "   " + aFloat + "    " + aDouble + "    " + aString + "     " + (int) anByte);
        }

        @Override
        public void saySomething(String word) throws RemoteException {
            Log.d(TAG, android.os.Process.myPid() + "    服务端收到客户端调用saySomethingAndRespon传入的数据为: " + word);
        }

        @Override
        public String saySomethingAndRespon(String word) throws RemoteException {
            Log.d(TAG, android.os.Process.myPid() + "    服务端收到客户端调用saySomethingAndRespon传入的数据为: " + word);
            return "我是服务端,我已经接收到你发送过来的数据了。 ---> " + word;
        }
    };
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return stub;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    }

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

服务端的注册清单: 此处只为注册service。 且exported属性为true是一定要的

        <service android:name="net.aidl_server.AidlService"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.AIDLService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

客户端:aidl文件直接从服务端复制过来(自建 包名会跟着客户端)

绑定服务的代码:

public class MainActivity extends AppCompatActivity {
    private final String TAG = "ClientMainActivity";
    Button bind_service;
    Intent intent;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IServiceAidlInterface aidlInterface = IServiceAidlInterface.Stub.asInterface(service);
            try {
                aidlInterface.setData(10,100L,true,50.01f,79.0,"XIAO JIAN", (byte) 90);
                aidlInterface.saySomething("服务端你好呀");
                String respon = aidlInterface.saySomethingAndRespon("你是个靓仔");
                Log.d(TAG, android.os.Process.myPid()+"      我是客户端,收到服务端传送过来的消息为: " + respon);
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bind_service = findViewById(R.id.bind_service);
        bind_service.setOnClickListener((v)->{
            intent = new Intent("android.intent.action.AIDLService");
            intent.setPackage("net.aidl_server");
            bindService(intent,connection,BIND_AUTO_CREATE);
        });

    }

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


}

运行结果:

服务端

在这里插入图片描述

客户端:

在这里插入图片描述

Aidl传输复杂数据注意事项:

数据需要实现Parceable接口,并且格式要跟Parceable实现格式一样,可以把鼠标指在Parceable那,然后Alt + 回车

在这里插入图片描述

需要在aidl文件中加入Parceable实现类

在这里插入图片描述

定义接口参数需要加上in

在这里插入图片描述

在AIDL中,in、out和inout是定向标记,用于指示数据在跨进程通信中的流向。其中,
in表示数据只能从客户端流向服务端,
out表示数据只能从服务端流向客户端,
inout表示数据可以在服务端和客户端之间双向流动
需要注意的是,in 和 out 的作用只是为了告诉系统参数的传递方向,实际上在 AIDL 中,所有的参数都是通过值传递的,即传递的是参数的副本,而不是参数本身。因此,如果需要修改参数的值,需要使用 inout 或者返回值的方式来实现。

引用和参考

Carson带你学Android–Android跨进程通信:图文详解 Binder机制 原理

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

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

相关文章

Guitar Pro8.0.1吉他制谱打谱软件

Guitar Pro是一款专业的吉他编曲、打谱软件&#xff0c;Guitar pro的特点是它几乎涵盖了所有的乐谱形式&#xff0c;包括四线谱、五线谱、六线谱等等&#xff0c;最新的Guitar Pro8.1版本还新增了简谱&#xff0c;我们可以在GuitarPro8.1中使用简谱进行演奏。Guitar pro支持在制…

使用ETL工具Kettle实现,把一个数据库中的多张表的数据同步到另外一个数据库中

需求&#xff1a;使用ETL工具Kettle实现&#xff0c;把一个数据库中的多张表的数据&#xff08;不少于3张表&#xff09;同步到另外一个数据库中 1》使用Kettle工具连接MySQL数据库&#xff1a;连接第一个数据库db03。出现圈3说明连接成功。 &#xff08;依次点击&#xff1a;…

webpack处理CSS文件,打包到单独的文件、压缩、以及兼容性处理

一、将css打包到单独的文件 如上图&#xff1a; Css 文件目前被打包到 js 文件中&#xff0c;当 js 文件加载时&#xff0c;会创建一个 style 标签来生成样式 这样对于网站来说&#xff0c;如果网络比较慢的话会出现闪屏现象&#xff0c;用户体验不好 我们去控制台将往速调慢&…

JDK version和class file version对应关系

https://docs.oracle.com/javase/specs/jvms/se20/html/jvms-4.html#jvms-4.1 表 4.1-A. 类文件格式主要版本 Java SEReleasedMajorSupported majors1.0.2May 199645451.1February 199745451.2December 19984645 .. 461.3May 20004745 .. 471.4February 20024845 .. 485.0Sept…

手把手教你实战TDD | 京东云技术团队

1. 前言 领域驱动设计&#xff0c;测试驱动开发。 我们在《手把手教你落地DDD》一文中介绍了领域驱动设计&#xff08;DDD&#xff09;的落地实战&#xff0c;本文将对测试驱动开发&#xff08;TDD&#xff09;进行探讨&#xff0c;主要内容有&#xff1a;TDD基本理解、TDD常…

depcheck检查缺失的或者位使用的依赖

depcheck它可以帮助我们找出问题&#xff0c;在 package.json 中&#xff0c;每个依赖包如何被使用、哪些依赖包没有用处、哪些依赖包缺失。它是解决前端项目中依赖包清理问题的一个常用工具 depcheck官方文档地址 Github&#xff1a;https://github.com/depcheck/depcheck 1…

笔记本触摸板没反应?1分钟,快速解决!

案例&#xff1a;在使用笔记本电脑时&#xff0c;我喜欢使用触摸板进行一些电脑上的操作。但是最近我的触摸板突然没反应&#xff0c;不能使用。有小伙伴知道这是什么原因吗&#xff1f;该如何解决呀&#xff1f; 笔记本电脑已经成为我们日常生活和工作中不可或缺的工具。然而…

光传感芯片产品应用领域解析

光传感产品主要应用于穿戴心率等健康检测、安防环境光监测、智能家居环境光感测、智慧电子产品自动控制、工业自动控制及安全检查、控制。 WH光感材料特点&#xff1a; 1、双波普独立通道&#xff0c;独立控制 2、波谱响应波长可客制化定制&#xff1a; —环境光红蓝绿、光距感…

企业邀请媒体报道活动,邀请本地媒体好,还是全国性的媒体好

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 企业做活动在制定媒体策略&#xff0c;媒体传播规划的时候&#xff0c;往往不知道改如何选择&#xff0c;今天胡老师就来分享下本地媒体和全国性媒体的特点&#xff0c;帮助大家更好的制…

SpringCloud搭建Eureka服务注册中心(六)

前面说过eureka是c/s模式的 server服务端就是服务注册中心&#xff0c;其他的都是client客户端&#xff0c;服务端用来管理所有服务&#xff0c;客户端通过注册中心&#xff0c;来调用具体的服务&#xff1b; 我们先来搭建下服务端&#xff0c;也就是服务注册中心&#xff1b…

uniapp小程序订阅消息推送+Thinkphp5后端代码教程示例

记录一下通过uniapp开发小程序消息推送的实例&#xff0c;配合后端tp推送&#xff0c;之前写的项目是微信小程序而且后端是原生php&#xff0c;这次通过项目记录一下 目录 回顾access_token获取规则以及思路 第一步&#xff1a;设计前端触发订阅事件第二步&#xff1a;设计将to…

1140道Java常见面试题及详细答案

最近感慨面试难的人越来越多了&#xff0c;一方面是市场环境&#xff0c;更重要的一方面是企业对 Java 的人才要求越来越高了。 基本上这样感慨的分为两类人&#xff1a; 第一&#xff0c;虽然挂着 3、5 年经验&#xff0c;但肚子里货少&#xff0c;也没啥拿得出手的项目&#…

OPNET出现“Packet pointer references unowned packet(<pk_id>)”错误的解决办法

在使用 OPNET Modeler 软件时&#xff0c;会遇到很多奇奇怪怪的报错&#xff0c;今天要介绍的报错内容如下。 Packet pointer references unowned packet(<pk_id>). 程序中断的原因截图如下图所示。 由上图可以看到&#xff0c;引发错误的 OPNET 核心函数是 op_pk_send(…

快速幂应用之剪绳子问题

有这样一类问题&#xff0c;给你一个长度为n的绳子&#xff0c;要求你可以剪切任意次数&#xff0c;分为任意段&#xff0c;使得这些子段长度的乘积最大。我们把这类问题暂时先称为剪绳子&#xff0c;这种问题的解法也很简单&#xff0c;通过数学证明可以得出&#xff0c;我们优…

​Java容器的继承关系​

Java容器的继承关系 Collection接口 Collection接口中所定义的方法 int size(); boolean isEmpty(); void clear(); boolean contains(Object element);//是否包含某个对象 boolean add(Object element); Iterator iterator(); boolean containsAll(Collection c);//是否包含另…

MybatisPlus 实现数据拦截

基于配置文件实现&#xff08;关键key存储在配置文件&#xff0c;通过读取配置文件来实现动态拼接sql&#xff09; 1、创建注解类 UserDataPermission(id"app") 注&#xff1a;id用以区分是小程序还是应用程序 注解加的位置&#xff1a; 2、配置枚举类配置文件 E…

学了Python后还用学R语言吗?

学习R语言是否有必要取决于你的具体需求和背景。虽然R语言和Python都是数据科学领域广泛使用的编程语言&#xff0c;但它们之间还是存在一些差异。 如果你主要从事数据分析、统计建模或者数据可视化等工作&#xff0c;那么学习R语言可能更为适合。R语言在数据处理和统计分析方…

【gcc, cmake, eigen, opencv,ubuntu】四.opencv安装和使用,获取opencv matiax 的指针

文章目录 ubuntu系统安装opencv1.下载opencv和opencv_contrib2.安装指导3.Linux 下 fatal error: opencv2/opencv.hpp: 没有那个文件或目录4.g 和cmake 编译使用opencv的程序5.opencv,eigen速度比较6.opencv常用类型符号7.获取opencv matiax 的指针 ubuntu系统安装opencv 1.下…

Java实训日志03

文章目录 八、项目开发实现步骤&#xff08;五&#xff09;创建数据库连接管理类1、创建数据库实用工具包2、创建数据库连接管理类&#xff08;1&#xff09;定义数据库连接属性常量&#xff08;2&#xff09;创建私有化构造方法&#xff08;3&#xff09;编写获取数据库连接静…

关于C++数组名和指针的一些思考

在学习指针数组与数组指针一节时&#xff0c;了解到数组名其实是指向数组收个元素的指针。如下面代码所示 int main() {int a[5] {1, 2, 3, 4, 5};cout << "*a:" << *a << endl;cout << "*(a 1):" << *(a 1) << e…