8、Android---探究服务

 

8.1、服务是什么

服务(Service)是Android中实现程序后台运行的解决方案

非常适合执行那些不需要和用户交互而且要求长期的任务

 

服务的运行不依赖于任何用户界面

即使程序被切换到后台

或者用户打开了另一个应用程序,服务仍然能狗爆出正常的运行

 

实际上服务不会依赖自动开启线程

所有的代码都是默认运行再主线程当中

需要再服务的内部手动创建子线程

并再这里执行具体的任务

否则就有可能出现主线程被阻塞的情况

 

8.2、Android多线程

执行一些耗时操作

再发器一条网络请求时

考虑到网络等其他原因

服务器未必会立刻响应请求

如果不将这类操作放在子线程中,就会导致主线程被阻塞

从而影响软件的使用

 

9.2.1、线程的基本用法

 

定义一个线程需要继承Thread

然后重写run()方法

class MyThread extends Thread{

  public void run(){

  } }

 

启动线程:

new一个MyThread的实例

然后再调用start()方法

这样run()方法中的代码就会再子线程中运行

new MyThread().start();

 

使用继承的方式耦合性有点高

更多的时候会选择Runnable接口的方式定义线程

class MyThread implements Runnable{

  public void run(){

  } }

 

启动:

MyThred my = new MyThred()

new Thread(my).start();

 

Thread的构造函数接收一个Runnable参数

而new出来的MyThread正是一个实现Runnable接口的对象

所以直接将他闯入Thread的构造函数中

接着调用Thread的start()方法

run()方法中的代码就会在子线程中运行

 

同时也可以使用匿名类的方式定义:

new Thread(new Runnable(){
  public void run(){
  
  } }).start();

 

使用方式和Java中的一样!!!

 

8.2.2、在子线程中更新UI

和许多其他的GUI库一样

Android的UI也是线程不安全的

如果想要更新应用程序的UI元素

则必须在主线程中进行,否则会抛异常

 

    <Button
        android:id="@+id/change"
        android:text="change"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/text"
        android:text="hello"
        android:textSize="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

 

 

MainActivity中

    TextView text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        text = (TextView) findViewById(R.id.text);
        Button change = (Button) findViewById(R.id.change);
        change.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        text.setText("change...");
                    }
                }).start();
            }
        });

    }

点击事件里面开启一个子线程

子线程中将TextView的内容进行改变

测试的内容只是在子线程中更新UI

同时控制台中:

 

可以证时Android确实不允许在子线程中进行更新UI的操作

如果必须在子线程中执行一些耗时任务

然后根据任务执行结果来更新相应的UI控件

 

对此Android提供了一套异步消息处理机制

完美的解决了在子线程中进行了UI的操作问题

    TextView text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        text = (TextView) findViewById(R.id.text);
        Button change = (Button) findViewById(R.id.change);
        change.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = 1;
                        handle.sendMessage(message);
                    }
                }).start();
            }
        });
    }

    private Handler handle = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    text.setText("chang...");
                    break;
                default:
                    break;
            }
        }
    };

新增一个Handler对象

并且重写父类的handleMessage()方法

在这里对具体的Message进行处理

发现Message的what字段的值等1,就进行重新设置

 

 

这种机制可以出色的解决掉在子线程中更新ui的问题

 

8.2.3、解析异步消息处理机制

 

Android中的异步消息护理主要由4部分组成:

1、Message

2、Handler

3、MessageQueue

4、Looper

 

上述的案例使用的是Message和Handler

 

1、Message

Message是线程之间传递的消息,可以在内部携带少量的信息

用于不同线程之间交换数据

不仅可以使用what字段,还可以使用arg1和arg2字段来携带一些整形数据

使用obj字段携带一个Object对象

 

2、Handler

Handler就是处理者的意思

主要用于发送和处理消息的

发送消息一般是使用handler的sendMessage()方法

发出的消息经过一系列的辗转处理后

最终传递到handleMessage()方法中

 

3、MessageQueue

是消息队列

主要用于存放所有通过Handler发送消息

这部分消息会一直存储在消息队列中

等待被处理

每个线程中只有一个MessageQueue对象

 

4、Looper

Looper是每个线程中的MessageQueue的管家

调用Looper的loop()方法后

就会进入到一个无限循环当中

然后发现当MessageQueue中存一条消息,就会将他取出

并传递到Handler的handleMessage()方法中

每个线程中只有一个Looper对象

 

首先在主线程中创建Handler对象

并且重写handlerMessage()方法

 

然后当子线程中需要进行UI操作时

就创建一个Message对象

并通过Handler将这条消息发出去

 

之后这条消息就被添加到MessageQueue的队列中等待被处理

而Looper则会一直尝试从MessageQueue中取出等待处理消息

 

最后分发回Handler的handlerMessage()方法中

 

由于Handler是在主线程中创建的

所以此时handleMessage()方法中的代码也会在主程序中 运行

于是可以进行UI操作

示意图:

 

 一条Message经过这样一个流程的辗转调用之后

