Android中的IPC方式

news2024/11/25 14:37:40

Android中的IPC方式

1.Bundle

其中Intent就是用Bundle进行传播

四大组件间的进程间通信

2.使用文件共享

2.1文件共享的缺点

无并发,适用于交换简单的数据实时性不高的情景

使用文件共享容易出:

  1. 内容不一致:如果多个线程同时写入文件,可能会导致文件内容不一致。这是因为多个线程同时写入文件时,写入的顺序和时间不确定,可能会出现覆盖、丢失或重复写入等问题,导致文件内容与预期不符。
  2. 文件损坏:如果多个线程同时读写文件,可能会导致文件损坏。这是因为多个线程同时读写文件时,可能会出现读写冲突、死锁等问题,导致文件被破坏或无法正常访问。
  3. 性能下降:如果多个线程同时读/写同一个文件,可能会导致性能下降。这是因为多个线程同时读/写文件时,会竞争文件系统的资源,导致频繁的上下文切换和锁竞争,降低了系统的并发性能和吞吐量。

Sharedpreferences对于其他文件共享来说是个特例,因为它只能被应用程序中的其他组件共享,并且访问权限是应用程序私有的,数据格式也非常简单。不仅如此,系统对它有一定的缓存策略,即在内存中年会有一份Sharedpreferences文件的缓存,在多线程模式下,系统对它的读/写不靠谱,面对高并发的读/写访问,Sharedpreferences有很大概率丢失数据。所以不建议在进程通信中使用Sharedpreferences

3.Messenger

先说服务端无法回应客户端的情况:

3.1服务端无法回应客户端

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote" />

先在AndroidManifest中加入:

 android:process=":remote" 

这样表示多进程

之后在MainActivity

public class MainActivity extends AppCompatActivity {
    // 服务端Messenger
    private Messenger mServerMessenger;
    // 服务端连接状态
    private boolean mIsBound = false;
    // 绑定服务端
    private Button message_0;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mServerMessenger = new Messenger(service);
            Message message = new Message();
            message.what = 1;
            Bundle bundle = new Bundle();
            bundle.putString("data","你好啊");
            message.setData(bundle);
            try {
                mServerMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        message_0 = findViewById(R.id.message_0);
        // 绑定服务端
        if(!mIsBound){
        message_0.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mIsBound = true;
                Intent intent = new Intent(MainActivity.this,MyService.class);
                bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
            }
        });
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解绑服务端
        unbindService(mConnection);
    }
}

在MyService

public class MyService extends Service {
private static class MessengerHandler extends Handler{
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case 1:
                Log.d("TAG","revice"+msg.getData().getString("data"));
                break;
            default:
                break;
        }
    }
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

3.2服务端回应客户端

首先是MyService

public class MyService extends Service {
private static class MessengerHandler extends Handler{
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case 1:
                Log.d("TAG",msg.getData().getString("data"));
                Messenger client = msg.replyTo;
                Message replyMessage = new Message();
                replyMessage.what = 2;
                Bundle bundle = new Bundle();
                bundle.putString("TAG","reply"+"我大后台收到你的来信了");
                replyMessage.setData(bundle);
                try {
                    client.send(replyMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

Log.d("TAG",msg.getData().getString("data"));

下面加上

  Messenger client = msg.replyTo;
                Message replyMessage = new Message();
                replyMessage.what = 2;
                Bundle bundle = new Bundle();
                bundle.putString("TAG","reply"+"我大后台收到你的来信了");
                replyMessage.setData(bundle);
                try {
                    client.send(replyMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

首先:

Messenger client = msg.replyTo;

msg是一个消息对象。该消息对象有一个replyTo属性

 replyMessage.what = 2;
                Bundle bundle = new Bundle();
                bundle.putString("TAG","reply"+"我大后台收到你的来信了");
                replyMessage.setData(bundle);
                try {
                    client.send(replyMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

这块代码和之前MainActivity里面差不多

MainActivity部分

public class MainActivity extends AppCompatActivity {
    // 服务端Messenger
    private Messenger mServerMessenger;
    // 服务端连接状态
    private boolean mIsBound = false;
    // 绑定服务端
    private Button message_0;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mServerMessenger = new Messenger(service);
            Message message = new Message();
            message.what = 1;
            Bundle bundle = new Bundle();
            bundle.putString("data","你好啊");
            message.setData(bundle);
            message.replyTo = mGetReplyMessenger;
            try {
                mServerMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        message_0 = findViewById(R.id.message_0);
        // 绑定服务端
        if(!mIsBound) {
            message_0.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mIsBound = true;
                    Intent intent = new Intent(MainActivity.this, MyService.class);
                    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
                }
            });
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解绑服务端
        unbindService(mConnection);
    }
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());
    private static class MessengerHandle extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 2:
                    Log.d("TAG",msg.getData().getString("TAG").toString());
            }
        }
    }
}

要加的地方是两个

private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());
private static class MessengerHandle extends Handler{
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case 2:
                Log.d("TAG",msg.getData().getString("TAG").toString());
        }
    }
}

这块和Service那端一样,是用来获取消息的

然后还有这块

 private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mServerMessenger = new Messenger(service);
            Message message = new Message();
            message.what = 1;
            Bundle bundle = new Bundle();
            bundle.putString("data","你好啊");
            message.setData(bundle);
            message.replyTo = mGetReplyMessenger;
            try {
                mServerMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

多的只有:

message.replyTo = mGetReplyMessenger;

感觉这块就是为了调用mGetReplyMessenger,否则就没办法显示出来service端发送的消息

以下是我对Messenger的一些理解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7mqYYn2u-1684168988782)(../../assets/未命名文件 (5)].png)

