Android05--Android服务通信

标签: Android

创建时间:2016-9-16 16:26:12
更新时间:2016-9-18 22:55:23


undefined

传递参数到Service #

我们知道,Activity可以传递参数到另一个Activity,Service同样可以接收来自Activity传来的参数。

下面是个示例,新建个应用程序LearnServicePutArgs,并新建MyService继承Service。当我们启动服务的时候传递文本输入框的参数到Service,Service接收并打印出来。

先注册服务:

<service android:name=".MyService"></service>

下面是Service代码:
MyService.java

package com.a52fhy.learnserviceputargs;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;

/**
 * Created by YJC on 2016/9/18 018.
 */
public class MyService extends Service {

    private boolean running = false;
    public String data = "默认信息";

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

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

        running = true;

        new Thread(){
            @Override
            public void run() {
                super.run();

                while(running){
                    System.out.println(data);
                    try {
                        sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        data = intent.getStringExtra("name");

        return super.onStartCommand(intent, flags, startId);
    }

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

        running = false;
    }
}

onCreate()里我们运行了一个线程,不断循环输出字符串。

MainActivity代码:

package com.a52fhy.learnserviceputargs;

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

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        findViewById(R.id.btnStartService).setOnClickListener(this);
        findViewById(R.id.btnStopService).setOnClickListener(this);
    }

    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    @Override
    public void onClick(View v) {

        String name = ((EditText)findViewById(R.id.etString)).getText().toString();
        Intent i = new Intent(this, MyService.class);
        i.putExtra("name",  name);

        switch (v.getId()){
            case R.id.btnStartService:
                startService(i);
                break;
            case R.id.btnStopService:
                stopService(i);
                break;
        }
    }
}

程序设定了2个按钮,分别是启动服务和停止服务。启动服务的时候获取文本框的值,并传递到服务里。

程序运行结果:

  1. 开始服务:
com.a52fhy.learnserviceputargs I/System.out: 默认信息
com.a52fhy.learnserviceputargs I/System.out: Hello World!
com.a52fhy.learnserviceputargs I/System.out: Hello World!
...
  1. 编辑文本,再次开始服务:
com.a52fhy.learnserviceputargs I/System.out: 啦啦啦
com.a52fhy.learnserviceputargs I/System.out: 啦啦啦
...

开始服务的时候,程序执行onCreate(),这时候running = truedata是默认值默认信息,所以输出了默认信息;程序继续执行了onStartCommand(),这时候data被赋新值Hello World!,输出了Hello World!;后面程序一直运行,输出的都是Hello World!

编辑文本,再次开始服务的时候,程序不再执行onCreate(),而是执行了onStartCommand()data被赋新值,所以输出的也是新值啦啦啦

如果我们把线程放到onStartCommand(),就不会输出第一行的默认信息

绑定服务传递数据到Service #

我们知道,绑定服务使用bindService(Intent service, ServiceConnection conn, int flags),取消绑定使用unbindService(ServiceConnection conn)

绑定服务的流程是bindService->onCreate()->onBind()->onServiceConnected。再次绑定不会执行onCreate()

我们可以利用Binder进行通信。因为我们可以在onServiceConnected里获取到IBinder service

和上节一样,界面有文本输入框,还有绑定服务、取消绑定服务、传输数据按钮。

新建BindService类:

package com.a52fhy.learnserviceputargs;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;

/**
 * Created by YJC on 2016/9/19 019.
 */
public class BindService  extends Service{