从子线程进入到主线程

从不能更新UI变成了可更新UI,整个异步消息处理的核心思想

 

8.2.4、使用AsyncTask

Android提供了更方便的工具AsyncTask

对异步消息处理机制不了解也可以是简单的从子线程中切换到主线程

其实现原理时异步消息处理机制

在其基础上做的封装

 

基本用法:

AsyncTask是一个抽象类

使用需要城建一个类去继承

指定三个泛型参数

第一个参数指定为Void,在执行AsyncTask的时候不需要传入参数给后台服务

第二个参数指定为Integer,使用整形数据作为进度条显示单位

第三个参数指定为Boolean,使用布尔值来反馈执行结果

 

class MyTask extends AsyncTask<Void,Interger,Boolean>{...

 

 

自定义的MyTask是一个空任务

不能执行任何操作

需要重新实现抽象类中的几个方法

 

 

 

 

 8.3、服务的基本用法

 

 8.3.1、定义一个服务

new -- Service --Service

Exported:表示属性是否允许除了当前程序之外的程序访问这个服务

Enable:表示是否启用这个服务

 

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

 

 继承Service类,说说明是一个服务

只有一个onBind()方法

这事Service中唯一的一个抽象方法

所以必须要在字类中实现

 

可以重写Service中的其他方法:

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

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

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

 

onCreate()方法在创建的时候调用

onStartCommand()每次启动服务的时候调用

onDestroy()在服务销毁的时候调用

 

通常希望服务一启动就执行某个动作使用onStartCommand()

在服务销毁时,使用onDestroy()方法回收不在使用的资源

 

 

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

 

 

 8.3.2、启动和停止服务

 启动和停止服务的方法都是借助Intent来实现的

<Button
    android:id="@+id/start"
    android:text="start"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
    <Button
        android:id="@+id/stop"
        android:text="stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

 

MainActivity中:

 @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(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent startIntent = new Intent(MainActivity.this,MyService.class);
                startService(startIntent);
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent stopIntent = new Intent(MainActivity.this,MyService.class);
                stopService(stopIntent);
            }
        });
    }

 

 MyService

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

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

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

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

startService()启动MyService服务

sotpService()停止MyService服务

 这里完全由活动来决定服务何时停止的

若没有停止按钮,服务回一直处于运行状态

此时在MyService中任何一个位置调用stopSelf()方法就能让这个服务停止

 

 onCreate()方法旨在服务第一次创建的时候调用

onStartCommand()方法旨在每次活动启动之后调用,在第二次点击之后只执行此方法

 

 

8.3.3、活动和服务进行通信

 

如何使活动指挥服务干什么服务就去干什么

此时需要使用onBind()方法

 

小事例:

MyService中提供一个下载功能

在活动中可以决定啥时候开始,以及随时查看下载进行

  

这里创建了一个DownloadBinder类

并且让他继承Binder

然后再它的内容提供了开始下载以及下载进度的提示

 

再MyService中创建DownloadBinder的实例

然后再onBind()方法返回这个实例,这样MyService中工作就完成了

 

 MyService

public class MyService extends Service {

    class DownLoadBinder extends Binder{
        //开始下载
        public  void startDownLoad(){
            Log.d("MyService", "startDownLoad");
        }
        //查看进度
        public int getProgress(){
            Log.d("MyService", "getProgress");
            return 0;
        }
    }

    private DownLoadBinder downLoadbinder = new DownLoadBinder();

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

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

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

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

 

按钮:

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

 

MianActivity

public class MainActivity extends AppCompatActivity {

    private  MyService.DownLoadBinder downLoadBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downLoadBinder = (MyService.DownLoadBinder) service;
            downLoadBinder.startDownLoad();
            downLoadBinder.getProgress();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };


    @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(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent startIntent = new Intent(MainActivity.this,MyService.class);
                startService(startIntent);
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent stopIntent = new Intent(MainActivity.this,MyService.class);
                stopService(stopIntent);
            }
        });

        Button bind = (Button) findViewById(R.id.bind);
        bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent bindIntent = new Intent(MainActivity.this,MyService.class);
                //绑定服务
                bindService(bindIntent,connection,BIND_AUTO_CREATE);
            }
        });
        Button unbind = (Button) findViewById(R.id.unbind);
        unbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //解绑服务
                unbindService(connection);
            }
        });
    }
}

首先创建一个ServiceConnection的匿名类

再里面重写onServiceConnected()方法和onServiceDisconnected()方法

这两个方法分别再服务成功绑定以及接触绑定的时候调用

 

再onServiceConnected()方法中有通过向下转型得到DownLoadService的实例

有了这个实例,活动和服务之间的关系就变得非常紧密了

 

现在再活动中根据具体的场景来调用DownLoadBinder中的任何public()方法

实现了指挥服务干啥就干啥

 

绑定按钮中:

构建一个Intent对象

调用bindService()方法讲MianActivity和MyService进行绑定,三个参数:

