zoukankan      html  css  js  c++  java
  • Android Service 与 Activity的交互方式

    当Activity有持续性、可能耗时的操作时,尽管能使用Handler等来新开线程来执行,但如果需要程序异常终止后还能继续重启恢复运行,或者需要对其他进程提供服务时,Service就能发挥它的作用了。下面列出Activity与Service的几种交互方式。

    一、通过startService() 方法来启动服务

    由于此种方法启动的Service无法跟Activity进一步交互,不进一步复述。

    二、通过IBinder为应用程序提供服务

    此功能主要涉及这几个类:

    1、ServiceConnection

    2、继承Service的自定义Service

    3、继承Binder的自定义Binder

    ServiceConnection的onServiceConnected()方法在程序连接Service时返回个IBinder引用,我们通过这个引用可得到自定义Service的引用,从而达到跟Service交互的目的。

    下面是自定义一个服务,其关键点是通过内部类里面的方法来向外暴露自定义Service代码如下:

    /**
     * 自定义本地服务
     *
     */
    public class CustomizeService extends Service{
    	private IBinder binder = new CustomizeBinder();
    	
    	// 自定义Binder
    	public class CustomizeBinder extends Binder{
    		public CustomizeService getService(){
    			return CustomizeService.this;
    		}
    	}
    	
    	@Override
    	public IBinder onBind(Intent intent) {
    		return binder;
    	}
    
    	// 向外提供的方法
    	public int serviceMethod(){
    		return 2 << 3;
    	}
    }
    

    下面是测试界面Activity,通过实现一个ServiceConnection接口来获得Service的引用:

    /**
     * Activity与Service通过IBinder通信
     *
     */
    public class LocalServiceCallActivity extends Activity {
    	private Button start, stop,invoke;
    	private ServiceConnection connection;
    	private CustomizeService service;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		this.setContentView(R.layout.my_customize_service);
    
    		connection = new ServiceConnection() {
    
    			@Override
    			public void onServiceDisconnected(ComponentName name) {
    				service = null;
    			}
    
    			@Override
    			public void onServiceConnected(ComponentName name, IBinder service) {
    				LocalServiceCallActivity.this.service = ((CustomizeService.CustomizeBinder)service).getService();
    			}
    		};
    
    		start = (Button)this.findViewById(R.id.start);
    		start.setOnClickListener(new OnClickListener() {
    
    			@Override
    			public void onClick(View v) {
    				bindService(new Intent(LocalServiceCallActivity.this, CustomizeService.class), connection,
    						LocalServiceCallActivity.BIND_AUTO_CREATE);
    				Toast.makeText(LocalServiceCallActivity.this, "服务已绑定!", Toast.LENGTH_SHORT).show();
    			}
    		});
    		
    		invoke = (Button)this.findViewById(R.id.invoke);
    		invoke.setOnClickListener(new OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				int result = service.serviceMethod();
    				Toast.makeText(LocalServiceCallActivity.this, "调用服务的方法,获得返回值:"+result, Toast.LENGTH_SHORT).show();
    			}
    		});
    		
    		
    
    		stop = (Button)this.findViewById(R.id.stop);
    		stop.setOnClickListener(new OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				unbindService(connection);
    				service = null;
    				Toast.makeText(LocalServiceCallActivity.this, "服务已解除绑定!", Toast.LENGTH_SHORT).show();
    			}
    		});
    		
    	}
    }
    

     my_customize_service.xml文件如下:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     android:orientation="vertical" >
     6 
     7     <Button
     8         android:id="@+id/start"
     9         android:layout_width="wrap_content"
    10         android:layout_height="wrap_content"
    11         android:text="绑定服务" />
    12 
    13     <Button
    14         android:id="@+id/invoke"
    15         android:layout_width="wrap_content"
    16         android:layout_height="wrap_content"
    17         android:text="调用服务" />
    18 
    19     <Button
    20         android:id="@+id/stop"
    21         android:layout_width="wrap_content"
    22         android:layout_height="wrap_content"
    23         android:text="解绑服务" />
    24 
    25 </LinearLayout>

    AndroidManifest.xml部分配置如下:

    <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name" >
            <activity android:name="com.chaseechou.broadcastdemo.localService.LocalServiceCallActivity" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <service android:name="com.chaseechou.broadcastdemo.service.CustomizeService"/>
    ... ...

    自此我们的应用程序通过绑定Service后,便可以自由使用Service的方法了。

    三、通过AIDL来为其他应用进程提供服务(传递基本数据)

    Android官网对于AIDL的部分解释如下:

    AIDL (Android Interface Definition Language) is similar to other IDLs you might have worked with. It allows you to define the programming interface that both the client and service agree upon in order to communicate with each other using interprocess communication (IPC). On Android, one process cannot normally access the memory of another process. So to talk, they need to decompose their objects into primitives that the operating system can understand, and marshall the objects across that boundary for you. The code to do that marshalling is tedious to write, so Android handles it for you with AIDL

    AIDL 是 Android Interface Definition Language,它可以通过自定义的接口来在服务端和客户端之间进行通信(IPC)。我们可以将前面的CustomizeService类的serviceMethod()方法抽离出来,单独放到一个接口中,不过这个接口不是以java为文件后缀名,接口不能带public等修饰符,而且客户端和服务端接口所在包名必须一样。

    在Android官网对于传递的数据类型有如下描述:

    By default, AIDL supports the following data types:

    • All primitive types in the Java programming language (such as int, long, char, boolean, and so on)
    • String
    • CharSequence
    • List

      All elements in the List must be one of the supported data types in this list or one of the other AIDL-generated interfaces or parcelables you've declared. A List may optionally be used as a "generic" class (for example, List<String>). The actual concrete class that the other side receives is always an ArrayList, although the method is generated to use the List interface.

    • Map

      All elements in the Map must be one of the supported data types in this list or one of the other AIDL-generated interfaces or parcelables you've declared. Generic maps, (such as those of the form Map<String,Integer> are not supported. The actual concrete class that the other side receives is always a HashMap, although the method is generated to use the Map interface.

    You must include an import statement for each additional type not listed above, even if they are defined in the same package as your interface.

    从上面这段话可知,AIDL支持如下数据类型:

    1、Java原生数据类型,如int,long,char,boolean。

    2、String

    3、CharSequence

    4、List

    List中包含的数据类型必须为上面描述的,或者是通过AIDL生成的接口类型(如下面说到的RemoteServiceInterface)。如果想支持其他类型的,则必须是实现Parcelable接口的类型。

    5、Map

    Map情况跟List差不多,只是不支持Map<String,Integer>这样的泛型结构。

    对于其他未在上述列表中的类型,必须在.aidl文件中引入自定义数据类所在包,即便是向外提供服务的接口跟自定义数据类在一个包里面。

    下面描述传递基本数据类型的情况,我在服务端定义了一个接口名RemoteServiceInterface.aidl,这个文件定义好后,Eclipse会自动生成同名类文件RemoteServiceInterface.java。代码如下:

    package com.chaseechou.remoteservice;
    
    interface RemoteServiceInterface {
    	int serviceMethod();
    }
    

    服务端的自定义Service跟提供本地服务的自定义有所不同,服务端的自定义Binder是继承自RemoteServiceInterface.Stub类,Stub子类继承了Binder,服务端自定义Service代码如下:

    public class RemoteService extends Service {
    
    	private MyBinder serviceBinder = new MyBinder();
    
    	public class MyBinder extends RemoteServiceInterface.Stub {
    
    		@Override
    		public RemoteBean serviceMethod() throws RemoteException {
    			RemoteBean bean = new RemoteBean();
    			bean.count = 2 << 3;
    			return bean;
    		}
    	}
    	
    	@Override
    	public IBinder onBind(Intent paramIntent) {
    		return serviceBinder;
    	}
    }
    

     为了方便启动Service,我定义一个界面Activity,代码如下:

    public class RemoteCallActivity extends Activity {
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		this.setContentView(R.layout.remote_call_activity);
    	}
    }
    

     remote_call_activity.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
        
        <Button android:id="@+id/callRemote"
            android:text="服务已启动"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
    </LinearLayout>

    AndroidManifest.xml文件在配置Service时需特别注意intent-filter的配置,com.chaseechou.remote.aidl用来唯一标识此Service,客户端的Service在配置此服务时必须跟这个保持一致:

     1 ...
     2 <application
     3         android:allowBackup="true"
     4         android:icon="@drawable/ic_launcher"
     5         android:label="@string/app_name"
     6         android:theme="@style/AppTheme" >
     7         <activity android:name="com.chaseechou.remoteservice.RemoteCallActivity" >
     8             <intent-filter>
     9                 <action android:name="android.intent.action.MAIN" />
    10 
    11                 <category android:name="android.intent.category.LAUNCHER" />
    12             </intent-filter>
    13         </activity>
    14 
    15         <service android:name="com.chaseechou.remoteservice.RemoteService" >
    16             <intent-filter>
    17                 <action android:name="com.chaseechou.remote.aidl" />
    18             </intent-filter>
    19         </service>
    20     </application>
    21 ...

    自此就配置好了服务端,下面讲讲客户端的配置。

    客户端配置步骤如下:

    1、拷贝服务端的RemoteServiceInterface.aidl文件,且此文件所在包需跟服务端的保持一致。

    2、定义一个界面Activity以用来方便调用远程Service,代码还是跟前面本地服务的Activity差不多。

    3、实现ServiceConnection接口时,onServiceConnected()方法有微小变动,onServiceConnected()方法及在调用远程服务的地方均需要捕获RemoteException异常,以防远程服务无法使用的情况。

    4、使用远程服务前先调用bindService()方法来先绑定Service。

    Activity代码如下:

    public class RemoteCallActivity extends Activity {
    	private Button start, stop, invoke;
    	private ServiceConnection connection;
    	private RemoteServiceInterface remote;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		this.setContentView(R.layout.my_customize_service);
    
    		connection = new ServiceConnection() {
    
    			@Override
    			public void onServiceDisconnected(ComponentName name) {
    				remote = null;
    			}
    
    			@Override
    			public void onServiceConnected(ComponentName name, IBinder service) {
    				try {
    					// 调用生成的Stub.asInterface()来生成代理服务
    					remote = RemoteServiceInterface.Stub.asInterface(service);
    				} catch (Exception e) {
    					Log.e(RemoteCallActivity.this.getClass().getName(), "无法获取远程服务连接");
    				}
    			}
    		};
    
    		start = (Button) this.findViewById(R.id.start);
    		start.setOnClickListener(new OnClickListener() {
    
    			@Override
    			public void onClick(View v) {
    				bindService(new Intent("com.chaseechou.remote.aidl"), connection,
    						RemoteCallActivity.BIND_AUTO_CREATE);
    				Toast.makeText(RemoteCallActivity.this, "服务已绑定!", Toast.LENGTH_SHORT).show();
    			}
    		});
    
    		invoke = (Button) this.findViewById(R.id.invoke);
    		invoke.setOnClickListener(new OnClickListener() {
    
    			@Override
    			public void onClick(View v) {
    				int result;
    				try {
    					result = remote.serviceMethod();
    					Toast.makeText(RemoteCallActivity.this, "调用服务的方法,获得返回值:" + result, Toast.LENGTH_SHORT).show();
    				} catch (RemoteException e) {
    					Toast.makeText(RemoteCallActivity.this, "远程方法调用出错!", Toast.LENGTH_SHORT).show();
    					Log.e(RemoteCallActivity.this.getClass().getName(), "无法获取远程服务连接");
    				}
    			}
    		});
    		
    		stop = (Button)this.findViewById(R.id.stop);
    		stop.setOnClickListener(new OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				unbindService(connection);
    				remote = null;
    				Toast.makeText(RemoteCallActivity.this, "服务已解除绑定!", Toast.LENGTH_SHORT).show();
    			}
    		});
    	}
    }
    

     下面是AndroidManifest.xml文件的部分配置:

     1 <application
     2         android:allowBackup="true"
     3         android:icon="@drawable/ic_launcher"
     4         android:label="@string/app_name" >
     5 
     6         <activity android:name="com.chaseechou.broadcastdemo.remoteService.RemoteCallActivity" >
     7             <intent-filter>
     8                 <action android:name="android.intent.action.MAIN" />
     9 
    10                 <category android:name="android.intent.category.LAUNCHER" />
    11             </intent-filter>
    12         </activity>
    13         <!-- 需注意的地方 -->
    14         <service android:name="com.chaseechou.broadcastdemo.remoteService.RemoteServiceInterface" >
    15             <intent-filter>
    16                 <action android:name="com.chaseechou.remote.aidl" />
    17             </intent-filter>
    18         </service>
    19     </application>

    自此我们已经配置好客户端,现在启动RemoteCallActivity,绑定服务后便可使用远程服务啦。

    四、通过AIDL来为其他应用进程提供服务(传递复杂数据)

    上节有说到如果想支持复杂类型的,则必须实现Parcelable接口,具体步骤如下:

    1、实现writeToParcel()方法,这个方法用于将你定义的对象数据写到Parcel对象中。

    2、定义一个Parcelable.Creator的静态变量,并实例化一份实现了Parcelable.Creator接口的类对象给该静态变量。

    3、在.aidl文件中声明你定义的数据类。

    下面是自定义数据类RemoteBean,代码如下:

    public class RemoteBean implements Parcelable {
    	public int count;
    	public static final Parcelable.Creator<RemoteBean> CREATOR = new Creator<RemoteBean>() {
    		
    		@Override
    		public RemoteBean[] newArray(int size) {
    			return new RemoteBean[size];
    		}
    		
    		@Override
    		public RemoteBean createFromParcel(Parcel source) {
    			RemoteBean bean = new RemoteBean();
    			bean.count = source.readInt();
    			return bean;
    		}
    	};
    	
    
    	@Override
    	public int describeContents() {
    		return 0;
    	}
    
    	@Override
    	public void writeToParcel(Parcel paramParcel, int paramInt) {
    		paramParcel.writeInt(count);
    	}
    }
    

     aidl文件:

    package com.chaseechou.remoteservice;
    
    parcelable RemoteBean;
    

    自此,我们已经在服务端定义好一个自定义数据类型,在使用时将这两个文件拷贝到客户端即可,前面讲到的服务端与客户端交互的接口文件RemoteServiceInterface.aidl可修改如下:

    package com.chaseechou.remoteservice;
    
    import com.chaseechou.remoteservice.RemoteBean;
    
    interface RemoteServiceInterface {
    	RemoteBean serviceMethod();
    }
    

     

  • 相关阅读:
    部分页面开启宽屏模式
    门户diy实现翻页功能的方法
    git命令详解,从入门到装逼
    array方法常用记载
    vue 生命周期的理解(created && mouted的区别)
    微信小程序传值的几种方式
    data-*
    本地存储和会话存储以及cookie的处理
    vue的安装和项目构建
    进击的UI----------动画
  • 原文地址:https://www.cnblogs.com/forchase/p/4079445.html
Copyright © 2011-2022 走看看