• 安卓Service完全解析(中)


    摘要: 版权声明:本文出自汪磊的博客,转载请务必注明出处。

    在上一篇中我们学习了Android Service相关的许多基础但是重要的内容,基本涵盖大部分平日里的开发工作。今天我们继续学习一下稍微高级一点的用法,即:远程Service用法,使用远程Service可以实现安卓跨进程通信的功能。下面我们就开始学习一下吧。

    什么是远程Service?

    所谓的远程Service就是与调用者不在同一进程的Service即可叫做远程Service。那是不是也有近程Service?其实不叫近程Service,专业叫法叫做本地Service,就是调用者与Service在同一进程。

    调用远程Service的桥梁-AIDL接口定义语言

    由于远程服务与调用者不在同一进程,我们不能再像调用本地服务一样的方式去调用远程服务,

    那么如何才能让Activity与一个远程Service建立关联呢?这就要使用AIDL来进行跨进程通信了(IPC)。

    AIDL(Android Interface Definition Language)是Android接口定义语言的意思,它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。(本篇着重讲解与远程服务通信的过程,不涉及AIDL接口定义语言的细节讲解)

    废话少说,下面我们通过一个简单的Demo来讲解一下与远程Service通信的过程。

    首先我们新建一个远程服务Demo的工程,新建RemoteService.aidl文件,代码如下:

    1 package com.wl.remoteservice;
    2 interface RemoteService{
    3     int call(int a,int b);
    4 }

    我们点击完保存之后,在gen目录下会生成一个与之对应的java文件,如下:

    关于文件中内容我们暂时不细究,会在下篇的时候在研究一下。

    然后编写Service类,新建WLService继承Service,如下:

     1 public class WLService extends Service {
     2 
     3     private static final String TAG = "HEART";
     4     @Override
     5     public IBinder onBind(Intent intent) {
     6         Log.i(TAG, "onBind");
     7         return new MyBinder();
     8     }
     9 
    10     private int methodInRemoteService(int a,int b){
    11 
    12         return a+b;
    13     }
    14     
    15     private class MyBinder extends RemoteService.Stub{
    16         
    17         @Override
    18         public int call(int a,int b) {
    19             
    20             return methodInRemoteService(a,b);
    21         }        
    22     }
    23     
    24     @Override
    25     public void onCreate() {
    26         Log.i(TAG, "onCreate");
    27         super.onCreate();
    28     }
    29     
    30     @Override
    31     public boolean onUnbind(Intent intent) {
    32         Log.i(TAG, "onUnbind");
    33         return super.onUnbind(intent);
    34 
    35     }
    36     @Override
    37     public void onDestroy() {
    38         Log.i(TAG, "onDestroy");
    39         super.onDestroy();
    40     }
    41     
    42 }

    这里我们定义了一个内部类MyBinder继承自RemoteService.Stub,那这个stub是什么玩意呢?点进去看一下,如下

     1 public static abstract class Stub extends android.os.Binder implements com.wl.remoteservice.RemoteService 

    看定义我们就明白了,原来MyBinder就是Binder子类并且实现了RemoteService接口,所以在onBind的时候返回其实例也就容易理解了。

    接下来,在清单文件注册Service。这里我们需要在另一个应用程序启动WLService,但是在另一个应用程序中去绑定Service的时候并没有WLService这个类,这时就必须使用到隐式Intent了。现在修改AndroidManifest.xml中的代码,给WLService加上一个action,如下所示:

    1         <service android:name="com.wl.remoteservice.WLService">
    2             <intent-filter >
    3                 <action android:name="com.wanglei.remoteservice"/>
    4             </intent-filter>
    5         </service>

    然后我们新建一个项目用以调用上面项目中的远程服务,新项目就叫做:调用远程服务。

    首先需要将RemoteService.aidl拷贝过来,记住需要连同原包路径一同拷贝过来,如下:

    接下来,修改布局文件:

     1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     2     xmlns:tools="http://schemas.android.com/tools"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     android:orientation="vertical"
     6     tools:context=".MainActivity" >
     7 
     8     <Button
     9         android:layout_width="fill_parent"
    10         android:layout_height="wrap_content"
    11         android:onClick="bind"
    12         android:text="绑定服务" />
    13 
    14     <Button
    15         android:layout_width="fill_parent"
    16         android:layout_height="wrap_content"
    17         android:onClick="unbind"
    18         android:text="解除绑定服务" />
    19 
    20     <Button
    21         android:layout_width="fill_parent"
    22         android:layout_height="wrap_content"
    23         android:onClick="click"
    24         android:text="调用远程服务方法" />
    25 
    26 </LinearLayout>

    然后编写MainActivity中代码,如下:

     1 public class MainActivity extends Activity {
     2     private static final String TAG = "HEART";
     3     private RemoteService mRemoteService;
     4     private MyConn conn;
     5 
     6     @Override
     7     protected void onCreate(Bundle savedInstanceState) {
     8         super.onCreate(savedInstanceState);
     9         setContentView(R.layout.activity_main);
    10     }
    11     
    12     public void bind(View view){
    13         Intent intent = new Intent();
    14         intent.setAction("com.wanglei.remoteservice");
    15         conn = new MyConn();
    16         bindService(intent, conn, BIND_AUTO_CREATE);//异步的操作
    17     }
    18     
    19     public void unbind(View view){
    20         unbindService(conn);
    21     }    
    22     
    23     public void click(View view){
    24 
    25         if(null != mRemoteService){
    26             try {
    27                 int result = mRemoteService.call(10,20);
    28                 Log.i(TAG, "result = "+result);
    29             } catch (RemoteException e) {
    30                 e.printStackTrace();
    31             }
    32         }
    33     }
    34     
    35     private class MyConn implements ServiceConnection{
    36         @Override
    37         public void onServiceConnected(ComponentName name, IBinder service) {
    38             
    39             mRemoteService = RemoteService.Stub.asInterface(service);
    40         }
    41 
    42         @Override
    43         public void onServiceDisconnected(ComponentName name) {
    44             
    45         }
    46     }
    47 }

    会发现和上篇讲的bind服务差别不大,这里我们需要在另一程序里面绑定服务所以需要用隐式意图开启。

    这里我们需要注意一下,在安卓5.0出来以后,服务意图必须用显式调用,调用bind服务的时候会报如下警告

    我们找一下源码,看看哪里报的这个警告,终于在 sdk/sources/android-21/android/app/ContextImpl.Java目录下找到报警告的代码:

     1     private void validateServiceIntent(Intent service) {
     2         if (service.getComponent() == null && service.getPackage() == null) {
     3             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
     4                 IllegalArgumentException ex = new IllegalArgumentException(
     5                         "Service Intent must be explicit: " + service);
     6                 throw ex;
     7             } else {
     8                 Log.w(TAG, "Implicit intents with startService are not safe: " + service
     9                         + " " + Debug.getCallers(2, 3));
    10             }
    11         }
    12     }

    好了找到报错的原因了,那怎么解决呢?观察源码发现最外层if判断的时候有个service.getPackage() == null,我们只需要使其不为null不就可以了,这也是谷歌推荐的解决方法

    我们将绑定服务的方法改为如下:

    1 public void bind(View view){
    2         Intent intent = new Intent();
    3         intent.setAction("com.wanglei.remoteservice");
    4         intent.setPackage("com.wl.remoteservice");//设置为Service所在包名
    5         conn = new MyConn();
    6         bindService(intent, conn, BIND_AUTO_CREATE);//异步的操作
    7 }

    这样修改就好了,当然还有另外一种解决方法,将隐式意图变为显示意图,方法如下:

     1 
     2 public static Intent getExplicitIntent(Context context, Intent implicitIntent) {  
     3         // Retrieve all services that can match the given intent  
     4         PackageManager pm = context.getPackageManager();  
     5         List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);  
     6         // Make sure only one match was found  
     7         if (resolveInfo == null || resolveInfo.size() != 1) {  
     8             return null;  
     9         }  
    10         // Get component info and create ComponentName  
    11         ResolveInfo serviceInfo = resolveInfo.get(0);  
    12         String packageName = serviceInfo.serviceInfo.packageName;  
    13         String className = serviceInfo.serviceInfo.name;  
    14         ComponentName component = new ComponentName(packageName, className);  
    15         // Create a new intent. Use the old one for extras and such reuse  
    16         Intent explicitIntent = new Intent(implicitIntent);  
    17         // Set the component to be explicit  
    18         explicitIntent.setComponent(component);  
    19         return explicitIntent;  
    20     } 

    好了,通过以上两种方式就可以解决了,到此我们两个项目都已经编写完毕,先运行远程服务demo项目,在运行调用远程服务项目,点击绑定远程服务按钮,然后点击调用远程服务按钮,打印如下:

    到此为止,我们成功调用了远程服务中的方法,跨进程通信成功实现,不过有些刚接触的同学可能还有些混乱,怎么觉得那么混乱,比平时用的组建多那么多步骤,那我们总结一下调用远程服务的步骤:

    1.编写aidl文件

    2.创建远程服务类,在服务的内部创建一个内部类提供一个方法,可以间接调用服务的方法

    3.实现服务的onbind方法,返回服务内部类实例

    4.拷贝aidl文件到另一工程联同包名

    5.在activity 绑定服务。bindService();

    6.在服务成功绑定的时候 会执行一个方法 onServiceConnected 传递过来一个 IBinder对象进行类型转换

    7.调用远程服务里面的方法。

    好了,到这里关于服务的所有最重要的部分都已经讲解完了,通过(上)(中)两篇相信你对服务有了一个全面的认识。

  • 相关阅读:
    zookeeper安装和使用
    一个最简单的Dubbo入门框架
    Dubbo Admin管理平台搭建
    Docker容器入门实践
    vue 项目安装 (入门)
    测试任何对象的某个特性是否存在 兼容js
    用户代理字符串检测呈现引擎、平台、Windows操作系统、移动设备和游戏系统 js
    React
    React (4) -- Conditional Rendering
    React (5) -- Lists and Keys
  • 原文地址:https://www.cnblogs.com/leipDao/p/7365570.html
Copyright © 2020-2023  润新知