3.3Messenger的缺点

Messenger是以串行的方式处理客户端发来的消息。如果大量消息同时发送到服务端,服务端仍然只能一个一个处理。

如果有大量的并发请求,那么用 Messenger 就不太合适了。同时,Messenger经完成的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,

4.AIDL

我们可以使用AIDL来实现跨进程的方法调用。AIDL也this is是Messenger的底层实现,因此 Messenger 本质上也是AIDL,只不过系统为我们做了封装从而方便上层的调用而已。

4.1AIDL接口创建

我们先在创建一个Book类,让它实现Parcelable接口

public class Book implements Parcelable {
    public int getBookNum() {
        return BookNum;
    }

    private int BookNum;

    public String getBookName() {
        return BookName;
    }

    private String BookName;
    public Book(String BookName,int BookNum) {
        this.BookName = BookName;
        this.BookNum = BookNum;
    }

    protected Book(Parcel in) {
        BookNum = in.readInt();
        BookName = in.readString();
    }

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

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

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

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(BookNum);
        dest.writeString(BookName);
    }
}

然后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vooeXezQ-1684168988783)(../../assets/image-20230513223820468.png)]

通过这个创建AIDL接口

我们创建了两个,一个是Book.aidl

一个是IBookManager.aidl

其中Book.aidl的代码如下

package com.example.ipc;

// Declare any non-default types here with import statements
parcelable Book;

表示在 AIDL 接口中定义了一个 Book 类型,该类型实现了 Parcelable 接口,可以被序列化和反序列化。

然后是IBookManager.aidl的代码

import com.example.ipc.Book;
interface IBookManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    List<Book>getBookList();
    void addBook(in Book book);
}

注意:虽然Book与IBookManager在同一个包下,但是也得导入

然后我们定义了两个方法,List类型的getBookList()void类型的addBook()

那么为什么addBook()里面的是in Book book呢?

因为AIDL中除了基本数据类型,其他的参数必须标注上方向

in输入类型参数
out输出类型参数
inout输入输出行参数

而AIDL有哪些数据类型呢?

基本数据类型懒得写
String和CharSequence
List只支持ArrayList,里面每个元素都必须被AIDL支持
Map只支持HashMap,里面的key和value都必须被AIDL支持
Parcelable所有实现了Parcelable接口的对象
AIDL所有的AIDL接口本身也可以在AIDL文件中使用

然后《Android艺术开发探索》中说到这样做完之后得把这个包和包里面的所有文件复制到客户端中

所以

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CpBWfApc-1684168988783)(../../assets/image-20230513225307699.png)]

如果你直接在服务端调用IBookManager会发现没有这个类,解决的办法就是把代码跑一下,之后就可以导入成功了

4.2服务端

首先先在AndroidManifest中

<service
    android:name=".BookManagerService"
    android:enabled="true"
    android:process=":remote"
    android:exported="true"></service>

之后

public class BookManagerService extends Service {
    public BookManagerService() {
    }
    private CopyOnWriteArrayList<Book>mBookList = new CopyOnWriteArrayList<Book>();
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }
        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
       return mBinder;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book("Android",1));
        mBookList.add(new Book("不想上班",2));
    }
}

上面是一个服务端 Service 的典型实现,首先在 onCreate 中初始化添加了两本图书息,然后创建了一个 Binder 对象并在 onBind 中返回它,这个对象继承自IBookManager并实现了它内部的AIDL 方法,getBookList和addBook 这两个 AIDL方法的实现,实现过程也比较简单,这里采用了CopyOnWriteArrayList,这个 CopyOnWriteArrayList支持并发读/写。在前面们提到,AIDL 方法是在服务端的 Binder 线程池中执行的,因此当多个客户端同时连糖时候,会存在多个线程同时访问的情形,所以我们要在 AIDL 方法中处理线程同步,而们这里直接使用 CopyOnWriteArrayList 来进行自动的线程同步。

为什么之前说AIDL能使用的List是ArrayList这里却可以用CopyOnWriteArrayList,并且它并不是继承自ArrayList

在 Android 中,AIDL 支持使用的 List 类型默认是 ArrayList,因为 ArrayList 是 Java 中最常用的 List 实现类之一,也是序列化和反序列化比较方便的类。但是,实际上 AIDL 也可以支持其他类型的 List,只要这些 List 类型实现了 List 接口并且是可序列化的。这里的List是抽象的,只是一个接口。

虽然Service端是CopyOnWriteArrayList,但是客户端接收到的是ArrayList等会儿就看到了

4.3客户端

4.3.1客户端获得数据

按下按钮

aidl_0.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mIsBound = true;
        Intent intent = new Intent(MainActivity.this,BookManagerService.class);
        bindService(intent,mConnection1,BIND_AUTO_CREATE);
    }
});

其中mConnection1的代码如下:

private ServiceConnection mConnection1 = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        IBookManager iBookManager = IBookManager.Stub.asInterface(service);
        try {
            List<Book>list = iBookManager.getBookList();
            Log.d("TAG","list type"+list.getClass().getCanonicalName());
            for (int i = 0;i<list.size();i++){
                Log.d("TAG","listNum"+list.get(i).getBookNum()+"    "+"listName"+list.get(i).getBookName());
            }

        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

运行的结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z9MvDRbe-1684168988784)(../../assets/QQ图片20230513230514.png)]

这里类型就变成了ArrayList

但是:

如果

<service
    android:name=".BookManagerService"
    android:enabled="true"
    android:process=":remote"
    android:exported="true"></service>

中没有:

android:process=":remote"

那么最后客户端的类型也是copyonWriteArrayList

