Android Service详解(一)

本文主要介绍Service相关的使用,以及使用Service实现IPC通信

What is a Service

根据官方的介绍:

  1. Service既不是一个线程,Service通常运行在当成宿主进程的主线程中,所以在Service中进行一些耗时操作就需要在Service内部开启线程去操作,否则会引发ANR异常。
  2. 也不是一个单独的进程。除非在清单文件中声明时指定进程名,否则Service所在进程就是application所在进程。

进程存在的目的有2个:

  1. 告诉系统,当前程序需要在后台做一些处理。这意味着,Service可以不需要UI就在后台运行,不用管开启它的页面是否被销毁,只要进程还在就可以在后台运行。可以通过startService()方式调用,这里需要注意,除非Service手动调用stopService()或者Service内部主动调用了stopSelf(),否则Service一直运行。
  2. 程序通过Service对外开放某些操作。通过bindService()方式与Service调用,长期连接和交互,Service生命周期和其绑定的组件相关。

Service Lifecycle

public class MyService extends Service {

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

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

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

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
复制代码

要解释这个首先要知道Service的实现,需要实现抽象方法onBind,以及重写onStartCommand,这2个方法会在下文介绍到。

通过上面的介绍可以知道,Service有3种启动方式:

  1. startService()
  2. bindService()
  3. 同时调用

这几种方式启动的Service生命周期略微不同。

startService方式

startService()只要一个Intent参数,指定要开启的Service即可

Intent intent = new Intent(MainActivity.this, MyService.class);
复制代码
  1. 当调用Service的startService()后,

    • Service首次启动,则先调用onCreate(),在调用onStartCommand()
    • Service已经启动,则直接调用onStartCommand()
  2. 当调用stopSelf()或者stopService()后,会执行onDestroy(),代表Service生命周期结束。

  3. startService方式启动Service不会调用到onBind()。 startService可以多次调用,每次调用都会执行onStartCommand()。 不管调用多少次startService,只需要调用一次stopService就结束。 如果startService后没有调用stopSelf或者stopService,则Service一直存活并运行在后台。

  4. onStartCommand的返回值一共有3种

    • START_STICKY = 1:service所在进程被kill之后,系统会保留service状态为开始状态。系统尝试重启service,当服务被再次启动,传递过来的intent可能为null,需要注意。
    • START_NOT_STICKY = 2:service所在进程被kill之后,系统不再重启服务
    • START_REDELIVER_INTENT = 3:系统自动重启service,并传递之前的intent

    默认返回START_STICKY;

bindService方式

通过bindService绑定Service相对startService方式要复杂一点。 由于bindService是异步执行的,所以需要额外构建一个ServiceConnection对象用与接收bindService的状态,同时还要指定bindService的类型。

//1. 定义用于通信的对象,在Service的onBind()中返回的对象。
public class MyBind extends Binder {
        public int mProcessId;
 }

//2. 定义用于接收状体的ServiceConnection
mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //和服务绑定成功后,服务会回调该方法
                //服务异常中断后重启,也会重新调用改方法
                MyService.MyBind myBinder = (MyService.MyBind) service;
            }

            @Override
            public void onNullBinding(ComponentName name) {
                //Service的onBind()返回null时将会调用这个方法,并不会调用onServiceConnected()
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                // 当服务异常终止时会调用。
                // 注意,unbindService时不会调用
            }
        };
        
//3. 在需要的地方绑定到Service
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
复制代码

bindService()也可以调用多次,与startService()不同,当发起对象与Service已经成功绑定后,不会多次返回ServiceConnection中的回调方法。

通过bindService方式与Service进行绑定后,当没有对象与Service绑定后,Service生命周期结束,这个过程包括绑定对象被销毁,或者主动掉调用unbindService()

startService和bindService同时开启

当同时调用startService和bindService后,需要分别调用stopService和unbindService,Service才会走onDestroy()

一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁。

代码实操---与远端进程的Service绑定

上面的代码都是在当前进程内跟Service通信,现在我们来实现一下,不同进程内Service如何绑定。

主要步骤是这样的

  1. 编写aidl文件,AS自动生成的java类实现IPC通信的代理
  2. 继承自己的aidl类,实现里面的方法
  3. 在onBind()中返回我们的实现类,暴露给外界
  4. 需要跟Service通信的对象通过bindService与Service绑定,并在ServiceConnection接收数据。

