Android入门(11)| 四大组件之服务

服务,即Service,是Android中实现程序后台的解决方案。所谓程序后台就是指那些不需要和用户进行交互但是却要求长时间运行的任务,这些任务不需要依赖任何的界面,也就是说即使服务所在的程序页面被关闭而且用户打开了另外的程序,服务仍然能够保持正常的运行。


12070003-1c3f76bdc1ec2398.png
本节目录

Android中使用多线程

Service默认并不会运行在子线程中,也不运行在一个独立的进程中,它同样执行在主线程中(UI线程)。换句话说,不要在Service里执行耗时操作(例如UI的变化),除非你手动打开一个子线程,否则有可能出现主线程被阻塞(ANR)的情况。首先来学习如何打开一个子线程。

在Android中开启子线程的方法其实是和Java中相似的,一般是有三种方法:

  • 方法一:使用Thread
    定义一个线程只需要继承自Thread并且重写Thread中的run()方法即可:
public MyThread extends Thread{
    @Override
    public void run(){
        //实现的操作
    }
}

在定义完线程之后,只需要调用Thread中的start()方法即可:

new MyThread().start();  //启动线程
  • 方法二:使用Runnable
    定义一个线程也可以让它实现Runnable接口,然后来实现接口中的方法即可:
public MyThread implements Runnable{
    @Override
    public void run(){
        //实现的操作
    }
}

如果使用的是接口,则相应的启动方式也要发生变化:

MyThread myThread = new MyThread();
new Thread(myThread).start();
  • 方法三:使用匿名类
    匿名类是创建线程比较常用的方法,它的使用最为简洁:
new Thread(new Runnable(){
    @Override
    public void run(){
        //执行的操作
    }
}).start();

异步消息处理机制

Android是不允许在子线程中执行有关UI的操作的,但是在有些时候我们必须要在子线程里执行一些耗时间的任务,然后根据任务的执行结果来更新相应的UI控件,这时我们就可以使用Android提供的一套异步消息处理机制。

异步消息处理机制主要是由4个部分组成:Message、Handle、MessageQueue和Looper。异步消息处理机制的主要流程是:首先在主线程中创建一个Handler对象,并重写handleMessage()的方法,接着是当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条信息发送出去,之后这条信息就会被添加的MessageQueue队列当中等待着被handleMessage()来处理,而Looper则会在等待的过程中一直尝试将队列中的信息发回到handleMessage()方法中。整个过程如图所示:

12070003-88562b64137b13ea.png
异步消息处理机制

知道了具体的流程,我们可以来实践一下,创建一个空项目,然后修改布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/change"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="change the text"/>
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text 1"/>
</LinearLayout>

我们这里是设置一个按钮和一段显示文字,功能是当点击按钮时我们在子线程中改变TextView显示的文字。接着修改主代码:

package com.example.yzbkaka.androidthreadtest;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    public static final int UPDATE_TEXT = 1;
    TextView textView;
    private Handler handler = new Handler(){
        public void handleMessage(Message msg){
            switch (msg.what){
                case UPDATE_TEXT:
                    textView.setText("text 2");
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button change = (Button)findViewById(R.id.change);
        change.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.change:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message);
                    }
                }).start();
        }
    }
}

我们先是定义了一个整型常量UPDATE_TEXT,然后是新建了一个Handler对象并且重写了它内部的handleMessage()方法,在这个方法中我们来对传入的信息进行判断,如果发现Message的what是等于之前定义好的UPDATE_TEXT,则就在这里(主线程)中更新UI。然后就是按钮的点击方法,在点击按钮之后我们会创建一个子线程,在子线程里面我们定义了一个Message对象,然后指定Message.what = UPDATE_TEXT,最后是使用Handler的sendMessage()方法将该消息传送到队列中等待处理。

使用服务

1.基本服务

我们先来定义一个基本服务,创建一个空项目,然后右键点击com.example.test—new—Service,然后会出来一个服务的创建界面:


12070003-15c3cf3b39238ab1.png
创建界面

这里面的Exported表示的是是否允许其他程序使用该服务,而Enable指的是是否启用该服务。都勾选之后点击finish,接着看到MyService中的代码:

package com.example.yzbkaka.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class MyService extends Service {
    public MyService() {

    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    
    @Override
    public void onCreate(){
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent,int flags,int started){
        return  super.onStartCommand(intent,flags,started);
    }

    @Override
    public void onDestroy(){
        super.onDestroy();
    }
}

一般在创建完服务之后,系统都会帮我们把代码中的构造函数和Service类中的唯一的抽象方法onBind()写好,这两个都是我们需要自己进行重写的。后面的三个方法是我们选择性重写的,不过是一般的服务里面都会使用到这三个方法,onCreate()和onDestroy()我们都很了解了,这里就不多说,而onStartCommand()则是会在每次服务启动时被调用。

当我们创建好服务之后就需要在活动里面启动和停止它,我们先在布局中添加2个按钮用于点击之后启动服务和停止服务,接着修改主代码;

package com.example.yzbkaka.servicetest;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button start = (Button)findViewById(R.id.start);
        Button stop = (Button)findViewById(R.id.stop);
        start.setOnClickListener(this);
        stop.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.start:
                Intent intent1 = new Intent(MainActivity.this,MyService.class);
                startService(intent1);
                break;
            case R.id.stop:
                Intent intent2 = new Intent(MainActivity.this,MyService.class);
                stopService(intent2);
                break;
        }
    }
}

可以看到,我们在点击的方法里面设置好了,如果是开始,则使用startService()方法并将Intent传入进去;如果是停止,则使用stopService()方法,同样也是将Intent传入进去就可以了。

接着我们在服务的代码里面添加日志记录工具来查看服务是否调用和停止成功:

package com.example.yzbkaka.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    public MyService() {

    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }


    @Override
    public void onCreate(){
        super.onCreate();
        Log.d("this","onCreate go");
    }

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

    @Override
    public void onDestroy(){
        super.onDestroy();
        Log.d("this","onDestroy go");
    }
}

运行程序,看到logcat,我们发现确实是调用成功和停止成功了:

12070003-b709459be0d63f54.png
日志

2.前台服务

由于基本服务的系统优先度是比较低的,所以当手机的内存不足时就会被系统杀死进程,但如果我们设置的服务为前台服务,则它不仅不会被杀死,还会有一个类似于通知的效果一直显示在手机的状态栏上提示用户服务还在运行。

创建前台服务的方法和创建基本服务是一样的,只不过是需要在onCreate()的方法当中修改一些代码即可:

@Override
    public void onCreate(){
        super.onCreate();
        Log.d("this","onCreate go");
        Intent intent = new Intent(this,MainActivity.class);
        PendingIntent pi = PendingIntent.getActivities(this,0, intent,0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("title")
                .setContentText("Content")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1,notification);
    }

可以看到我们在创建前台服务的方法和创建通知的方法很像。先是为通知设置点击,即当我们点击前台服务的时候页面会跳到主活动当中,接着就是为我们的通知设置一些属性,这些属性我们之前都见过,所以这里就不多说了。最后这里启动的方法不再是用NotificationMan-ager,而是使用的startForeground()的方法,这个方法需要传入两个参数:第一个参数是通知的id,这里必须要为每一个服务提供一个唯一的id;第二个参数就是将Notification对象传入进去。

运行程序我们就可以看到状态栏上显示的前台服务了。

3.IntentService

由于服务中的代码都是默认运行主线程当中的,所以如果我们将一些比较耗时的操作都放在服务中并让其在主线程中进行,就很有可能出现ANR的情况。所以我们一般需要在服务中的onStartCommand()方法中手动的开启子线程和关闭子线程:

   @Override
    public int onStartCommand(Intent intent,int flags,int started){
        new Thread(new Runnable() {
            @Override
            public void run() {
                //执行的操作
                stopSelf();
            }
        }).start();
        return  super.onStartCommand(intent,flags,started);
    }

手动开启线程和关闭线程有时候是很麻烦的,因此我们可以使用IntentService来使用系统自动的操作子线程。

我们先为再布局添加一个按钮,然后新建一个类并让它继承自IntentService,然后重写它的方法:

package com.example.yzbkaka.servicetest;

import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.util.Log;


public class MyIntentSrvice extends IntentService {
    
    public MyIntentSrvice() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d("MyIntentService","the thread is"+Thread.currentThread().getId());
    }
    
    @Override
    public void onDestroy(){
        Log.d("MyIntentService","onDestroy go");
    }
}

这里我们首先是重写了一个空构造函数,需要注意的是它调用父类的构造函数时必须要传入参数。接着是重写IntentService中的抽象方法onHandleIntent(),initService主要就是在这个方法中进行操作的,这里我们只是使用日志打印出当前的线程号以此来判断是否自动操作了线程。最后就是重写当服务销毁时的方法即可。

接着我们在主代码中修改按钮的点击事件:

......
case R.id.intent_service:
                Intent intent4 = new Intent(MainActivity.this,MyIntentSrvice.class);
                startService(intent4);