4.3.2客户端调用addBook()

就放这块的代码

private ServiceConnection mConnection1 = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        IBookManager iBookManager = IBookManager.Stub.asInterface(service);
        try {
            List<Book>list = iBookManager.getBookList();
            Log.d("TAG","list type"+list.getClass().getCanonicalName());
            for (int i = 0;i<list.size();i++){
                Log.d("TAG","listNum"+list.get(i).getBookNum()+"    "+"listName"+list.get(i).getBookName());
            }
            Book book = new Book("我爱纯爱",3);
            iBookManager.addBook(book);
            Log.d("TAG","添加后:");
            List<Book>list1 = iBookManager.getBookList();
            for (int i = 0;i<list1.size();i++){
                Log.d("TAG","listNum"+list1.get(i).getBookNum()+"    "+"listName"+list1.get(i).getBookName());
            }

        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

记住两点:

iBookManager.addBook(book);

而不是

list.add(book);

如果用下面的这个就没办法将新加的书传递给service端。

得重新创建list,用原先的list读出来还是原先的数据,没有新加的数据。

4.4进阶1(如何每隔一段时间服务端给客户端发送消息)

我们想每隔一段时间,我们就能知晓现在有哪些书

这是一种典型的观察者模式

4.4.1AIDL

在aidl文件夹下面创建IOnNewBookArrivedListener.aidl文件夹

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qLAlwofs-1684168988784)(../../assets/QQ图片20230514161830.png)]

代码如下,

import com.example.ipc.Book;
interface IOnNewBookArrivedListener {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
   void onNewBookArrived(in Book book);
}

我们的希望是:当服务端有新书到来的时候,会通知每个已经申请提醒功能的用户,

IBookManager.aidl中:

import com.example.ipc.Book;
import com.example.ipc.IOnNewBookArrivedListener;
interface IBookManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    List<Book>getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}

4.4.2服务端

public class BookManagerService extends Service {
    public BookManagerService() {
    }
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
    private CopyOnWriteArrayList<Book>mBookList = new CopyOnWriteArrayList<Book>();
    private CopyOnWriteArrayList<IOnNewBookArrivedListener>mListenerList = new CopyOnWriteArrayList<>();
    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if(!mListenerList.contains(listener)){
                mListenerList.add(listener);
            }
            else{
                Log.d("TAG","already exist");
            }
            Log.d("TAG","registerListener,size:"+mListenerList.size());
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if(mListenerList.contains(listener)){
                mListenerList.remove(listener);
                Log.d("TAG","unregister listener succeed");
            }
            else {
                Log.d("TAG","not found,can not unregister");
            }
            Log.d("TAG","unregisterListener,size:"+mListenerList.size());
        }
    };
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
       return mBinder;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book("Android",1));
        mBookList.add(new Book("不想上班",2));
        new Thread(new ServiceWorker()).start();

    }
    private class ServiceWorker implements Runnable{
        @Override
        public void run() {
            while(!mIsServiceDestroyed.get()){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookList.size()+1;
                Book newBook = new Book("new book#"+bookId,bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

        }
    }
    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        for(int i =0;i<mListenerList.size();i++){
            IOnNewBookArrivedListener listener = mListenerList.get(i);
            Log.d("TAG","onNewBookArrived,notify listener:"+listener);
            listener.onNewBookArrived(book);
        }
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        mIsServiceDestroyed.set(true);
    }
}

先看这块:

4.4.2.1register与unregister
    private Binder mBinder = new IBookManager.Stub() {
	@Override
    public List<Book> getBookList() throws RemoteException {
        return mBookList;
    }

    @Override
    public void addBook(Book book) throws RemoteException {
        mBookList.add(book);
    }

    @Override
    public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
        if(!mListenerList.contains(listener)){
            mListenerList.add(listener);
        }
        else{
            Log.d("TAG","already exist");
        }
        Log.d("TAG","registerListener,size:"+mListenerList.size());
    }

    @Override
    public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
        if(mListenerList.contains(listener)){
            mListenerList.remove(listener);
            Log.d("TAG","unregister listener succeed");
        }
        else {
            Log.d("TAG","not found,can not unregister");
        }
        Log.d("TAG","unregisterListener,size:"+mListenerList.size());
    }
};

我们重写了IBookManager中的registerListenerunregisterListener

mListenerList是一个支持并发读写IOnNewBookArrivedListener集合

registerListener

IOnNewBookArrivedListener的作用前面说了,当这个集合没有listener的时候,把它加入,。

unregisterListener

当集合有listener的时候,把它移除


4.4.2.2 oncreate()

在**oncreate()**中,和之前不一样的就是:多了:

 new Thread(new ServiceWorker()).start();

就是对开了一个线程,我们想实现每隔一段时间收到一条消息,这么耗费时间的工作就得另开一条线程

4.4.2.3多线程中ServiceWorker

