Android:Service组件及其简单应用

Posted 晚风Sensei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android:Service组件及其简单应用相关的知识,希望对你有一定的参考价值。

Service 组件

  • 可在后台执行长时间运行操作而不提供界面的应用组件
  • 用户切换到其他应用Service也在后台运行
  • 不能主动运行,需要调用方法来运行Service

创建

  1. 创建继承Service类的类
public class MyService1 extends Service 
    private static final String TAG = MyService1.class.getName();
    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return null;
    

    //生命周期相关函数
    @Override
    public void onCreate() 
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    
    @Override
    public void onDestroy() 
        Log.i(TAG, "onDestroy: ");
        super.onDestroy();
    
    @Override
    public boolean onUnbind(Intent intent) 
        Log.i(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    

  1. 在Mainifast中注册
<service android:name=".MyService1"/>

类别

  • Context.startService()

  • Context.bindService()

不同启动方式对应不同生命周期

startService(Intent)

startService启动的服务默认无限期执行(可以通过Context的stopService或Service的stopSelf方法停止运行)

//开启Service
Intent intent = new Intent();
intent.setClass(this, MyService1.class);
startService(intent);

如服务未被创建,会调用onCreate和OnStartCommand两个生命周期函数。若服务已被创建,则只调用onStartCommand

//停止Service
Intent intent = new Intent();
intent.setClass(this, MyService1.class);
stopService(intent);

停止服务时,调用onDestroy生命周期函数,服务被销毁。

bindService(Intent service, ServiceConnection conn, int flag)

bindService启动的服务在调用者和服务之间是典型的client-server的接口,即调用者是客户端,service是服务端,service就一个,但是连接绑定到service上面的客户端client可以是一个或多个

这里特别要说明的是,这里所提到的client指的是组件,比如某个Activity。

当client销毁的时候,client会自动与Service解除绑定,当然client也可以通过明确调用Context的unbindService方法与Service解除绑定。

当没有任何client与Service绑定的时候,Service会自行销毁

若有client与Service绑定,则无法使用stopService销毁

//绑定服务
//若该服务未被创建则创建一个新服务

Intent intent = new Intent();
intent.setClass(this, MyService1.class);
//connection生命为MainActivity的成员变量  
// private ServiceConnection connection = null;
connection = new ServiceConnection() 
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) 
        // when Service connect
    

    @Override
    public void onServiceDisconnected(ComponentName name) 
        //when Service disconnect
    
;

// flag = BIND_AUTO_CREATE  自动创建Service
bindService(intent, connection, BIND_AUTO_CREATE);
//解绑服务
unbindService(connection);

使用Service组件模拟下载app

1.新建项目并创建MyService类继承自Service类

public class MyService extends Service 

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        Log.i(TAG, "onBind: ");
        return null;
    

    @Override
    public void onCreate() 
        Log.i(TAG, "onCreate: ");
        super.onCreate();
    

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

    @Override
    public boolean onUnbind(Intent intent) 
        Log.i(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    

    @Override
    public void onDestroy() 
        Log.i(TAG, "onDestroy: ");
        super.onDestroy();
    

这里一开始最好在每个重要的周期函数中都添加一个Log.i打印语句,测试运行Service服务的各个生命周期是否正常。

2.在MainAcitvity的onCreate中调用bindService绑定服务

//绑定服务
Intent bind_intent = new Intent();
bind_intent.setClass(this, MyService.class);
ServiceConnection connection = new ServiceConnection() 
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) 
        localBinder = (MyService.LocalBinder) service;
        Log.i(TAG, "onServiceConnected: ");
    

    @Override
    public void onServiceDisconnected(ComponentName name) 
		Log.i(TAG, "onServiceDisconnected: ");
    
;
bindService(bind_intent, connection, BIND_AUTO_CREATE);

3.在activity_main.xml布局中添加两个按钮,分别为开始下载和取消下载,注意自定义的id和onClick点击事件函数名

<Button
        android:id="@+id/DownloadButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="20dp"
        android:layout_gravity="center_horizontal"
        android:onClick="StartDownload"
        android:text="开始下载" />