1、刚刚构建的Intent对象

2、ServiceConnection实例

3、一个标志位BIND_AUTO_CREATE表示活动和服务进行绑定后自动创建

这会让MyService中的onCreate()方法得到执行,onStartCOmmand()方法不会执行

 

解除绑定:

调用unbindService()方法就可以了

 

 

点击绑定事件之后:

 

 点击取消绑定:

 

 

 8.4、服务的生命周期

在项目的任何位置调用了Content的startService()方法

相应的服务就会启动起来

并回调onStartCommand()方法

 

这个服务之前没有创建过

onCreate()方法就会先于onStrartConnand()方法之前执行

 

服务启动之后就会一直保持运行状态

知道stopService()方法或者stopSelf()方法调用

 

虽然每调用一次sstartService()方法

onStratCommand()方法就会执行一次

但是实际上每个服务都只会存在一个实例

 

还可以调用Context的bindService()来获取一个永久链接

这时会调用onBind()方法

如果这个服务之前还没创建过

onCreate()方法会有限于onBind()方法

之后可以获取到onBind()方法返回的IBinder对象实例

这样就能实现了自由和服务进行通信

调用方和服务之间链接没有断开

服务就会一直保持运行的状态

 

调用了startService()方法之后

又去调用stopService()方法

这时服务中的onDestroy()方法就会执行,表示服务已经销毁

 

同样的:

当调用了bindService()方法后再调用unbindService()方法,onDestroy()方法也会执行

 

注意

完全有可能对一个服务既调用startService()方法又调用bindService()方法

这时该怎么销毁?

Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态

必须让两种条件同时不满足,服务才能被销毁

解决:同时调用stopService()和unbindService()方法,此时onDestroy()方法才能执行

 

 

8.5、服务的更多技巧

8.5.1、使用前台服务

服务几乎都是再后台运行的

一直以来都是默认做着工作

但是服务得系统优先级还是比较低的

再系统出现不足的情况下,就很有可能回收掉正在后台运行的服务

 

如果希望服务一直运行

不会由于内存不足的情况而导致被回收

此时可以考虑使用前台服务

 

前台服务和普通服务最大的区别:

他会一直正在运行的图标再系统的状态栏显示

下拉状态栏可以看到更多详细的信息

 

MyService中

public class MyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("onCreate", "onCreate: ");

        Intent intent = new Intent(this,MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("this content title")
                .setContentText("content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();

        startForeground(1,notification);

    }
...
}

 

只需要修改onCreate()方法中的方法

构建Notifiation对象后

并没有使用NotificationManager来将通知显示出来

此时调用了startForeground()方法

接收两个参数

1、通知的id

2、构建出来的Notification对象

调用方法之后就会让MyService变成一个前台服务

并且在状态栏进行显示

此时可以看到:

 

 

8.5.2、使用IntentService

服务中的代码都是默认运行在主线程中的

如果直接再服务中处理一些耗时的操作

就很容易出现ANR(Application Not Responding)

 

此时就需要使用Android的多线程技术

应该将服务的每个具体方法里面开辟一个子线程

在子线程中去处理耗时的逻辑

MyService

  @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("onStartCommand", "onStartCommand: ");
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                //耗时操作
            }
        });
        return super.onStartCommand(intent, flags, startId);
    }

 

此时服务启动之后

就会一直处于运行状态

必须调用stopService()或者stopSelf()方法才能让服务停止下来

实现让一个服务执行完毕后自动停止的功能:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("onStartCommand", "onStartCommand: ");

        new Thread(new Runnable() {
            @Override
            public void run() {
                //耗时操作
             stopSelf();
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

 

 在这里容易忘记的是调用stopSelf()或者开启线程

为了简单的创建一个异步的、会自动停止的服务

Android提供了一个IntentService

 

public class MyIntentService extends IntentService {

    public MyIntentService( ) {
        super("MyService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d("onHandleIntent", "Thread id is :" +Thread.currentThread().getId());
    }

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

 

首先需要提供一个无参的构造函数

然后再字类中实现onHandleIntent()这个抽象方法,处理一些具体的逻辑,不用担心ANR问题

因为这个放在已经是再子线程中运行,此时进行打印线程的id

最后,服务再运行结束会自动的停止,此时重写onDestroy()方法进行测试

 

定义一个按钮:

   <Button
        android:id="@+id/start_intentService"
        android:text="Start IntentService"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

 

MainActivity“:

   Button btn_internetService = (Button) findViewById(R.id.start_intentService);
        btn_internetService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("btn_internetService", "Thread id is:: " + Thread.currentThread().getId());
                Intent intentService = new Intent(MainActivity.this,MyIntentService.class);
                startService(intentService);

            }
        });

注册:

 

点击按钮之后:

主线程中的id为1

子线程中的id为133040

 此时开启多线程和自动停止都在一个方法上

极大的方便了开发

 

 

 

posted @ 2019-04-17 20:31  MrChengs  阅读(235)  评论(0编辑  收藏  举报