ServiceWorker中:

  private class ServiceWorker implements Runnable{
    @Override
    public void run() {
      while(!mIsServiceDestroyed.get()){
        try {
          Thread.sleep(5000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        int bookId = mBookList.size()+1;
        Book newBook = new Book("new book#"+bookId,bookId);
        try {
          onNewBookArrived(newBook);
        } catch (RemoteException e) {
          e.printStackTrace();
        }
      }

    }
  }
  private void onNewBookArrived(Book book) throws RemoteException {
    mBookList.add(book);
    for(int i =0;i<mListenerList.size();i++){
      IOnNewBookArrivedListener listener = mListenerList.get(i);
      Log.d("TAG","onNewBookArrived,notify listener:"+listener);
      listener.onNewBookArrived(book);
    }
  }

其中mIsServiceDestroyed

 private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);

这个操作表示mIsServiceDestroyed提供了原子性操作,初始为false

我们让它在为false的情况下进入while循环,true的时候退出

什么时候为true呢?

  @Override
    public void onDestroy() {
        super.onDestroy();
        mIsServiceDestroyed.set(true);
    }

当它退出的时候,我们让它为true
否则一直进入while循环。

看看while循环里面

首先让它每5s休息一次

然后执行

 int bookId = mBookList.size()+1;
        Book newBook = new Book("new book#"+bookId,bookId);
        try {
          onNewBookArrived(newBook);
        } catch (RemoteException e) {
          e.printStackTrace();
        }

我们在这里不断的创建新的Book

然后交由onNewBookArrived(newBook);

4.4.2.4onNewBookArrived()

让它帮我们传递给客户端

private void onNewBookArrived(Book book) throws RemoteException {
    mBookList.add(book);
    for(int i =0;i<mListenerList.size();i++){
        IOnNewBookArrivedListener listener = mListenerList.get(i);
        Log.d("TAG","onNewBookArrived,notify listener:"+listener);
        listener.onNewBookArrived(book);
    }
}

第一行代码就是把新生成的书加入mBookList

然后

遍历所有已注册的客户端IOnNewBookArrivedListener,将新生成的图书信息发送给每个客户端。对于每个客户端,调用其onNewBookArrived()方法,以将新的图书对象book传递给客户端。

4.4.3客户端

public class MainActivity extends AppCompatActivity {
    // 服务端Messenger
    private Messenger mServerMessenger;
    // 服务端连接状态
    private boolean mIsBound = false;
    // 绑定服务端
    private Button message_0;
    private Button aidl_0;
    private IBookManager mRemoteBookManager;
    private IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            mHandler.obtainMessage(555,book).sendToTarget();
        }
    };
    @SuppressLint("HandlerLeak")
    private final Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 555:
                    Log.d("TAG","receive new book:"+msg.obj);
                    break;
            }

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        message_0 = findViewById(R.id.message_0);
        aidl_0 = findViewById(R.id.aidl_0);
        // 绑定服务端
        aidl_0.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,BookManagerService.class);
                bindService(intent,mConnection1,BIND_AUTO_CREATE);
            }
        });
    }
    private ServiceConnection mConnection1 = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager iBookManager = IBookManager.Stub.asInterface(service);
            try {
                mRemoteBookManager = iBookManager;
                List<Book>list = iBookManager.getBookList();
                Log.d("TAG","query book list,list type:"+list.getClass().getCanonicalName());
                Book book = new Book("好好学习",3);
                iBookManager.addBook(book);
                Log.d("TAG","Now add book named"+"   "+book.getBookName());
                Log.d("TAG","添加后");
                List<Book>newList = iBookManager.getBookList();
                for(int i = 0;i<newList.size();i++){
                    Log.d("TAG",newList.get(i).getBookName()+"  "+newList.get(i).getBookNum());
                }
                iBookManager.registerListener(mIOnNewBookArrivedListener);

            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解绑服务端
        if(mRemoteBookManager!=null&&mRemoteBookManager.asBinder().isBinderAlive()){
            Log.d("TAG","unregister listener:"+mIOnNewBookArrivedListener);
            try {
                mRemoteBookManager.unregisterListener(mIOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
    }
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());
    private static class MessengerHandle extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 2:
                    Log.d("TAG",msg.getData().getString("TAG").toString());
                    break;
            }
        }
    }
}

客户端不同的地方从这块开始

4.4.3.1ServiceConnection()
 private ServiceConnection mConnection1 = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
      IBookManager iBookManager = IBookManager.Stub.asInterface(service);
      try {
        mRemoteBookManager = iBookManager;
        List<Book>list = iBookManager.getBookList();
        Log.d("TAG","query book list,list type:"+list.getClass().getCanonicalName());
        Book book = new Book("好好学习",3);
        iBookManager.addBook(book);
        Log.d("TAG","Now add book named"+"  "+book.getBookName());
        Log.d("TAG","添加后");
        List<Book>newList = iBookManager.getBookList();
        for(int i = 0;i<newList.size();i++){
          Log.d("TAG",newList.get(i).getBookName()+" "+newList.get(i).getBookNum());
        }
        iBookManager.registerListener(mIOnNewBookArrivedListener);} catch (RemoteException e) {
​        e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}
  };

我们定义了一个全局变量IBookManager属性的mRemoteBookManager让它等于 mRemoteBookManager以便后面**onDestroy()**用

然后就是

  iBookManager.registerListener(mIOnNewBookArrivedListener);

mIOnNewBookArrivedListener

4.4.3.2mIOnNewBookArrivedListener
private IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
    @Override
    public void onNewBookArrived(Book book) throws RemoteException {
        mHandler.obtainMessage(555,book).sendToTarget();
    }
};

当客户端调用远程服务端的 onNewBookArrived 方法时,服务端会将传入的 Book 对象作为消息的 obj 字段,将 what 字段设置为 555,然后通过 mHandler 发送到主线程的消息队列中,以便在主线程中处理该消息并通知相应的监听器。由于该监听器是在客户端注册的,所以通过该监听器发送的消息最终会通知到客户端。

然后消息在

4.4.3.3mHandler实现
private final Handler mHandler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 555:
                Log.d("TAG","receive new book:"+msg.obj);
                break;
        }

    }
};

最后就是

