Android开发-跨进程通讯

10.1、消息机制

当我们的服务器与启动端不是在统一项目内时,即服务器与页面属于不同的进程,

那么我们按照之前的方法来启动绑定服务时,会崩溃,因为找不到对应的本地服务器

所以我们要实现跨进程获取service,通过绑定模式

将服务器端设置为非本地

<service android:process=":remote"/>

设置后,会把服务器端定义为远程服务器,此时若进行绑定则会崩溃,

所以此时如果想要向服务器端发送消息,需要用到消息机制

  • 安卓的消息机制:Message消息Messenger信使Handler消息处理,一般处理消息(线程)的同步

  • 安卓app的进程,进程名称就是该app的包名

  • 在安卓组件注册时,可通过process属性设置组件进程的相关状态

1、客户端向服务器端发送消息

由于安卓的消息机制是基于绑定模式的,因此我们要先编写绑定模式框架

跨进程服务必须要借助消息机制,即包装成消息进行通信

服务器端:建立信使

步骤:

  1. 准备Handler对象,指定消息处理方式:

编写自定义类继承Handler,重写handleMessage回调函数 handleMessage函数在收到消息对象后回调。每当收到一条消息,都会回调一次,收到的消息对象即该函数的形参

  1. 准备一个信使Messenger对象,替换Binder对象 Messenger对象需要传入一个Handler对象,用作消息处理,即我们步骤一创建的handler对象

  2. 从创建的Messenger对象中通过getBinder函数提取出消息机制中的Binder对象,用作onbind函数返回

 public class srv extends Service {
     class handler extends Handler{
         @Override
         public void handleMessage(@NonNull Message msg) {
             super.handleMessage(msg);
         }
     }
     handler handler;
     Messenger srv_receive_msger;
     @Override
     public void onCreate() {
         super.onCreate();
         handler=new handler();
         srv_receive_msger=new Messenger(handler);
     }
     @Nullable
     @Override
     public IBinder onBind(Intent intent) {
         return srv_receive_msger.getBinder();
     }
 }

这样我们就用Messenger对象替换Binder对象,用作消息传递

启动端:发送消息

步骤:

  1. 连接成功后,将返回的Binder对象转换为Messenger对象 创建Messenger对象,将获取的Binder对象作为参数来转换

  2. 准备消息对象,由于每次所传递的消息不一样,所以我们在获取对象时创建消息

即每次获取服务时,将消息通过messenger传递到启动端 在创建Message对象时,需要使用obtain函数来获取

 public static Message obtain ()
 //Return a new Message instance from the global pool. Allows us to avoid allocating new objects in many cases.
  1. 通过Messenger对象的send函数向服务器端传递消息

    Message对象内定了一个数据传输的函数:setData()getData()

     public void setData (Bundle data)
     //Sets a Bundle of arbitrary data values. 
     Messenger act_send_msger;
     //重构自定的Connection内部类
     class Connection implements ServiceConnection{
         @Override
         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
             act_send_msger=new Messenger(iBinder);
         }
         @Override
         public void onServiceDisconnected(ComponentName componentName) {
             if(it!=null) it=null;
         }
     }
     //获取服务事件监听器
     case R.id.btn_get_service:{
         Message msg=Message.obtain();
         msg.what=101;//标识发送方
         Bundle bd=new Bundle();
         bd.putString("act_msg","send by activity:hello");
         msg.setData(bd);
         if(msg!=null)
             try {
                 act_send_msger.send(msg);
             } catch (RemoteException e) {
                 e.printStackTrace();
             }
         break;
     }

我们通过按钮事件实现消息传递,

首先我们启动服务,建立启动端与服务器端的连接

当我们点击按钮时,就通过信使对象将我们创建的消息传递通过链接传递给服务器端

服务器端:接受消息

handleMessage函数在收到消息对象后回调。每当收到一条消息,都会回调一次,收到的消息对象即该函数的形参

