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 class MyService extends Service {
    
    
    // ...
    public void pauseDownload(){
    
    
            myThread.pauseThread();
            setDownloadStatus(false);
            ServiceSendStatusBroadCast();
    }
    public void resumeDownload(){
    
    
            myThread.resumeThread();
            setDownloadStatus(true);
            ServiceSendStatusBroadCast();
    }
}
public class MainActivity extends AppCompatActivity {
    
    
	// ...
    public void ContinueDownload(View view) {
    
    
    	localBinder.getService().resumeDownload();
    }
    public void SuspendDownload(View view) {
    
    
        localBinder.getService().pauseDownload();
    }
}
<Button
        android:id="@+id/SuspendButton"
        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="SuspendDownload"
        android:text="暂停下载" />

<Button
        android:id="@+id/ContinueButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:onClick="ContinueDownload"
        android:text="继续下载" />

别忘了在暂停下载和继续下载执行时根据下载状态变量改变按钮状态。

运行,一个模拟下载的app基本就完成了!

最后

需要源码此处请
Android-Service组件应用-模拟下载

猜你喜欢

转载自blog.csdn.net/Dae_Lzh/article/details/121585403