现在的位置: 首页 > 综合 > 正文

Android服务通信

2013年03月28日 ⁄ 综合 ⁄ 共 11255字 ⁄ 字号 评论关闭

Android服务类型:                     

        

(1)、本地服务(LocalService)

 

(2)、远程服务(RemoteService)

 

(本文需要数据交互的地方全部以计数器为例)

   private int count = 0;
 
         new Thread() {
         public void run() {
            while (run) {
                try {
                   Thread.sleep(1000);
                }catch(InterruptedException e) {
                   e.printStackTrace();
                }
                count++;
                Log.i("Server","count = " + count);
            }
         };
      }.start();
   };

 

一、本地服务。

 

1、与Activity之间没有交互的Service服务。

        

         通过startService和stopService进行。通过Intent意图对象进行控制。每个Service只能启动一次,即使多次调用startService也只有一个Service在运行。(免代码)

 

2、与Activity交互的Service服务。

        

原理:

当Activity与Service之间有数据交互时,Service会将服务数据给Binder保存,然后Activity通过IBinder获取到数据。相当于在内存开辟了一块共享数据的区域。

        

(1)、创建一个接口IRemoteService。该接口内部的方法就是获取数据的方法,当然,接口内的方法是抽象方法,没有方法体。例如本文,Activity要获取Service中的计数器count,因此,接口中的方法可以定义为getCount();

        

(2)、创建一个新类RemoteService,继承Service。你必须要继承Service的onBind()方法。在该服务类的onCreate方法内做如下工作:

        

                  a、创建一个线程并启动,线程不断的Thread.sleep(1000);和 count++

        

                  b、创建一个新类CountBinder,继承Binder类,并实现IRemoteService接口,实现接口的getCount()方法。就是返回count的值。这里的逻辑是将Service中的count的值交给Binder,让Binder将值交给调用该服务的Activity。这里解释下:Binder是IBinder的直接子类,所以我们一般不会直接实现IBinder接口,这里实现IRemoteService只是要用getCount()方法。

                    

                   c、创建   CountBinder的对象,然后在onBind()方法中返回该对象。这个对象中带着count计数器。

 

                   d、在清单文件里配置RemoteService节点,并添加一个action。名字无所谓。客户端绑定该服务时需要使用这个action。

        

         (3)、创建MainActivity,继承Activity。在这个Activity中要使用刚才的服务,将服务中的计数器传递给Activity来做显示。所以,就像一般的客户端那样,需要有一个连接。这里使用java

的ServiceConnection接口,实现该接口并获得一个引用。该接口必须实现两个方法:onServiceConnected(ConponentName name,IBinder service)和onServiceDisConnected(ConponentNamename).在第一个方法里拿到IBinder,这个IBinder就是服务中的Binder,他持有服务器端定义的计数器服务。所以在MainActivity中先声明一个IRemoteService 对象,但不初始化。在onServiceConnected(ConponentName
name,IBinder service)中将其初始化,把IBinder赋值给IRemoteService对象。在onServiceDisConnected(ConponentNamename)中还要将其释放。通过IRemoteService对象,就可以拿到服务器端计数器的数据了。

                           绑定服务:onCreate时,使用this.bindService(Intentservice,ServiceConnection conn,int flags)绑定服务。这里的Intent需要将服务类清单文件节点下声明的action作为参数,以定位到指定的服务。

 

                   来源码:

                   RemoteService.java

package com.example.rpcdemo.service;
 
import com.example.rpcdemo.iservice.IRemoteService;
 
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
 
public classRemoteService extends Service {
 
   private int count = 0;// 计数器
 
   private boolean run = true;// 服务销毁时停止while循环。其实整个服务就是在不停的循环
   /*
    * 创建服务器端Binder。在客户端通过ServiceConnection中的onServiceConnected(ComponentName
    * name,IBinder service)会拿到该Binder
    */
   private CountBinder countBinder = new CountBinder();
 
   /*
    * 客户端通过ServiceConnection中的onServiceConnected(ComponentNamename,IBinder
    * service)会拿到该Binder
    */
   @Override
   public IBinder onBind(Intentintent) {
 
      return countBinder;
 
   }
 
   @Override
   public void onCreate() {
 
      super.onCreate();
 
      // 开始计数循环,也就是开启服务了
 
      new Thread() {
 
         public void run() {
 
            while (run) {
 
                try {
 
                   Thread.sleep(1000);
 
                }catch(InterruptedException e) {
 
                   e.printStackTrace();
 
                }
 
                count++;
 
            }
 
         };
 
      }.start();
 
   }
 
   // 销毁服务
 
   @Override
   public void onDestroy() {
 
      super.onDestroy();
 
      run = false;
 
   }
 
   // 实现了远程调用接口的服务器端Bingder,服务器端将此Binder给客户端
 
   private class CountBinder extends Binder implements IRemoteService {
 
      @Override
      public int getCount() {
 
         return count;
 
      }
 
   }
 
}
                  