<Button
        android:id="@+id/CancelDownloadButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:onClick="CancelDownload"
        android:text="取消下载" />

4.在MainAcitivity.java中写入这两个函数

由于下载的程序是存放在Service中的,所以我们在点击事件StartDownload()中调用startService(),然后在Service的onStartCommand生命周期函数中执行下载的线程即可。

// MainActivity.java

//开始下载按钮点击事件
public void StartDownload(View view) 
    Intent intent = new Intent();
    intent.setClass(this, MyService.class);
    startService(intent);


// MyService.java

@Override
public int onStartCommand(Intent intent, int flags, int startId) 
    Log.i(TAG, "onStartCommand: ");
    myThread = new MyThread();
    myThread.start();
    return super.onStartCommand(intent, flags, startId);

这里使用的MyThread类继承自线程类Thread(在Thread重写run方法,并且通过pause变量实现线程的挂起和解挂)。并且这里吧MyThread类写入MyService类中即可(内部类)

public class MyThread extends Thread
        private final Object lock = new Object();
        private boolean pause = false;

        /**
         * 调用该方法实现线程的暂停
         */
        void pauseThread()
            pause = true;
        
        /*
        调用该方法实现恢复线程的运行
         */
        void resumeThread()
            pause = false;
            synchronized (lock)
                lock.notify();
            
        
        /**
         * 这个方法只能在run 方法中实现,不然会阻塞主线程,导致页面无响应
         */
        void onPause() 
            synchronized (lock) 
                try 
                    lock.wait();
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        
        @Override
        public void run() 
            super.run();
            int index = 0;
            setDownloadStatus(true);
            ServiceSendStatusBroadCast();
            try 
                //模拟下载线程:30秒睡眠,这里使用30次for循环是为了每隔一秒能在界面中显示下载进度
                for(int i=0; i<30; i++)
                    //当pause为true时,调用onPause挂起该线程
                    while(pause) 
                        onPause();
                    
                    TimeUnit.SECONDS.sleep(1);
                    Log.i(TAG, "run: "+i);//可以先打印是否能运行成功
                
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

如果要取消下载,那么我们要使用Thread中的interrupt()方法将线程中断,但线程是存放在Service中的,没有办法通过按钮点击事件直接对线程进行中断。

所以我们用到了绑定服务中ServiceConnection的onServiceConnected方法(绑定Service时执行)。在绑定服务后,通过该方法中的IBinder类的参数service可以接收到Binder类实例(需要类型转换)。然后通过localBinder.getService()可以得到绑定的Service。

// MainActivity.java
Intent bind_intent = new Intent();
bind_intent.setClass(this, MyService.class);
ServiceConnection connection = new ServiceConnection() 
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) 
        // 通过localBinder.getService()即可得到绑定的Service
        localBinder = (MyService.LocalBinder) service;
        Log.i(TAG, "onServiceConnected: ");
    

    @Override
    public void onServiceDisconnected(ComponentName name) 
        Log.i(TAG, "onServiceDisconnected: ");
    
;
bindService(bind_intent, connection, BIND_AUTO_CREATE);
// MyService.java
@Nullable
@Override
public IBinder onBind(Intent intent) 
    Log.i(TAG, "onBind: ");
    //onServiceConnected()返回的参数IBinder service 就是onBind的返回值
    //这里返回一个Binder类的继承类LocalBinder
    return new LocalBinder();


//创建本地Binder类用于返回MyService实例
public class LocalBinder extends Binder
    //调用getService可以返回绑定的Service
    public MyService getService()
        return MyService.this;
    

因此,通过该方法可以在按钮点击事件中对Service中的下载线程进行中断。

// MainActivity.java

//取消下载
public void cancelDownload()
    if(myThread != null)
        myThread.interrupt();
    

至此,运行APP,观察LogCat界面是否可以开始下载和取消下载。(取消下载时正常会抛出一个中断异常InterruptedException)