我们通过代码来实现一下:

  1. 首先我们需要新建一个Service

    public class MyRemoteService extends Service {
    	@Nullable
    	@Override
    	public IBinder onBind(Intent intent) {
      		Log.e("MyRemoteService", "MyRemoteService thread id = " + Thread.currentThread().getId());
        return null;
    	}
    }
    复制代码
  2. 在manifest文件中声明我们的Service同时指定运行的进程名,这里并是不只能写remote进程名,你想要进程名都可以

    <service
            android:name=".service.MyRemoteService"
            android:process=":remote" />
    
    复制代码
  3. 新建一个aidl文件用户进程间传递数据。

    AIDL支持的类型:八大基本数据类型、String类型、CharSequence、List、Map、自定义类型。List、Map、自定义类型放到下文讲解。

    新建aidl文件

    里面会有一个默认的实现方法,删除即可,这里我们新建的文件如下:

    package xxxx;//aidl所在的包名
    //interface之前不能有修饰符
    interface IProcessInfo {
    	//你想要的通信用的方法都可以在这里添加
    	int getProcessId();
    }
    复制代码
  4. 实现我们的aidl类

    public class IProcessInfoImpl extends IProcessInfo.Stub {
    	@Override
    	public int getProcessId() throws RemoteException {
      		return android.os.Process.myPid();
    	}
    }
    复制代码
  5. 在Service的onBind()中返回

    public class MyRemoteService extends Service {
    	IProcessInfoImpl mProcessInfo = new IProcessInfoImpl();
    	@Nullable
    	@Override
    	public IBinder onBind(Intent intent) {
      		Log.e("MyRemoteService", "MyRemoteService thread id = " + Thread.currentThread().getId());
        return mProcessInfo;
    	}
    }
    复制代码
  6. 绑定Service

     mTvRemoteBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyRemoteService.class);
                bindService(intent, mRemoteServiceConnection, BIND_AUTO_CREATE);
            }
        });
    
    
    mRemoteServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
                Log.e("MainActivity", "MyRemoteService onServiceConnected");
    			// 通过aidl取出数据
                IProcessInfo processInfo = IProcessInfo.Stub.asInterface(service);
                try {
                    Log.e("MainActivity", "MyRemoteService process id = " + processInfo.getProcessId());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e("MainActivity", "MyRemoteService onServiceDisconnected");
            }
        };
    复制代码

只要绑定成功就能在有log打印成MyRemoteService所在进程的进程id。这样我们就完成了跟不同进程的Service通信的过程。

代码实操---调用其他app的Service

跟调同app下不同进程下的Service相比,调用其他的app定义的Service有一些细微的差别

  1. 由于需要其他app访问,所以之前的bindService()使用的隐式调用不在合适,需要在Service定义时定义action

    我们在定义的线程的App A 中定义如下Service:

    <service android:name=".service.ServerService">
    	<intent-filter>
    		//这里的action自定义
        	<action android:name="com.jxx.server.service.bind" />
          <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </service>
    复制代码
  2. 我们在需要bindService的App B 中需要做这些处理

    • 首先要将A中定义的aidl文件复制到B中,比如我们在上面定义的IProcessInfo.aidl这个文件,包括路径在内需要原封不动的复制过来。

    • 在B中调用Service通过显式调用

      mTvServerBind.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              Intent intent = new Intent();
              intent.setAction("com.jxx.server.service.bind");//Service的action
              intent.setPackage("com.jxx.server");//App A的包名
              bindService(intent, mServerServiceConnection, BIND_AUTO_CREATE);
          }
      });
      复制代码

aidl中自定义对象的传递

主要步骤如下:

  1. 定义自定对象,需要实现Parcelable接口
  2. 新建自定义对象的aidl文件
  3. 在传递数据的aidl文件中引用自定义对象
  4. 将自定义对象以及aidl文件拷贝到需要bindService的app中,主要路径也要原封不动

我们来看一下具体的代码:

  1. 定义自定义对象,并实现Parcelable接口

    public class ServerInfo implements Parcelable {
    
    public ServerInfo() {
    
    }
    
    String mPackageName;
    
    public String getPackageName() {
        return mPackageName;
    }
    
    public void setPackageName(String packageName) {
        mPackageName = packageName;
    }
    
    protected ServerInfo(Parcel in) {
        mPackageName = in.readString();
    }
    
    public static final Creator<ServerInfo> CREATOR = new Creator<ServerInfo>() {
        @Override
        public ServerInfo createFromParcel(Parcel in) {
            return new ServerInfo(in);
        }
    
        @Override
        public ServerInfo[] newArray(int size) {
            return new ServerInfo[size];
        }
    };
    
    @Override
    public int describeContents() {
        return 0;
    }
    
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mPackageName);
    }
    
    //使用out或者inout修饰时需要自己添加这个方法
    public void readFromParcel(Parcel dest) {
        mPackageName = dest.readString();
    }
    }
    复制代码
  2. 新建自定义对象的aidl文件

    package com.jxx.server.aidl;
    //注意parcelable 是小写的
    parcelable ServerInfo;
    复制代码
  3. 引用自定义对象

    package com.jxx.server.aidl;
    //就算在同一包下,这里也要导包
    import com.jxx.server.aidl.ServerInfo;
    interface IServerServiceInfo {
    	ServerInfo getServerInfo();
    	void setServerInfo(inout ServerInfo serverinfo);
    }
    复制代码

    注意这里的set方法,这里用了inout,一共有3种修饰符

    - in:客户端写入,服务端的修改不会通知到客户端
    - out:服务端修改同步到客户端,但是服务端获取到的对象可能为空
    - inout:修改都收同步的
    复制代码

    当使用out和inout时,除了要实现Parcelable外还要手动添加readFromParcel(Parcel dest)

  4. 拷贝自定义对象以及aidl文件到在要引用的App中即可。

  5. 引用

    mServerServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IServerServiceInfo serverServiceInfo = IServerServiceInfo.Stub.asInterface(service);
                try {
                    ServerInfo serviceInfo = serverServiceInfo.getServerInfo();
                    Log.e("MainActivity", "ServerService packageName = " + serviceInfo.getPackageName());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e("MainActivity", "ServerService onServiceDisconnected");
            }
        };
    复制代码

List、Map中引用的对象也应该是符合上面要求的自定义对象,或者其他的几种数据类型。

猜你喜欢

转载自juejin.im/post/5c6ff30b6fb9a049e5543ea4
今日推荐