Android:Service简介

来源:互联网 发布:中航控制软件下载 编辑:程序博客网 时间:2024/06/01 18:24

Android:Service简介

Service是Android系统提供的四种组件之一,是运行在后台的一种服务程序,没有可视化界面。Android 支持两种服务类型的服务:本地服务和远程服务。

•本地服务 LocalService 用于应用程序内部。

  它可以启动并运行,直至有人停止了它或它自己停止。在这种方式下,它以调用Context.startService()启动,而以调用Context.stopService()结束。它可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。不论调用了多少次startService()方法,你只需要调用一次stopService()来停止服务。

  用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好。

•远程服务 RemoteService 用于android系统内部的应用程序之间。

  它可以通过自己定义并暴露出来的接口进行程序操作。客户端建立一个到服务对象的连接,并通过那个连接来调用服务。连接以调用Context.bindService()方法建立,以调用 Context.unbindService()关闭。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。

生命周期:

service进程的优先级

   Android系统会尽量保持拥有service的进程运行,只要在该service已经被启动(start)或者客户端连接(bindService)到它。当内存不足时,会保持拥有service的进程具有较高的优先级。

1.如果service正在调用onCreate,onStartCommand或者onDestory方法,那么用于当前service的进程则变为前台进程以避免被killed。

 

2.如果当前service已经被启动(start),拥有它的进程则比那些用户可见的进程优先级低一些,但是比那些不可见的进程更重要,这就意味着service一般不会被killed.

 

3.如果客户端已经连接到service (bindService),那么拥有Service的进程则拥有最高的优先级,可以认为service是可见的。

 

4.如果service可以使用startForeground(int,Notification)方法来将service设置为前台状态,那么系统就认为是对用户可见的,并不会在内存不足时killed。

 

Android:本地服务

在Activity中,使用context.startService() 来启动本地服务,调用context.stopService()来停止服务。本地服务的生命周期为:onCreate -->onStart(可多次调用) -->onDestroy

Service代码:

public class LocalService extends Service {

 

       private static final String TAG = "LocalService";

 

       @Override

       public IBinder onBind(Intent intent) {

                Log.i(TAG, "onBind");

                return null;

       }

 

       @Override

       public void onCreate() {

                Log.i(TAG,"onCreate");

                super.onCreate();

       }

 

       @Override

       public void onDestroy() {

                Log.i(TAG,"onDestroy");

                super.onDestroy();

       }

 

        @Override

       public void onStart(Intent intent, int startId) {

                Log.i(TAG,"onStart");

                super.onStart(intent, startId);

       }

}

Activity代码:

public class ServiceActivity extendsActivity {

 

       @Override

       protected void onCreate(Bundle savedInstanceState) {

               super.onCreate(savedInstanceState);

               setContentView(R.layout.servicedemo);

 

                ((Button)findViewById(R.id.startLocalService)).setOnClickListener(

                                newView.OnClickListener(){

 

                                       @Override

                                        publicvoid onClick(View view) {

                                               // TODO Auto-generated method stub

                                              startService(new Intent("com.demo.SERVICE_DEMO"));

                                        } 

                                });

 

                ((Button)findViewById(R.id.stopLocalService)).setOnClickListener(

                                newView.OnClickListener(){

 

                                       @Override

                                        publicvoid onClick(View view) {

                                                // TODO Auto-generated method stub

                                               stopService(new Intent("com.demo.SERVICE_DEMO"));

                                        }

                                });

       }

 

}

AndroidManifest.xml:

<serviceandroid:name=".LocalService">

       <intent-filter>

                <actionandroid:name="com.demo.SERVICE_DEMO" />

                <categoryandroid:name="android.intent.category.default" />

       </intent-filter>

</service>

 

在第一次运行startService时,会调用onCreate和onStart方法,在没有调用stopService前,再次运行startService,都只会调用onStart。而stopService时调用onDestroy。

 

 

Android:远程服务