class handler extends Handler{
     @Override
     public void handleMessage(@NonNull Message msg) {
         super.handleMessage(msg);
         switch (msg.what){
             case 101:{
                 Bundle bd=msg.getData();
                 String str=bd.getString("act_msg");
                 Log.i("mytag",str);
                 break;
             }
         }
     }
 }

通过msg.what成员变量,来区分是由那个启动端发送来的消息

Message对象的成员变量:

 public int arg1;//int 参数
 public int arg2;//int 参数
 public Object obj;
 public Messenger replyTo;//消息回复
 public int sendingUid = -1;
 public int what;//消息标记:标记该信息是由谁传递的

测试:

android:process:

一个进程的名称,应用的所有组件都应在该进程中运行。 每个组件都可以通过设置自己的 process 属性来替换此默认值。

默认情况下,当应用的第一个组件需要运行时,Android 会为该应用创建一个进程。所有组件随后都在该进程中运行。 默认进程的名称与由 <manifest> 元素设置的软件包名称一致。

<application> 元素的 process 属性可以为所有组件设置不同的默认值。不过,组件可以使用其自己的 process 属性替换默认属性,从而允许您跨多个进程分布应用。

如果为此属性分配的名称以冒号(“:”)开头,则会在需要时创建一个应用专用的新进程。 如果进程名称以小写字符开头,则会创建一个采用该名称的全局进程。一个应用可与其他应用共享全局进程,从而减少资源使用量。

 

由于服务器端我们在清单文件中配置了以(“:”)开头的process名,所以Android创建了一个应用专用的新进程

我们在启动端向服务器端传递的消息,则在:remote进程中收到。

 

而我们的本地进程是收不到的

2、服务器端向客户端回传消息

服务器端的消息发送一般是被动的发送响应消息

当启动端向服务器端发送消息后,服务器端给予启动端一个响应

服务器端:回复响应

步骤:

  1. 准备消息对象

  2. 接受启动端消息后,从消息对象的replyto中获取启动端的信使对象

  3. 调用信使对象的send函数实现消息传递

 switch (msg.what){
     case 101:{
         //消息接受
         Bundle bd=msg.getData();
         String str=bd.getString("act_msg");
         Log.i("mytag",str)
         //消息响应
         Messenger srv_send_msger=msg.replyTo;
         Message srv_msg=Message.obtain();
         srv_msg.what=999;//999为服务器端
         Bundle bd1=new Bundle();
         bd1.putString("reply","service's reply:hello");
         srv_msg.setData(bd1);
         if(srv_send_msger!=null) {
             try {
                 srv_send_msger.send(srv_msg);
             } catch (RemoteException e) {
                 e.printStackTrace();
             }
         }
         break;
     }
 }

一般服务器的消息属于响应消息,即启动端向服务器端发送消息后,立马得到一个来自服务器的响应

所以服务器端在收到启动端的消息时,就立马响应

启动端:接受响应

步骤:

  1. 准备新的messenger对象

  2. 在启动向服务器端发送消息时,通过消息对象的replyto成员设置启动端的信使对象, 从而服务端可以获取启动端的信使对象,以实现服务端向启动端的响应

 //定义信使对象
 Messenger act_receive_msger=new Messenger(new Handler(){
   @Override 
     public void handleMessage(@NonNull Message msg) {
         super.handleMessage(msg);
         switch (msg.what){
             case 999:{
                 Bundle bd=new Bundle();
                 bd=msg.getData();
                 Log.i("mytag",bd.getString("reply"));
                 break;
             }
         }
     }
 });//在获取服务的事件监听器上设置reply对象
 //在发送消息前设置
 msg.replyTo=act_receive_msger;java

replyTo变量:

 public Messenger replyTo
 //Optional Messenger where replies to this message can be sent.

3.流程