4.4.3.4onDestroy()
@Override
protected void onDestroy() {
    super.onDestroy();
    // 解绑服务端
    if(mRemoteBookManager!=null&&mRemoteBookManager.asBinder().isBinderAlive()){
        Log.d("TAG","unregister listener:"+mIOnNewBookArrivedListener);
        try {
            mRemoteBookManager.unregisterListener(mIOnNewBookArrivedListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    unbindService(mConnection);
}

主要是:

mRemoteBookManager.asBinder().isBinderAlive()

这行代码的作用是检查mRemoteBookManager的底层Binder是否处于活动状态。Binder是Android系统中进程间通信的基本组件,每个Binder都有一个唯一的标识符,它可以用来检查Binder是否仍然可用。因此,通过调用mRemoteBookManager的asBinder()方法,可以获取到其底层Binder对象,然后通过调用isBinderAlive()方法,可以检查该Binder是否处于活动状态。如果Binder已经死亡,则不能再向其发送消息,否则会导致异常。因此,这行代码的作用是确保mRemoteBookManager仍然处于活动状态,以避免发生异常。


4.4.4注意:

IOnNewBookArrivedListener是一个接口,用于在客户端注册一个回调函数,当有新的书籍信息到达时,远程服务端会通过该接口通知客户端。客户端需要实现该接口的 onNewBookArrived() 方法,用于处理远程服务端传递过来的书籍信息。

4.4.5流程图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TbPyQUOq-1684168988785)(../../assets/未命名文件 (6)].png)

4.5进阶2(如何安全注销)

当我们退出来实现unregisterListener的时候,Log.d()会打印

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pEsJTtvI-1684168988785)(../../assets/QQ图片20230514181913.png)]

用aidl解除注册的时候会爆出找不到之前注册的listener

因为Binder会把客户端传递过来的对象重新转化并生成新的对象,虽然注册和解除注册用的是同一个listener但是进入到服务端是另一个

产生两个全新的对象。那应该怎么实现解除注册的功能?

RemoteCallbackList,它是系统专门提供用于删除跨进程的listener接口。并且在客户端进程终结的时候,能自动移除客户端的listener。并内部自动实现线程同步。

在service端

private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();

就是把原来的CopyOnWriteArrayList改成RemoteCallbackList

然后之前判断 mListenerList是否存在该IOnNewBookArrivedListener,不存在的话,register部分就把它加进去。

存在的话,unregister就把它remove。但是在这里面,不用进行判断存不存在

直接

      @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);

        }

然后在当有新书时,通知所有已经注册的listener那块:

  private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
        for(int i = 0;i<N;i++){
            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
            if(l!=null){
                l.onNewBookArrived(book);
            }
        }
        mListenerList.finishBroadcast();
    }

通过beginBroadcast()方法获取当前已经注册的监听器数量,并返回一个整型值N

通过

ListenerList.getBroadcastItem(i)

获取监听器l,并判断是否为空,不为空的话就把它发送给客户端。

改进之后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GZQKR89T-1684168988785)(../../assets/image-20230514183813096.png)]

成功退出。

4.5.1注意:

为什么RemoteCallbackList可以保证客户端和服务端传递的是同一个listener?

chatpgt给出的解释是:

RemoteCallbackList 通过维护客户端传递进来的 IInterface 对象的列表,并根据这些对象生成相应的 ICallback 类型的对象实例,将它们返回给客户端,从而确保服务端和客户端得到的是同一个 listener 对象的实例。

具体来说,RemoteCallbackList 通过一个底层的 Map 数据结构,将传递进来的 IInterface 对象和 ICallback 类型的对象实例一一映射。当客户端需要注册一个 ICallback 时,RemoteCallbackList 首先根据传递进来的 IInterface 对象在 Map 中查找对应的 ICallback 实例,如果找到了就返回这个实例,否则就创建一个新的 ICallback 实例,并将 IInterface 对象和 ICallback 实例一一对应存储在 Map 中,然后返回新创建的 ICallback 实例给客户端。

Map的key是IBinder类型,value是Callback类型

4.6进阶3(处理耗时的情况)

如果我在service端中在getBookList中让它每5s才返回一次

@Override
public List<Book> getBookList() throws RemoteException {
    SystemClock.sleep(5000);
    return mBookList;
}

如果你这时候打开app并连续按按钮,会出现:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k6waEKor-1684168988786)(../../assets/QQ图片20230514202942.png)]

即ARN

为了解决这个问题,我们在客户端调用getBookList的地方加入多线程就可以了

4.7防止Binder意外死亡

4.7.1给Binder设置DeathRecpient监听

4.7.1.1 服务端
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        // 当客户端与服务端的连接断开时,系统会自动调用该方法
        // 在这里进行一些清理工作,例如注销监听器、关闭数据库等
    }
};
4.7.1.2客户端

在客户端中,需要实现相应的Binder.DeathRecipient接口,然后在获取远程服务时,调用linkToDeath()方法将该接口注册到远程Binder中。

private ServiceConnection mConnection = new ServiceConnection() {
    ...
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mRemoteService = IRemoteService.Stub.asInterface(service);
        try {
            // 注册DeathRecipient监听
            service.linkToDeath(mDeathRecipient, 0);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
};

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        // 当与远程服务断开连接时,系统会自动调用该方法
        // 在这里进行相应的重连操作
    }
};

这样,在客户端与服务端之间的连接意外断开时,系统会自动调用客户端中注册的DeathRecipient接口的binderDied()方法,我们可以在这个方法中进行一些处理,例如重连操作。

4.7.2onServiceDisconnected

在这里面重连就行了

而二者的区别就是前者不能访问UI

4.8AIDL如何进行权限验证

现在AndroidManifest申明权限

然后service的onBind()调用

5.ContentProvider

ContentProvider生来就是用来进程间通信的,它在底层实现的是Binder

