Android面试Android成长笔记。android基础巩固和提升

Android实习生 —— 四大组件之Service

2017-03-28  本文已影响597人  博儿丶

目录

一、基础回顾
   - 定义特点
   - 注意事项
   - 应用场景举例
   - 类型
二、生命周期
    startService
    bindService
三、使用方法
   1、通过startService方式定义一个Service(继承Service类)
   2、通过startService方式定义一个Service(继承IntentService类)
   3、通过bindService方式定义一个Service:(使用Bind Service完成Service和Activity之间的通信)
   4、bindService和startService混合使用
四、startService、bindService区别大总结
五、在AndroidManifest.xml里Service元素常见选项
六、扩展:进程间通信及小例子

一、基础回顾




二、生命周期

从Service的启动到销毁,有两种路径(两种生命周期):
startService、bindService

Service两种生命周期

三、使用方法

1、通过startService方式定义一个Service(继承Service类):

public class MyService extends Service {  
        
       public static final String TAG = "MyService";  

       //创建服务时调用
       @Override  
       public void onCreate() {  
           super.onCreate();  
           Log.d(TAG, "onCreate");  
       }  

       //服务执行的操作
       @Override  
       public int onStartCommand(Intent intent, int flags, int startId) {  
           Log.d(TAG, "onStartCommand");  
           return super.onStartCommand(intent, flags, startId);  
       }  

       //销毁服务时调用
       @Override  
       public void onDestroy() {  
           super.onDestroy();  
           Log.d(TAG, "onDestroy");  
       }  

       //onBind()方法是Service中唯一的一个抽象方法,所以必须要在子类里实现。
       //Service有两种启动方式:一种是startService(),另一种是bindService()。第二种启动方式才会用到onBind()方法。
       //我们这先用第一种方式定义Service,所以暂时忽略onBind()方法。
       @Override  
       public IBinder onBind(Intent intent) {  
           return null;  
       }  
<service android:name=".MyService"></service>

请注意:为了保证应用的安全,请使用显式Intent启动或绑定一个Service,请不要在<service>标签中配置intent-filter。

public class MainActivity extends Activity implements OnClickListener {  
       private Button button1_start_service;  
       private Button button2_stop_service; 
       @Override  
       protected void onCreate(Bundle savedInstanceState) {  
           super.onCreate(savedInstanceState);  
           setContentView(R.layout.activity_main);  
           button1_start_service = (Button)findViewById(R.id.button1_start_service);  
           button2_stop_service = (Button)findViewById(R.id.button2_stop_service);  
           button1_start_service.setOnClickListener(this);  
           button2_stop_service.setOnClickListener(this);  
       }  
   
       @Override  
       public void onClick(View v) {  
           switch (v.getId()) {  
           case R.id.button1_start_service:  
               Intent startIntent = new Intent(this, MyService.class);  
               startService(startIntent);  
               break;  
           case R.id.button2_stop_service:  
               Intent stopIntent = new Intent(this, MyService.class);  
               stopService(stopIntent);  
               break;  
           default:  
               break;  
           }  
       }  
   }

4、onStartCommand方法的返回值:
onStartCommand方法执行时,返回的是一个int型。这个整型可以有三个返回值:START_NOT_STICKYSTART_STICKYSTART_REDELIVER_INTENT
START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand方法后,服务被异常kill掉,系统不会自动重启该服务。
START_STICKY:如果Service进程被kill掉,保留Service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建Service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到Service,那么参数Intent将为null。
START_REDELIVER_INTENT:重传Intent。使用这个返回值时,系统会自动重启该服务,并将Intent的值传入。

 ** 5、默认情况下,一个started的Service与启动他的组件在同一个线程中。上面的实例中,服务就是在主线程中运行的,如果是在服务中完成耗时操作的话,容易造成主线程阻塞。所以我们可以在服务中开启一个子线程来完成耗时操作。**

2、通过startService方式定义一个Service(继承IntentService类):

public class MyService extends Service {  
      public static final String TAG = "MyService";   
     //服务执行的操作
     @Override  
     public int onStartCommand(Intent intent, int flags, int startId) {  
         new Thread(new Runnable() {
             public void run() {
                  //在子线程中处理具体的逻辑
                  //在这里我们只做打印子线程id的操作
                 Log.i("MyService",Thread.currentThread().getId()+"");
                 stopSelf();  //服务执行完毕后自动停止
             }
         }).start();        
         return super.onStartCommand(intent, flags, startId);  
     }
 