IRemoteService.java

package com.example.rpcdemo.iservice;
 
//远程调用的接口类。客户端使用的服务就是通过该接口的实现方法传递的。
 
public interfaceIRemoteService {
 
   public abstract int getCount();
 
}
        
 
         MainActivity.java
 
package com.example.rpcdemo;
 
import com.example.rpcdemo.R;
import com.example.rpcdemo.iservice.IRemoteService;
 
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.widget.TextView;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
 
public classMainActivity extends Activity {
 
   private TextView tv;
 
   private boolean run = true;
 
   private IRemoteService iRemoteService;// 自定义的远程调用接口
 
   private Handler handler = new Handler() {
 
      public voidhandleMessage(android.os.Message msg) {
 
         tv.setText(iRemoteService.getCount() + "秒");// 给组件设置值,只能在主线程里进行。
      };
   };
   private ServiceConnection connection = new ServiceConnection() {
 
      // 连接中断时,销毁IBinder
 
      @Override
      public voidonServiceDisconnected(ComponentName name) {
 
         iRemoteService = null;
 
      }
 
      // 连接创建时,获取IBinder
 
      @Override
      public voidonServiceConnected(ComponentName name, IBinder service) {
 
         iRemoteService =(IRemoteService) service;
 
      }
 
   };
 
   @Override
   public void onCreate(BundlesavedInstanceState) {
 
      super.onCreate(savedInstanceState);
 
      setContentView(R.layout.activity_main);
 
      // 客户端绑定服务的语句
 
      this.bindService(
            new Intent("com.example.rpcdemo.service.RemoteService"),
            connection, BIND_AUTO_CREATE);
 
      tv = (TextView)findViewById(R.id.tv);
 
      new Thread() {
 
         public void run() {
 
            while (run) {
 
                try {
 
                   Thread.sleep(1000);
 
                }catch(InterruptedException e) {
 
                   e.printStackTrace();
 
                }
 
                // 给主线程发送消息,令其给TextVews设值
 
                handler.sendEmptyMessage(0);
 
            }
 
         };
 
      }.start();
 
   }
 
   @Override
   protected void onDestroy() {
 
      super.onDestroy();
 
      run = false;// 服务销毁,循环终止
 
      this.unbindService(connection);// 解绑服务
 
   }
 
}
 

 

 

二、远程服务  

        

         也叫RPC,进程间通信。是一种应用与应用之间的通信。原理和代码与上面差不多。只是将原来IBinder实现的工作交给接口定义语言aidl来实现了。根据传递的数据类型的差别,又可以分为基本数据类型远程服务和其他数据类型的远程服务。

        

         1、只能传递简单数据类型的远程服务

 

         步骤:

 

         服务端:

 

                   (1)、在工程的src目录下,创建一个空文件,文件名字后缀为aidl。(下面有位置截图)

                  

(2)、将IRemoteService中的代码贴到aidl文件中。这里要注意,aidl中不能出现权限修饰符。

        

IRemoteService.aidl

 

packagecom.example.rpcdemo.iservice;
 
interface IRemoteService {
 
   abstract int getCount();
 
}

 

                            如果内容格式正确,Eclipse将自动在gen目录下生成一个同名包和一个同名的java文件---IRemoteService.java内容不需要修改。

                  

                   (3)、在RemoteService中还有一处改动,将onBind方法返回的IRemoteService对象改成IRemoteService.Stub对象即可

 

                   (4)、清单文件RemoteService的节点下配置action    名字是:com.joe.remoteservice

                  

                   服务端源码:

 

                            RemoteService.java

 

packagecom.example.remoteserver.service;
 