《Android艺术开发探索》中主要用的是它来获取sqLite数据库里面的数据进行展示,为了展示多进程间的通信,ContentProvider也是被设置为

android:process=":provider

我们先用一个简单的样例讲述ContentProvider

5.1初级

public class BookProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        Log.d("TAG","onCreate,current thread:"+Thread.currentThread().getName());
        return false;
    }
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d("TAG","query,current thread:"+Thread.currentThread().getName());
        return null;
    }
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.d("TAG","getType");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.d("TAG","insert");
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d("TAG","delete");
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d("TAG","update");
        return 0;
    }
}

让一个类继承ContentProvider会重写这些方法

然后在AndroidManifest

<provider
    android:authorities="com.example.ipc.BookProvider"
    android:name=".BookProvider"
    android:permission="com.example.ipc.PROVIDER"
    android:process=":provider">
</provider>

上面的两个我输入一会儿就有api显示,应该就是根据你的类名固定了,

但是permission感觉随便写就行,只要符合命名规范

别忘了下面的process


然后就是MainActivity

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        contentProvider_0.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.example.ipc.BookProvider");
                getContentResolver().query(uri,null,null,null,null);
                getContentResolver().query(uri,null,null,null,null);
                getContentResolver().query(uri,null,null,null,null);

            }
        });
    }
});
thread.start();

Uri uri = Uri.parse("content://com.example.ipc.BookProvider")其中括号里面的就写为AndroidManifest中的authorities就行了

getContentResolver() 是 Android 中的一个方法,用于获取应用程序的内容解析器(ContentResolver)对象。内容解析器提供了对应用程序数据提供者的访问和操作功能

点击按钮

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ax4rPfLl-1684168988786)(…/…/assets/QQ图片20230515205612.png)]

没截图截全

其实还有一个是12582_1

执行了3个

getContentResolver().query(uri,null,null,null,null);

然后3个不在同一个线程执行

5.2进阶

5.2.1创建数据库

public class DbOpenHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "book_provider.db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME  ="user";
    private static final int DB_VERSION = 1;
    //图书和用户信息表

    private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "+
            BOOK_TABLE_NAME+"(_id INTEGER PRIMARY KEY," + "name TEXT)";

    private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "+
            USER_TABLE_NAME+"(_id INTEGER PRIMARY KEY," + "name TEXT,"+"sex INT)";

    public DbOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }


    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

其中

 private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "+
            BOOK_TABLE_NAME+"(_id INTEGER PRIMARY KEY," + "name TEXT)";

只在该表不存在时才创建一个:第一列名为 _id,数据类型为 INTEGER,并且被指定为主键(PRIMARY KEY)。第二列名为 name,数据类型为 TEXT,用于存储文本数据的且**名为:**BOOK_TABLE_NAME

下面

private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "+
            USER_TABLE_NAME+"(_id INTEGER PRIMARY KEY," + "name TEXT,"+"sex INT)";

然后在

  @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);
    }

创建CREATE_BOOK_TABLE与CREATE_USER_TABLE数据库

5.2.2ContentProvider

public class provider extends ContentProvider {
    private Context mContext;
    private SQLiteDatabase mDb;
    public static final String AUTHORITY = "com.example.ipc.provider";
    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/book");
    public static final Uri USER_CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/user");
    public static final int BOOK_URI_CODE = 0;
    public static final int USER_URI_CODE = 1;

    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sUriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);
        sUriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);
    }

    private String getTableName(Uri uri){
        String tableName = null;
        switch (sUriMatcher.match(uri)){
            case BOOK_URI_CODE:
                tableName = DbOpenHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                tableName = DbOpenHelper.USER_TABLE_NAME;
                break;
            default:break;
        }
        return tableName;
    }
    @Override
    public boolean onCreate() {
        Log.d("TAG","onCreate,current thread:"+Thread.currentThread().getName());
        mContext = getContext();
        initProviderData();
        return true;
    }
    private void initProviderData(){
        mDb = new DbOpenHelper(mContext,AUTHORITY,null,1).getWritableDatabase();

        mDb.execSQL("delete from "+DbOpenHelper.BOOK_TABLE_NAME);
        mDb.execSQL("delete from "+DbOpenHelper.USER_TABLE_NAME);
        mDb.execSQL("insert into book values (3,'Android');");
        mDb.execSQL("insert into book values (4,'iOS');");
        mDb.execSQL("insert into book values (5,'HarmonyOs');");
        mDb.execSQL("insert into user values (1,'jack',1);");
        mDb.execSQL("insert into user values (2,'hi',0);");
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d("TAG","query,current thread:"+Thread.currentThread().getName());
        String table = getTableName(uri);
        if(table==null){
            throw new IllegalArgumentException("Unsupport URI:"+uri);
        }
        return mDb.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);

    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.d("TAG","getType");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.d("TAG","insert");
        String table =getTableName(uri);
        if(table==null){
            throw new IllegalArgumentException("Unsupport URI:"+uri);
        }
        mDb.insert(table,null,values);
        mContext.getContentResolver().notifyChange(uri,null);
        return uri;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d("TAG","delete");
        String table =getTableName(uri);
        if(table==null){
            throw new IllegalArgumentException("Unsupport URI:"+uri);
        }
        int count =mDb.delete(table,selection,selectionArgs);
        if(count>0){
            getContext().getContentResolver().notifyChange(uri,null);
        }
        return count;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d("TAG","update");
        String table = getTableName(uri);
        if(table==null){
            throw new IllegalArgumentException("Unsupport URI:"+uri);
        }
        int row = mDb.update(table,values,selection,selectionArgs);
        if(row>0){
            getContext().getContentResolver().notifyChange(uri,null);
        }
        return row;
    }
}

