IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android:Service组件及其简单应用 -> 正文阅读

[移动开发]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组件应用-模拟下载

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-29 16:26:00  更:2021-11-29 16:28:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:21:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码