Android Service详解(一)

栏目: IOS · Android · 发布时间: 6年前

内容简介:本文主要介绍Service相关的使用,以及使用Service实现IPC通信根据官方的介绍:进程存在的目的有2个:

本文主要介绍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、自定义类型放到下文讲解。

    Android Service详解(一)

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

    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中引用的对象也应该是符合上面要求的自定义对象,或者其他的几种数据类型。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ordering Disorder

Ordering Disorder

Khoi Vinh / New Riders Press / 2010-12-03 / USD 29.99

The grid has long been an invaluable tool for creating order out of chaos for designers of all kinds—from city planners to architects to typesetters and graphic artists. In recent years, web designers......一起来看看 《Ordering Disorder》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器