其中:

 private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);     
static {        
    sUriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);  
    sUriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);    
	}

声明了一个静态的 UriMatcher 对象 sUriMatcher,并使用 UriMatcher.NO_MATCH 作为构造函数的参数初始化它。UriMatcher.NO_MATCH 表示默认的不匹配值。

  • sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);:将 AUTHORITY 和 “book” 这个路径关联起来,并为它们设置一个对应的 BOOK_URI_CODE。这意味着当传入的 Uri 与这个路径匹配时,可以通过 BOOK_URI_CODE 来识别和处理。
  • sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);:将 AUTHORITY 和 “user” 这个路径关联起来,并为它们设置一个对应的 USER_URI_CODE。这意味着当传入的 Uri 与这个路径匹配时,可以通过 USER_URI_CODE 来识别和处理。

后面没啥说的

5.2.3MainActivity

 contentprovier_1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri bookUri = Uri.parse("content://com.example.ipc.provider/book");
                ContentValues values = new ContentValues();
                values.put("_id",6);
                values.put("name","程序设计的艺术");
                getContentResolver().insert(bookUri,values);
                Cursor bookCursor = getContentResolver().query(bookUri,new String[]{"_id","name"},null,null,null);

                while(bookCursor.moveToNext()){
                   Book book = new Book(bookCursor.getString(1),bookCursor.getInt(0));
                   Log.d("TAG","query book:"+book.toString());
                }
                bookCursor.close();


                Uri userUri = Uri.parse("content://com.example.ipc.provider/user");
                Cursor userCursor = getContentResolver().query(userUri,new String[]{"_id","name","sex"},null,null,null);
                while(userCursor.moveToNext()){
                    User user = new User();
                    user.userName = userCursor.getString(1);
                    user.userId = userCursor.getInt(0);
                    user.isMale = userCursor.getInt(2) ==1;
                    Log.d("TAG","query user:"+user.toString());
                }
                userCursor.close();
            }
        });

6.Socket

我之前写聊天室就是这么用的

客户端

socket.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    InetAddress inetAddress = InetAddress.getLocalHost();
              
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }

                Intent intent = new Intent(MainActivity.this, SocketService.class);
                startService(intent);
                Socket socket = null;
                while (socket == null) {
                    try {
                        socket = new Socket("10.0.2.2", 5223);
                        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
                        out.println("你好啊!");
                        // 发送消息给服务器
                        Log.d("TAG", "Client端" + " 收到Service端发的消息:" + in.readLine());
                        // 接收服务器的回应消息
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        Log.d("TAG", "客户端问题");
                    }
                }
            }
        });
        thread.start();
    }
});

服务端

 @Override
    public void onCreate() {
        super.onCreate();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                ServerSocket serverSocket = null;
                try {
                    serverSocket = new ServerSocket(5223);
                    Log.d("TAG","Service端成功");
                    try {

                        Socket clientSocket = serverSocket.accept();
                        Log.d("TAG","Service端:"+" "+"Client端等待连接");
                        BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

                        out.println("服务端收到");
                        String message = in.readLine();
                        Log.d("TAG","Service端:"+" "+"收到Client端来信"+message);
                        clientSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        Log.d("TAG","不知道");
                    }
                    finally {
                        if (serverSocket != null){
                            try {
                                serverSocket.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                } catch (IOException e) {
                    Log.d("TAG","1666不行");
                    e.printStackTrace();
                }
                Log.d("TAG","Service端:"+" "+"Service端开启");


            }
        });
        thread.start();
    }
}

但是我只能打印出

Log.d(“TAG”,“Service端成功”);

其他打印不出来

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

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

相关文章

【JavaWeb】-- Request和Response、JSP、会话技术

文章目录 Request和Response1.概述2.Request对象2.1 Request继承体系2.2Request获取请求数据2.3 IDEA创建Servlet2.4 请求参数中文乱码问题POST请求解决方案GET请求解决方案 2.5 Request请求转发 3.Response对象3.1 Response设置响应数据功能介绍3.2 Response请求重定向3.3 路径…

RabbitMQ养成记 (8. 消费者接受消息可靠性 consumer Ack)

Consumer Ack ack 指的是acknowledge 确认&#xff0c; 指的是消费端收到消息后的确认方式。 有三种确认方式&#xff1a; 自动确认手动确认 &#xff08;根据业务情况 手动确认是否成功发送&#xff09;根据异常情况确认 我们在消费端用代码实践一下&#xff1a; 首先我们定…

不会还有人不知道软件测试报告模板怎么写吧?

在测试岗位上&#xff0c;写报告是一项非常重要的软技能&#xff0c;写得好可以加分&#xff0c;写不好必然减分。 但在测试岗位上&#xff0c;提测“通过”和提测“不通过"的软件测试报告所包含的内容是不一样的&#xff08;这是个坑&#xff09;。但很多测试新人可能并…

LIO-SAM UBUNTU16.04.7 ROS-KINETIC 环境 编译 安装

简单记录一下 VMWARE workstation15UBUNTU16.04.7ros-kineticgtsam4.0.0 &#xff08;默认16.04比较老旧&#xff0c;不好用&#xff0c;vmtools也都没有&#xff0c;选了一个.7&#xff09; 选16.04版本的理由也简单&#xff0c;只是为了参考一个博客&#xff0c;单独建的环…

C语言-易错点汇总

易错点汇总 指针数组和数组指针(*p).astruct stdent和typedef struct stdentF5和CtrlF5const的位置全局变量和局部变量两个相同的常量字符串数组名和&数组名数组指针定义的解读int (*arr[10])[5]数组传参(* (void (*)())0)();关于数组名是否表示整个数组和数组首元素的地址…