importcom.example.remoteserver.ibinder.IRemoteService;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
importandroid.os.RemoteException;
import android.util.Log;
 
public class RemoteService extends Service {
   private boolean run = true;//服务销毁时,停止while循环。
   private int count = 0;//计数器
 
   @Override
   public IBinder onBind(Intent intent) {
 
      return stub;
   }
   //自定义接口类,服务器端
   private IRemoteService.Stub stub = new IRemoteService.Stub() {
 
      @Override
      public int getCount() throws RemoteException {
         return count;
      }
   };
 
   public void onCreate() {
      super.onCreate();
      new Thread() {
         public void run() {
            while (run) {
                try {
                   Thread.sleep(1000);
                } catch (InterruptedExceptione) {
                   e.printStackTrace();
                }
                count++;
                Log.i("Server","count = " + count);
            }
         };
      }.start();
   };
 
   @Override
   public void onDestroy() {
      super.onDestroy();
     
      run = false;
   }
}
 

   MainActivity.java 服务启动界面(只有一个按钮,so布局文件不上了)

 

packagecom.example.remoteserver;
 
importcom.example.remoteserver.service.RemoteService;
 
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
importandroid.view.View.OnClickListener;
import android.widget.Button;
 
public class MainActivity extends Activity {
 
  private Button btn;
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     btn = (Button) findViewById(R.id.btn);
     btn.setOnClickListener(new OnClickListener() {
 
        @Override
        public void onClick(View v) {
 
           Intent i = new Intent(MainActivity.this, RemoteService.class);
           startService(i);
        }
     });
  }
 
  @Override
  protected void onDestroy() {
     super.onDestroy();
     Intent i = new Intent(MainActivity.this, RemoteService.class);
     stopService(i);
  }
}

 

 

图  IRemoteService.aidl文件及Eclipse生成的IRemoteService.java文件的位置

 

 

        

         客户端:

 

(1)、将服务端生成的aidl文件连同包一起复制到客户端的src目录下。(然后Eclipse会在gen目录下生成一个同名包同名的java文件)

 

                   (2)、只要将ServiceConnection中的onServiceConnected()方法中给IRemoteService直接赋值为IBinder参数的代码,改为将

 

   

   @Override
      public voidonServiceConnected(ComponentName name, IBinder service) {
 
         iRemoteService =IRemoteService.Stub.asInterface(service);
 
      }

 

     

      客户端源码:实现了显示服务端计数器的功能

 

     

 packagecom.example.remoteclient;
 
importcom.example.remoteserver.ibinder.IRemoteService;
 
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.app.Activity;
importandroid.content.ComponentName;
import android.content.Intent;
importandroid.content.ServiceConnection;
import android.util.Log;
importandroid.widget.TextView;
 
public class MainActivity extends Activity {
 
   private TextView tv;
 
   private boolean run = true;
 
   private IRemoteService iRemoteService;
 
   private ServiceConnection conn = new ServiceConnection() {
 
      @Override
      public void onServiceDisconnected(ComponentName name) {
 
         iRemoteService = null;
 
      }
 
      @Override
      public void onServiceConnected(ComponentName name,IBinder service) {
 
         iRemoteService = IRemoteService.Stub.asInterface(service);
 
      }
 
   };
 
   @Override
   public void onCreate(Bundle savedInstanceState) {
 
      super.onCreate(savedInstanceState);
 
      setContentView(R.layout.activity_main);
 
      tv = (TextView) findViewById(R.id.tv);
 
      this.bindService(new Intent("com.joe.remoteservice"), conn,
            BIND_AUTO_CREATE);
 
      new Thread() {
 
         public void run() {
 
            while (run) {
 
                try {
 
                   Thread.sleep(1000);
 
                } catch (InterruptedExceptione) {
 
                   e.printStackTrace();
 
                }
 
                handler.sendEmptyMessage(0);
 
            }
 
         };
 
      }.start();
 
   }
 
   @Override
   protected void onDestroy() {
 
      super.onDestroy();
 
      this.unbindService(conn);
 
      run = false;
 
   }
 
   private Handler handler = new Handler() {
 
      public void handleMessage(android.os.Message msg) {
 
         try {
 
            int count = iRemoteService.getCount();
           
            tv.setText(count + "");
 
         } catch (Exception e) {
 
            e.printStackTrace();
 
         }
 
      };
 
   };
 
}
 

      2、其他数据类型远程服务 