     @Override
     public IBinder onBind(Intent intent) {
         // TODO Auto-generated method stub
         return null;
     }      
}

在MainActivity中启动Service代码

public class MainActivity extends Activity implements OnClickListener {  
   private Button button1_start_service;  
   private Button button2_stop_service; 
   @Override  
   protected void onCreate(Bundle savedInstanceState) {  
       super.onCreate(savedInstanceState);  
       setContentView(R.layout.activity_main);  
       button1_start_service = (Button)findViewById(R.id.button1_start_service);  
       button2_stop_service = (Button)findViewById(R.id.button2_stop_service);  
       button1_start_service.setOnClickListener(this);  
       button2_stop_service.setOnClickListener(this);  
   }  

   @Override  
   public void onClick(View v) {  
       switch (v.getId()) {  
       case R.id.button1_start_service:  
           Log.i("Main",Thread.currentThread().getId()+"");
           Intent startIntent = new Intent(this, MyService.class);  
           startService(startIntent);  
           break;  
       case R.id.button2_stop_service:  
           Intent stopIntent = new Intent(this, MyService.class);  
           stopService(stopIntent);  
           break;  
       default:  
           break;  
       }  
   }  
}

注册Service步骤不再赘述,当开启Service后打印日志如下:

Service手动开启线程177
如果我们不手动开启线程,I/MyService: 177将会变成它依赖的主线程1,这就不能做耗时操作了。虽说上面的这种写法并不复杂,但总会有一些程序猿忘记开启线程,或者忘记调用stopSelf()方法。
为了可以简单地创建一个可开启单独线程、会自动停止的服务,Android专门提供了一个IntentService类,这个类就很好的解决了上面所提到的两种尴尬。
public class MyIntentService extends IntentService{
      public MyIntentService() {
         //第一步:重写父类的onHandleIntent()方法,这里首先要提供一个无参的构造方法,
         //并且必须在其内部调用父类的有参构造方法,这里我们手动给服务起个名字为:MyIntentService
         super("MyIntentService");
     }
 
       //第二步:重写父类的onHandleIntent()方法,该方法在会在一个单独的线程中执行,
       //来完成工作任务。任务结束后,该Service自动停止
     @Override
     protected void onHandleIntent(Intent intent) {
         for(int i = 0;i<3;i++) {
             //Service要执行的逻辑
             //这里我们只打印当前线程的id
             Log.d("MyIntentService","IntentService线程的id是:"+Thread.currentThread().getId());
             try {
                 //线程睡眠一秒钟
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }        
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
         Log.d("MyIntentService","onDestroy");
     }
 }

2)在清单文件中对服务进行注册服务:
<service android:name=".MyIntentService"></service>
3)在MainActivity里面加入启动IntentService的逻辑,核心代码如下:

       case R.id.button3_stop_intentservice:
             Log.d("MainActivity","主线程的id是:"+Thread.currentThread().getId());
             Intent intentService = new Intent(this,MyIntentService.class);
             startService(intentService);

运行程序,日志显示如下:


通过继承IntentService运行结果

3、通过bindService方式定义一个Service:(使用Bind Service完成Service和Activity之间的通信):

bindService()方法立即返回,并且不给客户端返回IBinder对象。要接收IBinder对象,客户端必须创建一个ServiceConnection类的实例,并且把这个实例传递给bindService()方法。ServiceConnection对象包含了一个系统调用的传递IBinder对象的回调方法。