最后使用IntentService需要先注册:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yzbkaka.servicetest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>
        <service android:name=".MyIntentSrvice"/>
    </application>

</manifest>

运行程序,打开日志,发现线程号确实改变了,说明IntentService是自动操作了线程:


12070003-7aab1db94f97afcc.png
线程改变

活动和服务进行通信

在前面创建服务的时候,我们只是在活动中对服务进行调用和停止操作,两者之间并没有过多的“交流”。其实如果想让两者之间进行通信,我们可以使用之前还没有用到过的onBind()方法来进行相关的操作。

我们接着使用上面的项目,先在服务里面修改代码:

......
    private TestClass myTestBinder = new TestClass();

    class TestClass extends Binder{
        public void testWay1(){
            Log.d("this", "testWay1 go");
        } 

        public void testWay2(){
            Log.d("this", "testWay1 go");
        }
    }  //内部类

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return myTestBinder;
    }
......

我们在服务里面添加一个内部类并让它继承自Binder,然后在这个内部类中添加了两个模拟方法并通过日志的方式来为之后的验证做好准备。接着是修改主布局中的代码:

......
<Button
        android:id="@+id/bind"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:text="bind"/>
    <Button
        android:id="@+id/unbind"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="unbind"/>
......

我们来为布局添加两个按钮,分别表示开始绑定活动和解除绑定活动。最后是修改主代码:

package com.example.yzbkaka.servicetest;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    MyService.TestClass testClass;
    
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            testClass = (MyService.TestClass) iBinder;
            testClass.testWay1();
            testClass.testWay2();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };  //匿名类

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button start = (Button)findViewById(R.id.start);
        Button stop = (Button)findViewById(R.id.stop);
        Button bind = (Button)findViewById(R.id.bind);
        Button unbind = (Button)findViewById(R.id.unbind); 
        start.setOnClickListener(this);
        stop.setOnClickListener(this);
        bind.setOnClickListener(this);
        unbind.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.start:
                Intent intent1 = new Intent(MainActivity.this,MyService.class);
                startService(intent1);
                break;
            case R.id.stop:
                Intent intent2 = new Intent(MainActivity.this,MyService.class);
                stopService(intent2);
                break;
            case R.id.bind:
                Intent intent3 = new Intent(MainActivity.this,MyService.class);
                bindService(intent3,connection,BIND_AUTO_CREATE);  //绑定服务
                break;
            case R.id.unbind:
                unbindService(connection);  //解除绑定
                break;
        }
    }
}

在主代码中我们先是创建了 MyService.TestClass的一个实例,接着是创建了一个ServiceConnection的匿名类并且在匿名类当中重写了它的两个方法,onServiceConnec ted()方法是当活动和服务绑定上之后被调用的,在这里我们是调用了TestClass中的两个方法;onServiceDisconnected()是当活动和服务解除绑定的时候调用的。最后就是在按钮中启动绑定和解绑了,启动绑定我们使用的方法是bindService(),这个方法需要传入三个参数:第一个参数是Intent;第二个参数是之前建立好的Connection实例;第三个参数则是一个标志位,这里我们使用的是系统默认的操作。而在解绑中我们直接将Connection实例传入到unbindService()中即可。

最后运行程序,打开日志会发现我们已经在活动中调用到服务中的方法了。

服务的生命周期

首先放出Android官方的生命周期图:

12070003-b1035ac0b61776c8.png
服务的生命周期

通过图示,我们可以总结一下:
(1)当我们在活动中调用startService()的方法时,相应的服务就会启动并调用onCreate(),接着是调用onStartCommand()方法,如果这个活动之前已经创建过了则不会调用onCreate()方法。之后服务就会一直运行下去直到调用了stopService()或者是stopSelf()被调用,这时活动就会停止下来,而服务中的onDestroy()就会执行,服务就会被销毁。

(2)如果我们想要将活动的服务有更多的交流,则是可以使用bindService()方法来调用服务,接着服务中的onCrea()方法就会启动,然后就是调用onBind()方法,同样如果服务之前已经创建,则不会调用onCreate()方法。接着就是服务一直运行直到使用unBindService()方法,最后是调用onDestroy()方法来销毁活动。

(3)如果当使用服务时即调用了startSer()方法和onBind()方法,则还是会和正常的一样调用方法,不过如果要销毁活动时必须是将stopService()和unBindService()方法同事调用之后才能执行onDestroy()方法。

猜你喜欢

转载自blog.csdn.net/weixin_33978044/article/details/87260730