在Android中, 每个应用程序都有自己的进程,当需要在不同的进程之间传递对象时,该如何实现呢? 显然, Java中是不支持跨进程内存共享的。因此要传递对象, 需要把对象解析成操作系统能够理解的数据格式,以达到跨界对象访问的目的。在JavaEE中,采用RMI通过序列化传递对象。在Android中, 则采用AIDL(Android Interface Definition Language:接口描述语言)方式实现。

   AIDL是一种接口定义语言,用于约束两个进程间的通讯规则,供编译器生成代码,

实现Android设备上的两个进程间通信(IPC)。AIDL的IPC机制和EJB所采用的CORBA很类似,进程之间的通信信息,首先会被转换成AIDL协议消息,然后发送给对方,对方收到AIDL协议消息后再转换成相应的对象。由于进程之间的通信信息需要双向转换,所以android采用代理类在背后实现了信息的双向转换,代理类由android编译器生成,对开发人员来说是透明的。

假设A应用需要与B应用进行通信,调用B应用中的download(Stringpath)方法,

B应用以Service方式向A应用提供服务。需要下面四个步骤:

 

1> 在B应用中创建*.aidl文件,aidl文件的定义和接口的定义很相类,

如:在cn.flyfot.aidl包下创建IDownloadService.aidl文件,内容如下:

package cn.flyfot.aidl;

interface IDownloadService

{

   void download(String path);

}

  

 当完成aidl文件创建后,eclipse会自动在项目的gen目录中同步生成IDownloadService.java接口文件。接口文件中生成一个Stub的抽象类,里面包括aidl定义的方法,还包括一些其它辅助方法。值得关注的是asInterface(IBinderiBinder),它返回接口类型的实例,对于远程服务调用,远程服务返回给客户端的对象为代理对象,客户端在onServiceConnected(ComponentName name, IBinder service)方法引用该对象时不能直接强转成接口类型的实例,而应该使用asInterface(IBinderiBinder)进行类型转换。

编写Aidl文件时,需要注意下面几点:

  1.接口名和aidl文件名相同。

  2.接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。

  3.Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、CharSequence),使用这些类型时不需要import声明。对于List和Map中的元素类型必须是Aidl支持的类型。如果使用自定义类型作为参数或返回值,自定义类型必须实现Parcelable接口。

  4.自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。

  5.在aidl文件中所有非Java基本类型参数必须加上in、out、inout标记,以指明参数是输入参数、输出参数还是输入输出参数。

 6.Java原始类型默认的标记为in,不能为其它标记。

 

2> 在B应用中实现aidl文件生成的接口(本例是IDownloadService),但并非直接实现接口,而是通过继承接口的Stub来实现(Stub抽象类内部实现了aidl接口),并且实现接口方法的代码。内容如下:

public class ServiceBinder extends IDownloadService.Stub

{

   @Override

   public void download(String path) throws RemoteException

{

       Log.i("DownloadService", path);

   }       

}

 

3> 在B应用中创建一个Service(服务),在服务的onBind(Intent intent)方法中返回实现了aidl接口的对象(本例是ServiceBinder)。内容如下:

public class DownloadService extends Service

{

   

private ServiceBinder serviceBinder =new ServiceBinder();

    @Override

public IBinderonBind(Intent intent)

{

       return serviceBinder;

    }

   

}

配置AndroidManifest.xml:

<serviceandroid:name=".DownloadService">

<intent-filter>

<actionandroid:name="cn.flyfot.process.aidl.DownloadService"/>

</intent-filter>

</service>

 

4> 把B应用中aidl文件所在package连同aidl文件一起拷贝到客户端A应用,eclipse会自动在A应用的gen目录中为aidl文件同步生成IDownloadService.java接口文件,接下来就可以在A应用中实现与B应用通信,代码如下:

public class ClientActivity extendsActivity

{

   private IDownloadService downloadService;

    @Override

public voidonCreate(Bundle savedInstanceState)

{

       super.onCreate(savedInstanceState);

       setContentView(R.layout.main);

       this.bindService(newIntent("cn.flyfot.process.aidl.DownloadService"),

this.serviceConnection, BIND_AUTO_CREATE);//绑定到服务

    }