public class MyBindService01 extends Service {
    public static final String TAG = "MyBindService01";
    private MyBinder mBinder = new MyBinder();
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;  //在这里返回新建的MyBinder类
    }
    
    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }
    
    //MyBinder类,继承Binder:让里面的方法执行下载任务,并获取下载进度
    class MyBinder extends Binder {
        public void startDownload() {
            Log.d("TAG", "startDownload() executed");
            // 执行具体的下载任务  
        }
        public int getProgress(){
            Log.d("TAG", "getProgress() executed");
            return 0;
        }
    }
}

2)检查清单文件,是否已经对Service进行注册:
<service android:name=".MyBindService01" ></service>
3)让我们修改MainActivity和MyBindService01之间建立关联

public class MainActivity extends Activity implements OnClickListener {
    private Button button1_bind_service;
    private Button button2_unbind_service;
    private MyBindService01.MyBinder myBinder;
    
    boolean mBound = false; //一开始,并没有和Service绑定.这个参数是用来显示绑定状态
    
    //匿名内部类:服务连接对象
    private ServiceConnection connection = new ServiceConnection() {
        
        //当服务异常终止时会调用。注意,解除绑定服务时不会调用
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBound = false; //服务异常终止时,状态为未绑定
            //解决了多次执行unbindService()方法引发的异常问题
        }
        
        //和服务绑定成功后,服务会回调该方法
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBinder = (MyBindService01.MyBinder) service;
            //在Activity中调用Service里面的方法
            myBinder.startDownload();
            myBinder.getProgress();
            mBound = true; //true说明是绑定状态
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button1_bind_service = (Button) findViewById(R.id.button1_bind_service);
        button2_unbind_service = (Button) findViewById(R.id.button2_unbind_service);
        button1_bind_service.setOnClickListener(this);
        button2_unbind_service.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.button1_bind_service:
            Intent bindIntent = new Intent(this, MyService.class);
            bindService(bindIntent, connection, BIND_AUTO_CREATE);
//这里传入BIND_AUTO_CREATE表示在Activity和Service建立关联后会自动创建Service(即使之前没有创建
//Service也没有关系),这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
            break;
        case R.id.button2_unbind_service:
            //如果和Service是绑定的状态,就解除绑定。
            if(mBound){
                unbindService(connection);
                mBound=false;
            }
            break;
        default:
            break;
        }
    }
}

说明:这里我们首先创建了一个ServiceConnection的匿名类,在里面重写了onServiceConnected()方法和onServiceDisconnected()方法,如果当前Activity与服务连接成功后,服务会回调onServiceConnected()方法
在onServiceConnected()方法中,我们又通过向下转型得到了MyBinder的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。现在我们可以在Activity中根据具体的场景来调用MyBinder中的任何public方法,即实现了Activity指挥Service干什么Service就去干什么的功能。
4)执行bindService()方法。



5)解除Activity和Service之间的关联。执行unbindService()。


            case R.id.button4_unbind_service:
            //如果和Service是绑定的状态,就解除绑定。
            if(mBound){
                unbindService(connection);
                mBound=false;
            }
            break;
**4、当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了)。**

 **5、只有Activity、Service、Content Provider能够绑定服务;BroadcastReceiver广播接收器不能绑定服务。**

4、bindService和startService混合使用:


四、startService、bindService区别大总结

1、生命周期不同。(详见二)
2、多次启动,前者会多次执行onStartCommand()方法,后者什么都不执行。多次停止,前者只会执行一次onDestroy()方法,后者报异常信息。
3、当启动Service的组件已被Destroy的时候,前者不停止,后者会停止。
4、前者停止直接执行onDestroy()方法(Service中的),后者则先解除绑onUnbind()定再执行onDestroy()方法(Service中的)。
5、当手机屏幕在“横”“竖”变换时,前者创建的Service不会停止,后者会随着Activity的重建而停止。
6、后者的onBind回调方法将返回给客户端一个IBinder接口实例,IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。而这些操作前者启动的Service是没有的。


五、在AndroidManifest.xml里Service元素常见选项

android:name   -- 服务类名
android:label  --  服务的名字,如果此项不设置,那么默认显示的服务名则为类名
android:icon -- 服务的图标
android:permission -- 申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务
android:process -- 表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字
android:enabled --表示是否能被系统实例化,为true表示可以,为false表示不可以,默认为true
android:exported -- 表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false