代码和基本数据类型基本上差不多。只不过将计时器由基本类型  int count改成了一个自定义的Bean类   Countor countor。Countor必须实现Parcelable接口.

        

         即使如此,还是有些需要注意的地方。

 

         下面先说下Countor类的编写。

        

         Countor类必须实现Parcelable接口,并实现Parcelable.Creator读取方法。该方法不是必须自己手动创建,Parcelable接口不会自己实现。之后要生成一个同名的aidl文件,Countor.aidl.

         代码很简单:

            parcelable Countor;

         记得还有有“Package 包名;”

         然后把这两个文件和包一起拷贝到客户端的src目录下。(关于Countor.java,服务端和客户端并不完全一样,贴出代码后会说)

         服务器端Countor.java源码。      

packagecom.example.countorservice.bean;
 
import android.os.Parcel;
import android.os.Parcelable;
 
//一定要实现Parcelable可序列化接口,非基本数据类型如果不实现该接口就无法并传递
 
public class Countor implements Parcelable {
  public int count = 0;// 像普通的Bean一样声明一个成员属性
 
  /*
   * 创建Parcelable接口内部类Creator的对象,泛型类型就是我们的计数器Bean.这里稍微注意下,生成ICountorService.
   * aidl生成的java文件里需要使用该引用
   * ,而且名字默认是CREATOR,所以这里尽量不要自己定义了,就写成大写的CREATOR吧。不过无所谓,这里乱写,那边编译时候会给你报错的.
   */
  public static final Parcelable.Creator<Countor> CREATOR = newCreator<Countor>() {
 
     @Override
     public Countor createFromParcel(Parcelsource) {// --------备注1
        Countor countor = new Countor();
        countor.count = source.readInt();// ---------------备注3
 
        return countor;
     }
 
     @Override
     public Countor[] newArray(int size) {
 
        return new Countor[size];
     }
  };
 
  @Override
  public int describeContents() {
 
     return 0;
  }
 
  @Override
  public void writeToParcel(Parcel dest, int flags) {// ---------------备注2
     dest.writeInt(count);   //---------------备注3
 
  }
 
}
 

 

重点解释:

 

备注1 : 这里有个参数类型Parcel,可以理解为内存区域。该区域可读可写。有点像Binder. createFromParcel(Parcel source) 方法是客户端Countor要使用的方法.客户端使用该

方法获取到服务器端传递过来的countor.所以这里先跳到 备注2.等说完 备注2这里就顺理成章了。

  备注2 : writeToParcel(Parcel dest, int flags)是服务器端的Countor必须实现的方法。我们在CountorService中开辟的子线程不断的自增长的count,通过这个方法被

写到了一块内存(Parcel)中去,那么,再回到 备注1中,就可以理解,为什么createFromParcel(Parcel source) 方法是客户端Countor必须实现的方法了。这个方法从刚才服

务器写入count的内存(Parcel)去读取了一个int值。这样,我们又把服务器和客户端连起来了。当然,如果你的客户端还有点返回数据的话,那就把客户端的createFromParcel和writeToParcel全部实现了就可以了。

 

备注3:

关于服务器端Countor和客户端Countor:

之前的三种服务出传递的数据都可以看成是写在一块共享内存区域内的。而复杂数据类型的aidl服务除了那块公共内存区域,还需要其他东西。针对本例,Countor是一个类,我们总不能把类写在那个内存,然后各自为政,自己创建自己的对象吧。所以,服务器端的Countor类和客户端的Countor类就有一些小小的不同。

首先,注意到Countor类中的两个方法:createFromParcel和writeToParcel。服务器端,需要实现writeToParcel,将count写到共享内存上。客户端实现createFromParcel,创建Countor对象,并从共享内存读取count值并赋值给Countor对象的count值,这样就完成了一次复杂数据类型的传递。

     写在后面:想知道那些数据类型是简单数据类型,可以使用备注3中的方法,如:dest.write然后alt+/.等敲完了代码,再总结下这些类型吧。

  

 

点击这里下载 同一应用远程调用服务Demo 免积分源码

点击这里下载 进程间基本数据类型通信Demo
免积分源码

  

     

 

 

 

 

 

 

 

 

 

 

抱歉!评论已关闭.