启动端——>服务器端:

  1. 启动端建立Activity-Message消息对象用来存放即将发送的消息

  2. 将消息对象交给Activity的发送信使Activity_send_Messenger

  3. Activity_send_Messenger的本质是对Service_receive_Messenger的一个引用,这两个对象其实是同一个对象

    1. 当我们在启动端启动服务器服务时,会调用服务器端的onbind回调函数,而该回调函数的返回对象就是Service_receive_Messenger

    2. 我们在启动端设置的Activity_send_Messenger就在连接成功时,建立起了引用关系

  4. Service_receive_Messenger服务器的接受收到消息后交给Service_Handler来处理

  5. 我们要实现的对消息的相关操作就在Handler里编写

服务器端——>启动端

  1. 服务器端建立Service-Message消息对象用来存放用来响应的消息内容

  2. 将消息对象交给Service_send_Messenger来实现消息发送

  3. Service_send_Messenger该信使对象,该信使将消息回传给发送方

    1. 在该信使对象创建时,是将发送方的replyTo变量赋值给了该信使

    2. 在发送方发送消息时,就把发送方的接受信使通过replyTo变量传递给了服务器

    3. 服务器的发送信使,通过replyTo找到了目标信使,从而实现消息的回传

  4. 发送方收到消息后,交给发送方的Handler对象来处理

  5. 我们要实现的对服务器响应消息的接受就在这里编写

4.消息机制对线程的关系:ui进程

例如,我们想要在UI界面上显示实时时间

通过一个死循环:

 findViewById(R.id.btn_get_time).setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View view) {
         flag=true;
         while(flag)
             Log.i("mytag",""+System.currentTimeMillis());
     }
 });
 findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View view) {
         flag=false;
     }
 });

两个按钮控制,启停

可以发现,当程序在执行死循环时,UI界面无法互动,因为在后台程序运行完之前,前台时一致保持未响应的状态

这样就导致了UI线程的阻塞

解决:

将其运行在线程中,即可实现启停

 new Thread(){
     public void run() {
         flag=true;
         while(flag)
             Log.i("mytag",""+System.currentTimeMillis());
     }
 }.start();

这是在Logcat窗口来显示时间值,那么如果我们想要在UI页面上显示

设置一个Textview来放置时间

 new Thread(){
     public void run() {
         flag=true;
         while(flag)
             textView.setText("Time:"+System.currentTimeMillis());
     }
 }.start();

发现在运行时,程序崩溃,报错:

安卓UI中的各种界面元素,不支持异步操作,只能由UI线程来操作

意味着需要做线程同步,安卓使用消息机制(安卓线程同步)

通过消息传递,将消息传递给自己

将消息传递给Handler,UI中的元素需要在Handler中进行处理

public class Amain extends Activity {
     boolean flag;
     Messenger msger;
     TextView textView;
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.ly_main);
         class handler extends Handler{
             @Override
             public void handleMessage(@NonNull Message msg) {
                 super.handleMessage(msg);
                 switch (msg.what){
                     case 111:{
                         Bundle bd=new Bundle();
                         bd=msg.getData();
                         textView.setText(bd.getString("key"));
                     }
                 }
             }
         }
         handler handler=new handler();
         msger=new Messenger(handler);
         textView=(TextView)findViewById(R.id.tv_time);
         findViewById(R.id.btn_get_time).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
                 new Thread(){
                     public void run() {
                         flag=true;
                         while(flag){
                             Message msg=Message.obtain();
                             msg.what=111;
                             Bundle bd=new Bundle();
                             bd.putString("key","Time:"+System.currentTimeMillis());
                             msg.setData(bd);
                             if(msger!=null)
                                 try {
                                     msger.send(msg);
                                 } catch (RemoteException e) {
                                     e.printStackTrace();
                                 }
                             try {
                                 Thread.sleep(1000);
                             } catch (InterruptedException e) {
                                 e.printStackTrace();
                             }
                         }
                     }
                 }.start();
             }
         });
         findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
                 textView.setText("stop");
                 flag=false;
             }
         });
     }
 }
 

10.2、AIDL

Android 接口定义语言 (AIDL) 与您可能使用过的其他接口语言 (IDL) 类似。您可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。

官方文档:Android 接口定义语言 (AIDL)  |  Android 开发者  |  Android Developers