   @Override

protected voidonDestroy()

{

       super.onDestroy();

       this.unbindService(serviceConnection);//解除服务

   }   

   

   

privateServiceConnection serviceConnection = new ServiceConnection()

{

           @Override

           public voidonServiceConnected(ComponentName name, IBinder service)

{

           downloadService =IDownloadService.Stub.asInterface(service);

           try

                    {

               downloadService.download("http://www.flyfot.cn");

           }

catch(RemoteException e)

{

                Log.e("ClientActivity",e.toString());

           }

       }

       @Override

       public void onServiceDisconnected(ComponentName name)

{

           downloadService =null;

       }

   };

}

 

 

补充案例:电话窃听和拦截应用

 

基于广播机制,做一个简单的电话窃听和录音应用。

业务需求分析:

  1.当手机处于开机状态,监听服务就要启动,对来电进行监听录音。

  2.设置电话黑名单,当来电是黑名单电话,则直接挂断。

实现步骤:

 

android的新版本已经把Phone类给隐藏起来了,想要用代码实现挂断电话,就必须通过AIDL才行,

第一步:在程序中新建一个包,包名必须为:com.android.internal.telephony,因为要使用aidl,

第二步:在这个包里面新建一个名为ITelephony.aidl的文件,然后在文件里面写入代码:

package com.android.internal.telephony;

interface ITelephony{

boolean endCall();

void answerRingingCall();

}

然后保存,eclipse会自动在gen文件夹下生成一个ITelephony.java的类。

第三步:在程序中新建一个包,包名为:android.telephony;

第四步:在此包中创建NeighboringCellInfo.aidl文件:

package android.telephony; 

parcelable NeighboringCellInfo; 

 

 

定义一个电话监听的服务,对来电进行监听录音和拦截。具体代码如下:

PhoneListenerService:
 
 importjava.lang.reflect.Method;
 importcom.android.internal.telephony.ITelephony;
 importandroid.app.Service;
 importandroid.content.Context;
 importandroid.content.Intent;
 importandroid.media.MediaRecorder;
 importandroid.os.Environment;
 importandroid.os.IBinder;
 importandroid.telephony.PhoneStateListener;
 importandroid.telephony.TelephonyManager;
 importandroid.util.Log;
 importandroid.view.LayoutInflater;
 importandroid.view.View;
 importandroid.widget.Toast;
 
 public class PhoneListenerServiceextendsService {
 
     privateMediaRecorder recorder;
     privateboolean recording =false;
     @Override
     publicIBinder onBind(Intent intent) {
         returnnull;
     }
     @Override
     publicvoid onCreate() {
         Log.v("TAG","service onCreate()");
         super.onCreate();
         //电话服务管理
        TelephonyManager manager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
         //监听电话状态
        manager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
     }
 
     privatePhoneStateListener listener = newPhoneStateListener() {
         /*
          * @seeTelephonyManager#CALL_STATE_IDLE值为0
          *
          * @seeTelephonyManager#CALL_STATE_RINGING值为1
          *
          * @see TelephonyManager#CALL_STATE_OFFHOOK值为2
          */
         @Override
         publicvoid onCallStateChanged(int state, String incomingNumber) {
             super.onCallStateChanged(state,incomingNumber);
             //打印电话状态改变信息
            Log.v("TAG", "onCallStateChanged state=" + state);
             switch(state) {
             caseTelephonyManager.CALL_STATE_IDLE:// 没有来电或者挂断
                 stopRecord();
                 break;
             caseTelephonyManager.CALL_STATE_RINGING:// 响铃时
                 stop(incomingNumber);
                 break;
             caseTelephonyManager.CALL_STATE_OFFHOOK:// 接起电话
                 recordCalling();
                 break;
             default:
                 break;
             }
         }
 
     };
     //停止录音
     private voidstopRecord() {
         Log.v("TAG","stopRecord");
         if(recording) {
             recorder.stop();
             recorder.release();
             recording=false;
         }
     }
     //电话拦截
     public voidstop(String s) {
          try {
              if(s.equals("110")) {
                  Toast.makeText(this, "拦截成功", 0).show();
                  Log.e("TAG", "此来电为黑名单号码,已被拦截!");
                  //调用ITelephony.endCall()结束通话
                  Method method = Class.forName("android.os.ServiceManager")
                         .getMethod("getService", String.class);
                  IBinder binder = (IBinder)method.invoke(null,
                          new Object[] { TELEPHONY_SERVICE });
                  ITelephony telephony =ITelephony.Stub.asInterface(binder);
                  telephony.endCall();
              } else
                  Toast.makeText(this, "不需拦截", 0).show();
              recording=false;
          } catch(Exception e) {
              e.printStackTrace();
          }
      }
      //进行录音
      privatevoid recordCalling() {
          try {
              Log.v("TAG","recordCalling");
             recorder = new MediaRecorder();
            recorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 读麦克风的声音
            recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);//输出格式.3gp
            recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);//编码方式
            recorder.setOutputFile(Environment.getExternalStorageDirectory()
                     .getAbsolutePath()
                     + "/"
                     +System.currentTimeMillis()
                     + ".3gp");// 存放的位置是放在sdcard目录下
            recorder.prepare();
             recorder.start();
             recording = true;
         } catch(Exception e) {
             e.printStackTrace();
         }
     }
 }

 