    private boolean running = false;
    public String data = "默认信息";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //第一次绑定服务后会执行onCreate->onBind
        //第二次绑定服务后会执行onBind
        return new Binder();
    }

    public class Binder extends android.os.Binder{

        public void setData(String data){
            BindService.this.data = data;
        }
    }

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

        running = true;

        new Thread(){
            @Override
            public void run() {
                super.run();

                while(running){
                    System.out.println(data);
                    try {
                        sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

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

        running = false;
    }
}

这里新建了内部类Binder继承自android.os.Binder,并包含setData成员方法,进行修改外部类的属性BindService.this.data。执行到onBind()会返回实例化的Binder对象。

再看BindServiceAty:

package com.a52fhy.learnserviceputargs;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;

public class BindServiceAty extends AppCompatActivity implements ServiceConnection, View.OnClickListener {

    public BindService.Binder binder;

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

        findViewById(R.id.btnBindService).setOnClickListener(this);
        findViewById(R.id.btnUnbindService).setOnClickListener(this);
        findViewById(R.id.btnSyncData).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {

        String name = ((EditText)findViewById(R.id.etString)).getText().toString();

        switch (v.getId()){
            case R.id.btnBindService:
                Log.i("Service", "bindService");
                bindService(new Intent(BindServiceAty.this, BindService.class), this, Context.BIND_AUTO_CREATE);
                break;
            case R.id.btnUnbindService:
                Log.i("Service", "unbindService");
                unbindService(this);
                break;

            case R.id.btnSyncData:
                if(binder != null){
                    Log.i("Service", "btnSyncData");
                    binder.setData(name);
                }
                break;
        }
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i("Service",  "onServiceConnected");
        binder = (BindService.Binder) service;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
}

当点击了绑定服务才允许点击传输数据按钮。因为绑定服务后返回了Binder对象,在onServiceConnected里有。我们赋值给binder,并强制转为BindService.Binder类型的,这样,我们可以使用内部类成员方法setData()

注意到AndroidManifest.xml注册服务:

<service android:name=".BindService" />

程序运行结果:

  1. 第一次点击绑定服务,输出:
com.a52fhy.learnserviceputargs I/Service: bindService
com.a52fhy.learnserviceputargs I/System.out: 默认信息
com.a52fhy.learnserviceputargs I/Service: onServiceConnected
com.a52fhy.learnserviceputargs I/System.out: 默认信息
com.a52fhy.learnserviceputargs I/System.out: 默认信息
com.a52fhy.learnserviceputargs I/System.out: 默认信息
...

这个好理解,第一次会依次运行bindService->onCreate()->onBind()->onServiceConnected

  1. 然后点击传输数据,输出:
com.a52fhy.learnserviceputargs I/Service: btnSyncData
com.a52fhy.learnserviceputargs I/System.out: Hello World!
com.a52fhy.learnserviceputargs I/System.out: Hello World!
com.a52fhy.learnserviceputargs I/System.out: Hello World!
...

因为已经绑定过服务了,binder不为空,是个实例,可以直接调用setData方法。

获取服务的状态信息 #

既然可以将数据传给绑定的服务,那么是否可以从运行中的Service里取到数据呢?答案是肯定的。

下面我们将把Service里生成的数据显示在Activity里。

我们得借助回调函数做到这个。想法是在onServiceConnected(ComponentName name, IBinder service)里设置回调,在Service里执行。这样,回调执行的时候返回数据给onServiceConnected

好,借助回调,我们拿到了数据,但安卓的安全机制限制了Service里进程不能修改UI进程的数据,怎么办?我们得借助Message对象来辅助解决。

下面是代码:
BindService.java

package com.a52fhy.learnserviceputargs;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;

/**
 * Created by YJC on 2016/9/19 019.
 */
public class BindService  extends Service{

    private boolean running = false;
    public String data = "默认信息";

    //callback实例
    public ServiceCallback callback;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //第一次绑定服务后会执行onCreate->onBind
        //第二次绑定服务后会执行onBind
        return new Binder();
    }

    public class Binder extends android.os.Binder{

        public void setData(String data){
            BindService.this.data = data;
        }

        /**
         * 返回外部类,以方便获取外部类的成员
         * @return
         */
        public BindService getService(){
            return BindService.this;
        }
    }

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

        running = true;

        new Thread(){
            @Override
            public void run() {
                super.run();
                int i = 0;
                while(running){
                    String str = i + ":" + data;
                    System.out.println(str);

                    //设置了回调则执行回调里方法
                    if(callback != null){
                        callback.onDateChange(str);//设置数据
                    }

                    try {
                        sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    i++;
                }
            }
        }.start();
    }

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

        running = false;
    }


    /**
     * interface ServiceCallback
     */
    public interface ServiceCallback{
        public void onDateChange(String data);
    }

    /**
     * 设置callback
     * @param callback
     */
    public void setCallback(ServiceCallback callback){
        this.callback = callback;
    }
}

以上代码,我们创建了接口ServiceCallback,并包含需要实现的方法onDateChange()。我们给类BindService添加了成员方法setCallback用于设置回调,当回调设置后,执行回调接口的onDateChange(String data),并附带参数。

BindServiceAty里我们再增加个Textview,用于显示回调里接收到的数据。代码:

package com.a52fhy.learnserviceputargs;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class BindServiceAty extends AppCompatActivity implements ServiceConnection, View.OnClickListener {

    //BindService内部类的属性
    public BindService.Binder binder;

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

        findViewById(R.id.btnBindService).setOnClickListener(this);
        findViewById(R.id.btnUnbindService).setOnClickListener(this);
        findViewById(R.id.btnSyncData).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {

        String name = ((EditText)findViewById(R.id.etString)).getText().toString();

        switch (v.getId()){
            case R.id.btnBindService:
                Log.i("Service", "bindService");
                bindService(new Intent(BindServiceAty.this, BindService.class), this, Context.BIND_AUTO_CREATE);
                break;
            case R.id.btnUnbindService:
                Log.i("Service", "unbindService");
                unbindService(this);
                break;

            case R.id.btnSyncData:
                if(binder != null){
                    Log.i("Service", "btnSyncData");
                    binder.setData(name);
                }
                break;
        }
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i("Service",  "onServiceConnected");
        binder = (BindService.Binder) service;

        //设置回调
        binder.getService().setCallback(new BindService.ServiceCallback() {
            @Override
            public void onDateChange(String data) {
                //这里取到service里传来的参数data
                Message message = new Message();
                Bundle bundle = new Bundle();
                bundle.putString("msg", data);
                message.setData(bundle);//设置参数
                handler.sendMessage(message);//发送消息
            }
        });
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }


    private Handler handler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            
            //处理接收到的消息
            super.handleMessage(msg);

            //显示传过来的数据
            ((TextView)findViewById(R.id.textView)).setText(msg.getData().getString("msg"));
        }
    };
}

我们先实现了Handler里的handleMessage()方法以接收来自onServiceConnected发送的消息。onServiceConnected里给BindService设置回调,回调里可以获取来自Service里的数据。由于Service里进程不能修改UI进程的数据,我们采用了发送消息的方式传递参数。接收到消息后,显示到Textview里。

AIDL #

AIDL是 Android Interface definition language的缩写,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口。

Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.
只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL。

AIDL是处理多线程、多客户端并发访问的。

Build by Loppo 0.6.6