六、扩展:进程间通信。

调用者和Service如果不在一个进程内, 就需要使用android中的远程Service调用机制.
android使用AIDL定义进程间的通信接口. AIDL的语法与java接口类似, 需要注意以下几点:

  1. AIDL文件必须以.aidl作为后缀名.
  2. AIDL接口中用到的数据类型, 除了基本类型, String, List, Map, CharSequence之外, 其他类型都需要导包, 即使两种在同一个包内. List和Map中的元素类型必须是AIDL支持的类型.
  3. 接口名需要和文件名相同.
  4. 方法的参数或返回值是自定义类型时, 该自定义的类型必须实现了Parcelable接口.
  5. 所有非java基本类型参数都需要加上in, out, inout标记, 以表明参数是输入参数, 输出参数, 还是输入输出参数.
  6. 接口和方法前不能使用访问修饰符和static, final等修饰.

【远程通信扩展小例子】

 <service android:name=".AIDLService"
          android:process=":remote"></service>
package mangues.com.aidl;
interface IPerson {
    String greet(String someone);
}

3.在aidl_service 中建立AIDLService
这个IPerson.Stub 就是通过IPerson.aidl 自动生成的binder 文件,你实现下,然后在onBind()中 return出去就好了,就和Android Service实现和activity交互一样。
代码:

public class AIDLService extends Service {
    private static final String TAG = "AIDLService";

    IPerson.Stub stub = new IPerson.Stub() {
        @Override
        public String greet(String someone) throws RemoteException {
            Log.i(TAG, "greet() called");
            return "hello, " + someone;
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate() called");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onBind() onStartCommand");
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind() called");
        return stub;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "onUnbind() called");
        return true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy() called");
    }
}

4.aidl_service MainActivity 中启动这个service
简单点就不写关闭什么的了;

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent startIntent = new Intent(this, AIDLService.class);
        startService(startIntent);
    }

在AndroidManifest.xml注册

<service android:name=".AIDLService"
                 android:process=":remote">
            <intent-filter>
                <action android:name="android.intent.action.AIDLService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

作用就是把这个service暴露出去,让别的APP可以利用
android.intent.action.AIDLService 字段隐形绑定这个service,获取数据。

5.aidl_client 中绑定aidl_service service 获取数据
代码:

      public class MainActivity extends AppCompatActivity {
      private IPerson person;
      private ServiceConnection conn = new ServiceConnection() {
         @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i("ServiceConnection", "onServiceConnected() called");
            person = IPerson.Stub.asInterface(service);
            String retVal = null;
            try {
                retVal = person.greet("scott");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Toast.makeText(MainActivity.this, retVal, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //This is called when the connection with the service has been unexpectedly disconnected,
            //that is, its process crashed. Because it is running in our same process, we should never see this happen.
            Log.i("ServiceConnection", "onServiceDisconnected() called");
        }
      };
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent mIntent = new Intent();
        mIntent.setAction("android.intent.action.AIDLService");
        Intent eintent = new Intent(getExplicitIntent(this,mIntent));
        bindService(eintent, conn, Context.BIND_AUTO_CREATE);
    }
    public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
        // Retrieve all services that can match the given intent
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
        // Make sure only one match was found
        if (resolveInfo == null || resolveInfo.size() != 1) {
            return null;
        }
        // Get component info and create ComponentName
        ResolveInfo serviceInfo = resolveInfo.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);
        // Create a new intent. Use the old one for extras and such reuse
        Intent explicitIntent = new Intent(implicitIntent);
        // Set the component to be explicit
        explicitIntent.setComponent(component);
        return explicitIntent;
    }
}

和Android Service 中学习的调用MyBinder获取service中数据一样,这边只是吧MyBinder 改成了aidl定义的接口IPerson 本质上还是一个Binder。
因为android 5.0 不允许隐形启用service 所有用getExplicitIntent转一下


整理作者:汪博
少壮不努力,老大徒悲伤。
本文为Android学习规划打造,如有不好的地方请多多指教。

上一篇 下一篇

猜你喜欢

热点阅读