我们知道服务不能自己启动,需要手动启动,所以我们需要一个广播,当手机刚开机,我们就发送广播,启动监听电话的服务。下面是一个发送广播的Receiver

BootCompleteReceiver:
 
 importandroid.content.BroadcastReceiver;
 importandroid.content.Context;
 importandroid.content.Intent;
 importandroid.util.Log;
 
 public class BootCompleteReceiverextendsBroadcastReceiver {
 
     @Override
     publicvoid onReceive(Context context, Intent intent){
         //运用广播开启监听这个服务
        Log.v("TAG", "开机了!");
             Intent i = new Intent(context, PhoneListenerService.class);
           i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
           context.startService(i);
    }
}

下面就是要在AndroidManifest.xml中配置相关的权限:具体代码如下:

AndroidManifest.xml

 <?xml version="1.0"encoding="utf-8"?>
 <manifestxmlns:android="http://schemas.android.com/apk/res/android"
       package="cn.yj3g.L21_PhoneListener"
       android:versionCode="1"
      android:versionName="1.0">
     <uses-sdkandroid:minSdkVersion="8" />
     <applicationandroid:icon="@drawable/icon"android:label="@string/app_name">  
         <serviceandroid:name=".PhoneListenerService">
             <intent-filter>
               <actionandroid:name="cn.yj3g.L21_PhoneListener.PhoneListenerService"></action>
             </intent-filter>
         </service>
         <receiverandroid:name=".BootCompleteReceiver">
             <intent-filter>
              <actionandroid:name="android.intent.action.BOOT_COMPLETED" />
             </intent-filter>
         </receiver>
     </application>
        <!-- 读取电话状态权限-->
     <uses-permissionandroid:name="android.permission.READ_PHONE_STATE"/>
        <!-- 录音权限 -->
     <uses-permissionandroid:name="android.permission.RECORD_AUDIO"/>
       <!-- 向sdcard中写数据的权限 -->
     <uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE" />
       <!-- 开机启动广播的权限 -->
     <uses-permissionandroid:name="android.permission.RECEIVE_BOOT_COMPLETED" />
       <!-- 挂断电话时需要的权限 -->
      <uses-permission android:name="android.permission.CALL_PHONE"/>
 </manifest>

这样一个监听电话的应用就做好了。当安装此应用的手机开机时,来电就处于监听状态,这样就可以不动声色的对该手机的来电进行监听录音或者拦截。

本文来自上海Android培训官方课件,感谢上海it培训机构提供。

0 0