AIDL接口文件的声明

步骤:

  1. 直接使用Android Studio直接创建文件 src\main\aidl

  2. 编写服务

 interface Srv_aidl {
     //接口中的函数为服务端提供服务
     int add(int a,int b);
 }

服务器端:

步骤:

  1. 创建AIDL接口的存根对象(Stub)

  2. 通过onBind回调函数返回存根对象

  3. 设置隐式跳转路径

 import com.example.aidl.Srv_aidl;
 public class MyService extends Service {
     Srv_aidl.Stub istub=new Srv_aidl.Stub() {
         @Override
         public int add(int a, int b) throws RemoteException {
             return a+b;
         }
     };
     @Override
     public IBinder onBind(Intent intent) {
         return istub;
     }
 }
 <action android:name="myaction"/>

aidl接口在安卓默认实现时,会自动添加一个Stub静态抽象内部类 该类继承了aidl接口(并没有实现接口中的函数),同时继承了Binder类

 //源码
 public static abstract class Stub extends android.os.Binder implements com.example.aidl.Srv_aidl

启动端

步骤:

  1. 双进程创建

     

    构造跨进程通讯环境

  2. 创建aidl文件,保证与服务端的一致(推荐文件拷贝),复制到启动进程的main文件夹下

    注意:启动端的aidl包名须与服务端的一致

  3. 启动端绑定模式框架

  4. 绑定成功后,使用存根对象Stubasinterface函数,将binder对象转换为接口对象

  5. 通过接口对象调用接口的服务函数

  6. 通过页面文本框显示结果

 import com.example.aidl.Srv_aidl;
 public class Amain extends Activity {
     Intent it;
     Srv_aidl srv_aidl;
     connection connection;
     TextView tv;
     boolean isbound;//用于健壮性检测
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.ly_main);
         
         it=new Intent();
         it.setAction("myaction");
         it.setPackage("com.example.aidl");//完全包名
 ​
         connection=new connection();
 ​
         tv=(TextView) findViewById(R.id.tv_view);
         findViewById(R.id.btn_startservice).setOnClickListener(listener);
         findViewById(R.id.btn_getservice).setOnClickListener(listener);
         findViewById(R.id.btn_stopservice).setOnClickListener(listener);
     }
     class connection implements ServiceConnection{
         @Override
         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
             srv_aidl=Srv_aidl.Stub.asInterface(iBinder);
             if(srv_aidl!=null) isbound=true;
         }
         @Override
         public void onServiceDisconnected(ComponentName componentName) {
             if(srv_aidl!=null) srv_aidl=null;
             isbound=false;
         }
     }
     View.OnClickListener listener=new View.OnClickListener() {
         @Override
         public void onClick(View view) {
             switch(view.getId()){
                 case R.id.btn_startservice:{
                     if(it!=null && connection!=null &&!isbound)
                         bindService(it,connection,BIND_AUTO_CREATE);
                     break;
                 }
                 case R.id.btn_getservice:{
                     if(srv_aidl!=null && isbound)
                         try {
                             int num=srv_aidl.add(10,20);
                             tv.setText("10+20="+num);
                         } catch (RemoteException e) {
                             e.printStackTrace();
                         }
                     break;
                 }
                 case R.id.btn_stopservice:{
                     if(connection!=null && isbound) {
                         unbindService(connection);
                         srv_aidl=null;
                         isbound=false;
                     }
                     break;
                 }
             }
         }
     };
 }
 

测试:

首先开启服务器端

由于服务器端是没有页面显示的,我们要在运行配置中将运行页面设置为nothing

然后开启启动端

 

结果:

 

同样,我们可以设置随机两个数进行加法运算,通过每次点击获取服务,获得任意两个数的和

 case R.id.btn_getservice:{
     if(srv_aidl!=null && isbound)
         try {
             int x= (int) (Math.random()*100);
             int y= (int) (Math.random()*100);
             int num=srv_aidl.add(x,y);
             tv.setText(x+"+"+y+"="+num);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
     break;
 }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值