Java位运算

Java >>和>>>的区别 | 或&#xff1a; 有1得1&#xff0c; 全0得0 int temp a|c;System.out.println(Integer.toBinaryString(a));System.out.println(Integer.toBinaryString(c));System.out.println(Integer.toBinaryString(temp));/*** 结果输出* * 01* …

对比SQL学Python:筛选|条件判断

SQL里筛选数据主要用到了where、and、in等语句。而在Python里实现筛选的方式比较多样&#xff0c;用到了 与&或|非~ 逻辑运算符&#xff0c;和isin()函数等。我们感受一下二者的区别吧&#xff1a; 汇总&#xff1a; 类型Python语句参考单条件筛选 data[data[shop_type]A…

Python复制目录及其子目录下的所有文件到指定新目录并重命名

Python复制目录及其子目录下的所有文件到指定新目录并重命名 前言前提条件相关介绍实验环境Python复制目录及其子目录下的所有文件到指定新目录并重命名代码实现输出结果 前言 本文是个人使用Python处理文件的电子笔记&#xff0c;由于水平有限&#xff0c;难免出现错漏&#x…

最常用的界线矢量数据大合集(文末有附下载方法)

最近收集了挺多比较常用的矢量界线数据&#xff0c;例如地理分区、气候分区等界线数据&#xff0c;在日常制图、学习、科研等方面使用频率比较高。废话不多说&#xff0c;这里给大家分享一下&#xff01;&#xff01; 1、中国农业熟制区划矢量数据 2、黄土高原空间范围矢量数据…

【Leetcode -509.斐波那契数 -520.检测大写字母】

Leetcode Leetcode - 509.斐波那契数Leetcode - 520.检测大写字母 Leetcode - 509.斐波那契数 题目&#xff1a;斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。 该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。…

node笔记_koa框架是什么?

文章目录 ⭐前言⭐ koa框架是如何发展而来的&#xff1f;⭐ koa框架的基本使用&#x1f496; 安装 koa&#x1f496; koa的Middleware示例&#x1f496; 支持xml ⭐ 结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文介绍node的一个web框架koa。 往期文章 node_window…

第三章: Mybatis-Plus 的通用CRUD API 练习使用

目录 1. Insert 操作 -> Create 1.1: TableId 的 id策略 1.2: TableField 2. Update 操作 -> Update 2.1: 根据 ID 更新 2.2: 根据条件更新 3. Delete 操作 -> Delete 3.1: deleteById 3.2: deleteByMap 3.3: delete 3.4: deleteBatchIds 4. Select 操作 -&g…

Redis修炼 (15. redis的持久化-RDB)

RDB 就是 redis database backup file 数据备份文件 就是把内存中的所有数据都保存在磁盘中。 save 注意这个保存过程是主进程在做 因为redis 是单线程 所有其他所有请求都会被卡死。 bgsave 这个稍微友好一点 是子进程 执行&#xff0c;避免主进程收到影响。 redis在服务停机…

【JavaWeb】-- Maven基础、MyBatis

文章目录 Maven基础1.Maven简介1.1 Maven模型1.2 仓库 2.Maven安装3.Maven基本使用3.1 Maven常用命令3.2 Maven生命周期 4.IDEA使用Maven4.1 IDEA配置Maven环境4.2 Maven坐标4.3 IDEA 创建Maven项目 5.依赖管理5.1 使用坐标引入jar包5.2 依赖范围 MyBatis1.MyBatis概述1.1JDBC的…

JDK、JRE和JVM三者的区别和联系

一、JDK、JRE、JVM分别是什么 &#xff08;一&#xff09;JDK JDK&#xff0c;全称Java Development Kit&#xff0c;是 Java 语言的软件开发工具包&#xff0c;主要用于移动设备、嵌入式设备上的Java应用程序。JDK是整个Java开发的核心。 &#xff08;二&#xff09;JRE J…

Day970.数据库表解耦 -遗留系统现代化实战

数据库表解耦 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于数据库表解耦的内容。 微服务拆分之初&#xff0c;需要搭建好的两个基础设施&#xff0c;一个是基于开关的反向代理&#xff0c;另一个是数据同步机制。 有了这两个设施做保障&#xff0c;接下来就可以…

Python合并同名Labelme标注文件内容

Python合并同名Labelme标注文件内容 前言前提条件相关介绍实验环境Python合并同名Labelme标注文件内容Json文件代码实现输出结果json文件 前言 本文是个人使用Python处理文件的电子笔记&#xff0c;由于水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。 (https://b…

Sping核心知识点总结

Spring框架日渐成熟&#xff0c;已经成为java开发中比不可少的部分&#xff0c;框架这东西我的理解里属于工具型应用&#xff0c;意味着如果没有大量实践之前之间研究理论 研究源码之类的 体会会很效率会很低&#xff0c;所以个人建议萌新先找个项目做一做&#xff0c;感受一下…

【C/C++的内存管理】

欢迎阅读本篇文章 前言&#x1f355;1. C/C内存分布1.1有关C/C的一道题目 &#x1f355;2. C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free&#x1f355;3. C内存管理方式3.1 new/delete操作内置类型3.2 new和delete操作自定义类型 &#x1f355;4. operator …

论国内如何免费使用GPT4

什么是GPT&#xff0c;能做什么&#xff1f; GPT&#xff0c;全名为Generative Pre-trained Transformer&#xff0c;是一类基于Transformer架构的自然语言处理模型。GPT的主要功能包括&#xff1a; 文本生成&#xff1a;能够根据给定的输入生成合理的文本&#xff0c;如文章、…