5.正在下载时设置开始下载按钮禁用,不在下载时设置取消下载按钮禁用

这个不难,但是需要结合之间学习的Android本地广播机制。

基本思路是:在Service中写入一个成员变量,用来表示是否在下载(不要在MainActivity中定义,否则app切入后台再切回可能会将该变量重置)。在点击开始下载(取消下载)按钮时根据下载状态改变该变量值,并通过发送本地广播LocalBroadcast通知MainActivity改变按钮状态(前面讲了,通过localBinder获取到MyService中的属性或方法)

public class MyService extends Service 
    private boolean DownloadStatus = false;
	public boolean getDownloadStatus() 
        return DownloadStatus;
    
    public void setDownloadStatus(boolean downloadStatus) 
        DownloadStatus = downloadStatus;
    
    
    public class MyThread extends Thread

      // ...
        @Override
        public void run() 
            super.run();
            int index = 0;
            setDownloadStatus(true);//设置下载状态变量
            ServiceSendStatusBroadCast();//发送本地广播
            try 
                for(int i=0; i<30; i++)
                    //当pause为true时,调用onPause挂起该线程
                    while(pause) 
                        onPause();
                    
                    TimeUnit.SECONDS.sleep(1);
                
             catch (InterruptedException e) 
                e.printStackTrace();
            
            setDownloadStatus(false);//下载结束或被中断,设置下载状态变量
            ServiceSendStatusBroadCast();//发送本地广播

        
    
    //取消下载
    public void cancelDownload()
        if(myThread != null)
            //中断下载线程,将下载状态置为false, 发送本地广播消息通知app更改按钮状态
            myThread.interrupt();
            
            setDownloadStatus(false);//设置下载状态变量
            ServiceSendStatusBroadCast();//发送本地广播
        
    


// MainActivity.java

public class MainActivity extends AppCompatActivity 
	public Button DownloadButton;
    public Button CancelButton;
	@Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DownloadButton = (Button) findViewById(R.id.DownloadButton);
        CancelButton = (Button) findViewById(R.id.CancelDownloadButton);

        //...
        
        //注册用于接收Service下载状态通知的本地广播
        IntentFilter statusFilter = new IntentFilter();
        statusFilter.addAction("DOWNLOAD_STATUS_CHANGED");
        BroadcastReceiver statusReceiver = new BroadcastReceiver() 
            @Override
            public void onReceive(Context context, Intent intent) 
                //在接收时根据MyService中的下载状态成员变量改变按钮状态
                MonitorDownloadStatus(localBinder.getService().getDownloadStatus());
            
        ;
        LocalBroadcastManager.getInstance(this).registerReceiver(statusReceiver, statusFilter);
        
    
    
	//根据下载状态布尔值参数改变布局中按钮的状态
    public void MonitorDownloadStatus(boolean status)
        if(status)
            //正在下载
            DownloadButton.setEnabled(false);
            CancelButton.setEnabled(true);
        else
            //未在下载
            DownloadButton.setEnabled(true);
            CancelButton.setEnabled(false);
        
    

运行app,观察LogCat打印信息是否符合预期(如果没有改变按钮可用状态,检查是否发送了本地广播,是否在每次下载状态改变后根据下载状态变量改变按钮状态)。

6.显示下载进度条

下载进度条和改变下载状态的思路一样,在每一秒(线程中的每一次for循环)通过本地广播发送一个int类型的进度。然后MainActivity接收到广播后根据这个进度值改变ProgressBar控件的值即可。

7.使用Java线程类Thread的挂起wait()和恢复notify()实现暂停和继续下载

调用MyThread类中的挂起和解挂两个方法。然后老规矩,在MainActivity中使用localBinder.getService()。

// MyService.java
public 以上是关于Android:Service组件及其简单应用的主要内容,如果未能解决你的问题,请参考以下文章

Android使用Service组件实现简单的音乐播放器

使用Service组件实现简单的音乐播放器功能 --Android基础

浅谈Android四大组建之一Service

android学习之Service

Android应用组件 —— Service

Android开发之怎样监听让Service不被杀死