在了解service相关知识之前,先了解一下多线程的知识。在实际开发中,Service很多时候会和多线程相关进行结合。
唠叨两句,有很多同学会把进程和线程的概念混淆分不清,这里教大家一个办法,把进程看作一列火车;而把线程看作组成火车的一节节车厢,然后我们再来看线程和进程的概念是不是很符合这个概念呢?
进程是最小的资源分配单位;线程是最小的程序执行单元
一共五个阶段 新建、就绪、运行、阻塞、死亡
//创建并启动
new Thread(myThread).start();
//使用匿名内部类
new Thread(new Runnable(){
public void run();
}).start();
什么是Servie?
Service (服务)是能够在后台执行长时间运行操作并且不提供用户界面的应用程序组件。其他应用程序组件能启动服务,并且即便用户切换到另一个应用程序,服务还可以在后台运行。此外,组件能够绑定到服务并与之交互,甚至执行进程间通信(IPC) 。例如,服务能在后台处理网络事务、播放音乐、执行文件IO或者与ContentProvider通信。
由上图可知,Service主要分为两种
其实还有一种就是启动Service后,绑定Service,也就是两种兼有的
不管应用程序是否为启动状态、绑定状态或者两者兼有,都能通过Intent使用服务,就像使用Activity那样。然而,开发人员可以在配置文件中将服务声明为私有的,从而阻止其他应用程序访问。
服务运行于管理它的进程的主线程,服务不会创建自己的线程,也不会运行于独立的进程(除非开发人员定义)。这意味着,如果服务要完成CPU密集工作或者阻塞操作(如MP3回放或者联网),开发人员需要在服务中创建新线程来完成这些工作。通过使用独立的线程,能减少应用程序不响应(ANR)错误的风险,并且应用程序主线程仍然能用于用户与Activity的交互。
为了创建服务,开发人员需要创建Service类(或其子类)的子类。在实现类中,需要重写一些处理服务生命周期重要方面的回调方法,并根据需要提供组件绑定到服务的机制。
回调 | 描述 |
---|---|
onCreate() | 当服务通过onStartCommand()和onBind()被第一次创建的时候,系统调用该方法。该方法在整个生命周期 中只会调用一次,如果服务已经运行,该方法不被调用 |
onDestory() | 当服务不再有用或者被销毁时,系统调用该方法。你的服务需要实现该方法来清理任何资源,如线程,已注册的监听器,接收器等。该方法只会回调一次! |
onStartCommand() | 其他组件(如活动)通过调用startService()来请求启动服务时,系统调用该方法。如果你实现该方法,你有责任在工作完成时通过stopSelf()或者stopService()方法来停止服务。当客户端调用startService(Intent)方法时会回调,可多次调用StartService方法, 但不会再创建新的Service对象,而是继续复用前面产生的Service对象,但会继续回调 onStartCommand()方法 |
IBinder onOnbind() | 该方法是Service都必须实现的方法,当其他组件想要通过bindService()来绑定服务时,系统调用该方法。如果你实现该方法,你需要返回IBinder对象来提供一个接口,以便客户来与服务通信。你必须实现该方法,如果你不允许绑定,则直接返回null。 |
onUnbind() | 当该Service上绑定的所有客户端都断开时会回调该方法 |
onRebind() | 当新的客户端与服务连接,且此前它已经通过onUnbind(Intent)通知断开连接时,系统调用该方法。 |
需要重写的重要回调方法有onStartCommand()
、onBind()
、onCreate()
、onDestroy()
service>
android:enabled
android:exported
android:icon
android:label
android:name
android:permission
android:process
要想声明并启动Service,开发人员需要创建Service类(或其子类)的子类
Android提供了两个类供开发人员继承以创建启动服务。
Service
: 这是所有服务的基类。当继承该类时,创建新线程来执行服务的全部工作是非常重要的。因为服务默认使用应用程序主线程,这可能降低应用程序Activity的运行性能。IntentService
: 这是Service类的子类,它每次使用一个工作线程来处理全部启动请求。在不必同时处理多个请求时,这是最佳选择。开发人员仅需要实现onHandleIntent()方法,该方法接收每次启动请求的Intent 以便完成后台任务。
①首次启动会创建一个Service实例,依次调用onCreate()和onStartCommand()方法,此时Service 进入运行状态,如果再次调用StartService启动Service,将不会再创建新的Service对象, 系统会直接复用前面创建的Service对象,调用它的onStartCommand()方法!
②但这样的Service与它的调用者无必然的联系,就是说当调用者结束了自己的生命周期, 但是只要不调用stopService,那么Service还是会继续运行的!
③无论启动了多少次Service,只需调用一次StopService即可停掉Service
FirstService.java
package com.thundersoft.myblogdemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
/**
* @ClassName FirstService
* @Description TODO
* @Author Yu
* @Date 2022/6/30 17:19
* @Version 1.0
**/
public class FirstService extends Service {
private final static String TAG="Service1";
//必须要实现的方法
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"onBind()被调用");
return null;
}
//Service被启动时调用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"onStartCommand()被调用");
return super.onStartCommand(intent, flags, startId);
}
//Service被创建时调用
@Override
public void onCreate() {
Log.i(TAG,"onCreate()被调用");
super.onCreate();
}
//Service被关闭之前回调
@Override
public void onDestroy() {
Log.i(TAG,"onDestroy()被调用");
super.onDestroy();
}
}
AndroidManifest.xml
<service
android:name=".FirstService"
android:enabled="true"
>
service>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button btn1,btn2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = findViewById(R.id.btn1);
btn2 = findViewById(R.id.btn2);
Intent intent = new Intent(MainActivity.this, FirstService.class);
btn1.setOnClickListener(v->{
startService(intent);
});
btn2.setOnClickListener(v->{
stopService(intent);
});
}
}
这里补充一些知识点
onStartCommand()方法必须返回一个 整数。该值用来描述系统停止服务后如何继续服务onStartCommand()方法返回值必 须是下列常量之一。
START_ NOT STICKY
如果系统在onStartCommand()方法返回后停止服务,不重新创建服务,除非有PendingIntent要发送。为避免不在不需要的时候运行服务,这是最佳选择。
START_ STICKY
如果系统在onStartCommand()方法返回后停止服务,重新创建服务并调用onStartCommand()方法,但是不重新发送最后的Intent;相反,系统使用空Intent调用onStartCommand0方法,除非有PendingIntent来启动服务,此时,这些Intent会被发送。这适合多媒体播放器(或者类似服务),它们不执行命令但是无限期运行并等待工作。
START_REDELIVER_ INTENT
如果系统在onStartCommand0方法返回后停止服务,重新创建服务并使用发送给服务的最后Intent调用onStrtCommand0方法,全部PendingIntent依次发送。这适合积极执行应该立即恢复工作的服务,如下载文件。
说明:这些常 量都定义在Service类中。
①当首次使用bindService绑定一个Service时,系统会实例化一个Service实例,并调用其onCreate()和onBind()方法,然后调用者就可以通过IBinder和Service进行交互了,此后如果再次使用bindService绑定Service,系统不会创建新的Sevice实例,也不会再调用onBind()方法,只会直接把IBinder对象传递给其他后来增加的客户端!
②如果我们解除与服务的绑定,只需调用unbindService(),此时onUnbind和onDestory方法将会被调用!这是一个客户端的情况,假如是多个客户端绑定同一个Service的话,情况如下 当一个客户完成和service之间的互动后,它调用 unbindService() 方法来解除绑定。当所有的客户端都和service解除绑定后,系统会销毁service。(除非service也被startService()方法开启)
③另外,和上面那张情况不同,bindService模式下的Service是与调用者相互关联的,可以理解为 “一条绳子上的蚂蚱”,要死一起死,在bindService后,一旦调用者销毁,那么Service也立即终止!
通过BindService调用Service时调用的Context的bindService的解析 bindService(Intent Service,ServiceConnection conn,int flags)
service:通过该intent指定要启动的Service
conn:ServiceConnection对象,用户监听访问者与Service间的连接情况, 连接成功回调该对象中的onServiceConnected(ComponentName,IBinder)方法; 如果Service所在的宿主由于异常终止或者其他原因终止,导致Service与访问者间断开 连接时调用onServiceDisconnected(CompanentName)方法,主动通过unBindService() 方法断开并不会调用上述方法!
flags:指定绑定时是否自动创建Service(如果Service还未创建), 参数可以是0(不自动创建),BIND_AUTO_CREATE(自动创建)
Context中的bindService方法:
ServiceConnection
对象:监听访问者与Service间的连接情况,如果成功连接,回调 onServiceConnected(),如果异常终止或者其他原因终止导致Service与访问者断开 连接则回调onServiceDisconnected方法,调用unBindService()不会调用该方法!onServiceConnected
方法中有一个IBinder对象,该对象即可实现与被绑定Service 之间的通信!我们再开发Service类时,默认需要实现IBinder onBind()方法,该方法返回的 IBinder对象会传到ServiceConnection对象中的onServiceConnected的参数,我们就可以 在这里通过这个IBinder与Service进行通信!
SecondService.java
package com.thundersoft.myblogdemo;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
/**
* @ClassName SecondService
* @Description TODO
* @Author Yu
* @Date 2022/6/30 18:04
* @Version 1.0
**/
public class SecondService extends Service {
private static final String TAG="Service2";
private boolean quit;
private int count;
//onBind()返回的对象
private MyBinder binder=new MyBinder();
public class MyBinder extends Binder{
SecondService getService(){
return SecondService.this;
}
}
//必须实现的方法,绑定改Service时回调该方法
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"onBind()被调用");
return binder;
}
//Service被创建时回调
@Override
public void onCreate() {
Log.i(TAG,"onCreate()被调用");
new Thread(new Runnable() {
@Override
public void run() {
//当服务还在就一直累加
while (!quit){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}).start();
super.onCreate();
}
//Service断开连接时回调
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind方法被调用!");
return true;
}
//Service被关闭前回调
@Override
public void onDestroy() {
super.onDestroy();
this.quit = true;
Log.i(TAG, "onDestroyed方法被调用!");
}
@Override
public void onRebind(Intent intent) {
Log.i(TAG, "onRebind方法被调用!");
super.onRebind(intent);
}
public int getCount(){
return count;
}
}
AndroidManifest.xml
<service
android:name=".SecondService"
android:enabled="true"
android:exported="true"
>
service>
MainActivity.java
package com.thundersoft.myblogdemo;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private Button btn1,btn2,btn3;
SecondService second_service;
ServiceConnection connection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
SecondService.MyBinder binder=( SecondService.MyBinder) service;
second_service=binder.getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = findViewById(R.id.btn1);
btn2 = findViewById(R.id.btn2);
btn3= findViewById(R.id.btn3);
Intent intent = new Intent(this,SecondService.class);
btn1.setOnClickListener(v->{
bindService(intent,connection, Service.BIND_AUTO_CREATE);
});
btn2.setOnClickListener(v->{
unbindService(connection);
});
btn3.setOnClickListener(v->{
Toast.makeText(this,second_service.getCount()+"",Toast.LENGTH_SHORT).show();
});
}
}
如果Service已经由某个客户端通过StartService()启动,接下来由其他客户端 再调用bindService()绑定到该Service后调用unbindService()解除绑定最后在 调用bindService()绑定到Service的话,此时所触发的生命周期方法如下:
onCreate( )->onStartCommand( )->onBind( )->onUnbind( )->onRebind( )
PS:前提是:onUnbind()方法返回true!!! 这里或许部分读者有疑惑了,调用了unbindService后Service不是应该调用 onDistory()方法么!其实这是因为这个Service是由我们的StartService来启动的 ,所以你调用onUnbind()方法取消绑定,Service也是不会终止的!
得出的结论: 假如我们使用bindService来绑定一个启动的Service,注意是已经启动的Service!!! 系统只是将Service的内部IBinder对象传递给Activity,并不会将Service的生命周期 与Activity绑定,因此调用unBindService( )方法取消绑定时,Service也不会被销毁!
在前面我们提到了可以继承Service或者IntentService来创建并启动自己的Service,前面只对继承Service类的方式做了介绍,下面来详细介绍一下IntentService
既然继承Service类就可以实现自己的Service,为什么还需要IntentService类呢?
事实上,如果我们直接把耗时线程放到Service中的onStart()方法中,很容易 会引起ANR异常(Application Not Responding),虽然可以这样做,但是官方不推荐在Service中进行一些耗时操作。
1.Service不是一个单独的进程,它和它的应用程序在同一个进程中
2.Service不是一个线程,这样就意味着我们应该避免在Service中进行耗时操作
于是,Android给我们提供了解决上述问题的替代品——IntentService; IntentService是继承与Service并处理异步请求的一个类,在IntentService中有 一个工作线程来处理耗时操作,请求的Intent记录会加入队列
IntentService工作流程为:
客户端通过startService(Intent)来启动IntentService; 我们并不需要手动地去控制IntentService,当任务执行完后,IntentService会自动停止; 可以启动IntentService多次,每个耗时操作会以工作队列的方式在IntentService的 onHandleIntent回调方法中执行,并且每次只会执行一个工作线程,执行完一,再到二这样
ThirdService.java
/**
* @ClassName ThirdService
* @Description TODO
* @Author Yu
* @Date 2022/7/1 11:18
* @Version 1.0
**/
public class ThirdService extends IntentService {
private static final String TAG="ThirdService";
//必须实现父类的构造方法
public ThirdService() {
super("ThirdService");
}
//必须重写的核心方法
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String param = intent.getExtras().getString("PARAM");
if (param.equals("s1")) Log.i(TAG,"service1启动");
else if (param.equals("s2")) Log.i(TAG,"service2启动");
else if (param.equals("s3")) Log.i(TAG,"service3启动");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"onBind()方法启动");
return super.onBind(intent);
}
@Override
public void onCreate() {
Log.i(TAG,"onCreate()方法启动");
super.onCreate();
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.i(TAG,"onStartCommand()方法启动");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG,"onDestroy()方法启动");
super.onDestroy();
}
}
AndroidManifest.xml注册下Service
<service
android:name=".ThirdService"
>
service>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//启动多次IntentService,每次启动,都会新建一个工作线程
//但始终只有一个IntentService实例
Intent intent1 = new Intent(this,ThirdService.class);
Bundle bundle1 = new Bundle();
bundle1.putString("PARAM","s1");
intent1.putExtras(bundle1);
startService(intent1);
Intent intent2 = new Intent(this,ThirdService.class);
Bundle bundle2 = new Bundle();
bundle2.putString("PARAM","s2");
intent2.putExtras(bundle2);
startService(intent2);
Intent intent3 = new Intent(this,ThirdService.class);
Bundle bundle3 = new Bundle();
bundle3.putString("PARAM","s3");
intent3.putExtras(bundle3);
startService(intent3);
}
}
这就是实现IntentService类所必须的全部操作:无参构造方法和onHandleIntent()方法。
如果开发人员决定重写其他回调方法,如onCreate()、onStartCommand()或 onDestroy(),需要调用父类实现,这样IntentService能正确处理工作线程的生命周期.
小结:
当一个后台的任务,需要分成几个子任务,然后按先后顺序执行,子任务 (简单的说就是异步操作),此时如果我们还是定义一个普通Service然后 在onStart方法中开辟线程,然后又要去控制线程,这样显得非常的繁琐; 此时应该自定义一个IntentService然后再onHandleIntent()方法中完成相关任务!
我们前面的操作都是通过Activity启动和停止Service,假如我们启动的是一个下载 的后台Service,而我们想知道Service中下载任务的进度!那么这肯定是需要Service 与Activity进行通信的,而他们之间交流的媒介就是Service中的onBind()方法! 返回一个我们自定义的Binder对象!
基本流程如下:
给一个Demo
SecondService.java
/**
* @ClassName SecondService
* @Description TODO
* @Author Yu
* @Date 2022/6/30 18:04
* @Version 1.0
**/
public class SecondService extends Service {
private static final String TAG="Service2";
private boolean quit;
private int count;
//onBind()返回的对象
private MyBinder binder=new MyBinder();
public class MyBinder extends Binder{
//需要暴露的方法
public int getCount(){
return count;
}
}
//必须实现的方法,绑定改Service时回调该方法
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"onBind()被调用");
//这个Binder对象返回
return binder;
}
//Service被创建时回调
@Override
public void onCreate() {
Log.i(TAG,"onCreate()被调用");
new Thread(new Runnable() {
@Override
public void run() {
//当服务还在就一直累加
while (!quit){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}).start();
super.onCreate();
}
//Service断开连接时回调
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind方法被调用!");
return true;
}
//Service被关闭前回调
@Override
public void onDestroy() {
super.onDestroy();
this.quit = true;
Log.i(TAG, "onDestroyed方法被调用!");
}
@Override
public void onRebind(Intent intent) {
Log.i(TAG, "onRebind方法被调用!");
super.onRebind(intent);
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button btn1,btn2,btn3;
SecondService.MyBinder binder;
ServiceConnection connection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//获取Binder对象,然后调用相关方法即可
binder=(SecondService.MyBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = findViewById(R.id.btn1);
btn2 = findViewById(R.id.btn2);
btn3= findViewById(R.id.btn3);
Intent intent = new Intent(this,SecondService.class);
btn1.setOnClickListener(v->{
bindService(intent,connection, Service.BIND_AUTO_CREATE);
});
btn2.setOnClickListener(v->{
unbindService(connection);
});
btn3.setOnClickListener(v->{
//获取Binder对象调用相关方法
Toast.makeText(this,"当前进度:"+binder.getCount(),Toast.LENGTH_SHORT).show();
});
}
}
一般情况下我们都知道Service一般都是运行在后台的,但是Service的系统优先级 还是比较低的,当系统内存不足的时候,就有可能回收正在后台运行的Service, 对于这种情况我们可以使用前台服务,从而让Service稍微没那么容易被系统杀死, 当然还是有可能被杀死的…所谓的前台服务就是状态栏显示的Notification!
我们来实现一个前台服务,在自定义的Service类中,重写onCreate(),然后根据自己的需求定制Notification; 定制完毕后,调用startForeground(1,notification对象)即可
给出相关代码
foreService.java
/**
* @ClassName foreService
* @Description TODO
* @Author Yu
* @Date 2022/7/1 12:41
* @Version 1.0
**/
public class foreService extends Service {
@Override
public void onCreate() {
String CHANNEL_ID = "com.example.fore_service";
String CHANNEL_NAME = "TEST";
NotificationChannel notificationChannel = null;
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(notificationChannel);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);
builder.setContentIntent(PendingIntent.getActivity(this,0,new Intent(this,MainActivity.class),0));
builder.setAutoCancel(false);
builder.setSmallIcon(R.drawable.fore_icon);
builder.setTicker("Foreground Service Start");
builder.setContentTitle("Socket服务端");
builder.setContentText("正在运行...");
startForeground(1, builder.build());
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button btn1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = findViewById(R.id.btn1);
Intent intent = new Intent(this,foreService.class);
btn1.setOnClickListener(v->{
startService(intent);
//bindService(intent,connection, Service.BIND_AUTO_CREATE);
});
}
}
AndroidManifest.xml
API28 以上需要在清单中申请创建前台服务的权限
<service
android:name=".foreService"
>
service>
application>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
fore_icon.xml
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="1024"
android:viewportWidth="1024"
android:width="200px"
android:height="200px"
>
<path
android:pathData="M512 412.116m-215.393 0a215.393 215.393 0 1 0 430.786 0 215.393 215.393 0 1 0-430.786 0Z"
android:fillColor="#1296db"
/>
<path
android:pathData="M678.914 607.572c-39.498 48.371-99.595 79.396-166.914 79.396s-127.416-31.094-166.914-79.464C220.987 649.516 135.66 756 135.66 857c0 92 752.68 92 752.68 0 0-101-85.327-207.416-209.426-249.428zM808.888 325.339C773.655 194.214 653.914 97.904 512.001 97.904c-141.821 0-261.498 96.183-296.818 227.175-32.929 6.717-58.723 39.877-58.723 79.802 0 44.772 32.429 80.908 70.991 80.908 19.211 0 28.849-39.789 28.918-80.789h0.801c0-140 114.317-254.672 254.832-254.672 137.2 0 249.403 109.35 254.62 245.04-1.616 43.421 7.959 90.541 28.739 90.541 38.562 0 70.991-36.275 70.991-81.047-0.001-39.417-25.139-72.229-57.464-79.523z"
android:fillColor="#1296db"
/>
vector>
除了上述的前台服务外,实际开发中Service还有一种常见的用法,就是执行定时任务, 比如轮询,就是每间隔一段时间就请求一次服务器,确认客户端状态或者进行信息更新等(比较容易想到的业务是社交媒体APP在后台会定时与服务器通信)!而Android中给我们提供的定时方式有两种使用Timer类与Alarm机制!
那么这两种有什么区别呢?
前者不适合于需要长期在后台运行的定时任务,CPU一旦休眠,Timer中的定时任务 就无法运行;Alarm则不存在这种情况,他具有唤醒CPU的功能,另外,也要区分CPU 唤醒与屏幕唤醒!
所以大部分业务场景下,还是Alarm使用较多,使用流程如下:
Step 1:获得Service: AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
Step 2:通过set方法设置定时任务 int anHour = 2 * 1000; long triggerAtTime = SystemClock.elapsedRealtime() + anHour; manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,pendingIntent);
Step 3:定义一个Service 在onStartCommand中开辟一条事务线程,用于处理一些定时逻辑
Step 4:定义一个Broadcast(广播),用于启动Service 最后别忘了,在AndroidManifest.xml中对这Service与Boradcast进行注册!
set(int type,long startTime,PendingIntent pi)
方法的参数说明
AlarmManager.ELAPSED_REALTIME
: 闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;AlarmManager.ELAPSED_REALTIME_WAKEUP
闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;AlarmManager.RTC
闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;AlarmManager.RTC_WAKEUP
表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;AlarmManager.POWER_OFF_WAKEUP
表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一, 该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;第一个参数决定第二个参数的类型,如果是REALTIME的话就用: SystemClock.elapsedRealtime( )方法可以获得系统开机到现在经历的毫秒数 如果是RTC的就用:System.currentTimeMillis()可获得从1970.1.1 0点到 现在做经历的毫秒数
startTime:闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。 需要注意的是,本属性与第一个属性(type)密切相关,如果第一个参数对应的闹钟 使用的是相对时间(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP)
,那么本属 性就得使用相对时间(相对于系统启动时间来说),比如当前时间就表示为: SystemClock.elapsedRealtime()
;如果第一个参数对应的闹钟使用的是绝对时间 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP)
,那么本属性就得使用绝对时间, 比如当前时间就表示为:System.currentTimeMillis()
。
PendingIntent: 绑定了闹钟的执行动作,比如发送一个广播、给出提示等等。PendingIntent 是Intent的封装类。
如果是通过启动服务来实现闹钟提示的话, PendingIntent对象的获取就应该采用PendingIntent.getService(Context context, int requestCode, Intent intent, int flags)
方法;
如果是通过广播来实现闹钟提示的话, PendingIntent对象的获取就应该采用 PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)
方法;
如果是采用Activity的方式来实现闹钟提示的话,PendingIntent对象的获取 就应该采用 PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
方法。
intent就是需要启动的Activity、Service、BroadCastReceiver的intent。
Flags的类型:
FLAG_ONE_SHOT:得到的pi只能使用一次,第二次使用该pi时报错
FLAG_NO_CREATE: 当pi不存在时,不创建,返回null
FLAG_CANCEL_CURRENT: 每次都创建一个新的pi
FLAG_UPDATE_CURRENT: 不存在时就创建,创建好了以后就一直用它,每次使用时都会更新pi的数据(使用较多)
从4.4版本后(API 19),Alarm任务的触发时间可能变得不准确,有可能会延时,是系统 对于耗电性的优化,如果需要准确无误可以调用setExtra()方法
ALongTimeRunningService.java
/**
* @ClassName ALongTimeRunningService
* @Description 轮询服务,保持长时间通信
* @Author Yu
* @Date 2022/7/1 15:33
* @Version 1.0
**/
public class ALongTimeRunningService extends Service {
private int count=0;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
//打印服务执行的时间日志
Log.i("ServiceRunTime", new Date().toString());
count++;//累加
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int point_time =2*1000;//间隔时长
long times = SystemClock.elapsedRealtime() + point_time;//每次执行时间
Intent intent1 = new Intent(this,AlarmReceiver.class);
//每次通过广播发送一条数据
Bundle bundle = new Bundle();
bundle.putInt("num",count);
intent1.putExtras(bundle);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent1, PendingIntent.FLAG_UPDATE_CURRENT);
manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,times,pendingIntent);
return super.onStartCommand(intent, flags, startId);
}
}
AlarmReceiver.java
/**
* @ClassName AlarmReceiver
* @Description 接到广播后,再次执行服务
* @Author Yu
* @Date 2022/7/1 15:39
* @Version 1.0
**/
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent intent1 = new Intent(context, ALongTimeRunningService.class);
//接收长连接服务传来的广播数据
Bundle bundle = intent.getExtras();
int num = bundle.getInt("num");
Log.i("当前数据:",num+"");
context.startService(intent1);
}
}
AndroidManifest.xml
<service android:name=".ALongTimeRunningService">
service>
<receiver android:name=".AlarmReceiver">
receiver>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button btn1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = findViewById(R.id.btn1);
Intent intent = new Intent(this,ALongTimeRunningService.class);
btn1.setOnClickListener(v->{
startService(intent);
//bindService(intent,connection, Service.BIND_AUTO_CREATE);
});
}
}
首先来了解一下它们的概念(官方定义)
IBinder是远程对象的基本接口,是为了高性能而设计的轻量级远程调用机制(RPC)的核心部分。但它不仅用于远程调用,也用于进程内调用。该接口定义了与远程对象间交互的协议。但不要直接实现 这个接口,而是继承(extends)Binder。
IBinder主要的API是transact(),与之对应的API是Binder.onTransact()。通过前者,你能 想远程IBinder对象发送发出调用,后者使你的远程对象能够响应接收到的调用。IBinder的API都是 Syncronous(同步)执行的,比如transact()直到对方的Binder.onTransact()方法调用完后才返回。 调用发生在进程内时无疑是这样的,而在进程间时,在IPC的帮助下,也是同样的效果。
通过transact()发送的数据是Parcel,Parcel是一种一般的缓冲区,除了有数据外还带有 一些描述它内容的元数据。元数据用于管理IBinder对象的引用,这样就能在缓冲区从一个进程移动 到另一个进程时保存这些引用。这样就保证了当一个IBinder被写入到Parcel并发送到另一个进程中, 如果另一个进程把同一个IBinder的引用回发到原来的进程,那么这个原来的进程就能接收到发出的 那个IBinder的引用。这种机制使IBinder和Binder像唯一标志符那样在进程间管理。
系统为每个进程维护一个存放交互线程的线程池。这些交互线程用于派送所有从另外进程发来的IPC 调用。例如:当一个IPC从进程A发到进程B,A中那个发出调用的线程(这个应该不在线程池中)就阻塞 在transact()中了。进程B中的交互线程池中的一个线程接收了这个调用,它调用 Binder.onTransact(),完成后用一个Parcel来做为结果返回。然后进程A中的那个等待的线程在 收到返回的Parcel后得以继续执行。实际上,另一个进程看起来就像是当前进程的一个线程, 但不是当前进程创建的。
Binder机制还支持进程间的递归调用。例如,进程A执行自己的IBinder的transact()调用进程B 的Binder,而进程B在其Binder.onTransact()中又用transact()向进程A发起调用,那么进程A 在等待它发出的调用返回的同时,还会用Binder.onTransact()响应进程B的transact()。 总之Binder造成的结果就是让我们感觉到跨进程的调用与进程内的调用没什么区别。
当操作远程对象时,你经常需要查看它们是否有效,有三种方法可以使用:
transact()
方法将在IBinder所在的进程不存在时抛出RemoteException异常。- 如果目标进程不存在,那么调用
pingBinder()
时返回false。- 可以用
linkToDeath()
方法向IBinder注册一个IBinder.DeathRecipient, 在IBinder代表的进程退出时被调用。
小结:IBinder是Android给我们提供的一个进程间通信的一个接口,而我们一般是不直接实现这个接口的, 而是通过继承Binder类来实现进程间通信!是Android中实现IPC(进程间通信)的一种方式
Android中的Binder机制由一系列系统组件构成: Client、Server、Service Manager和Binder驱动程序
Binder的调用流程如下:
- Client调用某个代理接口中的方法时,代理接口的方法会将Client传递的参数打包成Parcel对象;
- 然后代理接口把该Parcel对象发送给内核中的Binder driver;;
- 然后Server会读取Binder Driver中的请求数据,假如是发送给自己的,解包Parcel对象, 处理并将结果返回;
代理接口中的定义的方法和Server中定义的方法是一一对应的, 另外,整个调用过程是一个同步的,即Server在处理时,Client会被Block(锁)住! 而这里说的代理接口的定义就是等下要说的AIDL(Android接口描述语言)
可靠性
:在移动设备上,通常采用基于Client-Server的通信方式来实现互联网与设备间的内部通信。目前linux支持IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket中只有socket支持Client-Server的通信方式。Android系统为开发者提供了丰富进程间通信的功能接口,媒体播放,传感器,无线传输。这些功能都由不同的server来管理。开发都只关心将自己应用程序的client与server的通信建立起来便可以使用这个服务。毫无疑问,如若在底层架设一套协议来实现Client-Server通信,增加了系统的复杂性。在资源有限的手机 上来实现这种复杂的环境,可靠性难以保证。传输性能
:socket主要用于跨网络的进程间通信和本机上进程间的通信,但传输效率低,开销大。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的一块缓存区中,然后从内核缓存区拷贝到接收方缓存区,其过程至少有两次拷贝。虽然共享内存无需拷贝,但控制复杂。比较各种IPC方式的数据拷贝次数。共享内存:0次。Binder:1次。Socket/管道/消息队列:2次。安全性
:Android是一个开放式的平台,所以确保应用程序安全是很重要的。Android对每一个安装应用都分配了UID/PID,其中进程的UID是可用来鉴别进程身份。传统的只能由用户在数据包里填写UID/PID,这样不可靠,容易被恶意程序利用。而我们要求由内核来添加可靠的UID。 所以,出于可靠性、传输性、安全性。android建立了一套新的进程间通信方式。 ——摘自:Android中的Binder机制的简要理解
总而言之,Binder机制给我们带来的最直接的好处就是: 我们无需关心底层如何实现,只需按照AIDL的规则,自定义一个接口文件, 然后调用调用接口中的方法,就可以完成两个进程间的通信了
前面我们讲到IPC这个名词,他的全名叫做:跨进程通信(interprocess communication), 因为在Android系统中,每个应用程序都运行在自己的进程中,进程之间一般是无法直接进行数据交换的, 而为了实现跨进程,Android给我们提供了上面说的Binder机制,而这个机制使用的接口语言就是: AIDL(Android Interface Definition Language),他的语法很简单,而这种接口语言并非真正的编程 语言,只是定义两个进程间的通信接口而已!而生成符合通信协议的Java代码则是由Android SDK的 platform-tools目录下的aidl.exe工具生成,生成对应的接口文件在:gen目录下,一般是:Xxx.java的接口! 而在该接口中包含一个Stub的内部类,该类中实现了在该类中实现了IBinder接口与自定义的通信接口, 这个类将会作为远程Service的回调类——实现了IBinder接口,所以可作为Service的onBind( )方法的返回值
AIDL注意事项:
- 接口名词需要与aidl文件名相同
- 接口和方法前面不要加访问权限修饰符:public ,private,protected等,也不能用static final!
- AIDL默认支持的类型包括Java基本类型,String,List,Map,CharSequence,除此之外的其他类型都 需要import声明,对于使用自定义类型作为参数或者返回值,自定义类型需要实现Parcelable接口, 详情请看后面的传递复杂数据类型
- 自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义 的包在同一个包中。
AS下创建AIDL需要在main目录下新建一个aidl文件夹,AIDL文件所在的路径(包名)要跟项目的包名保持一致,最后创建一个aidl文件,接着按ctrl + f9(Build->Make Project)重新编译,就可以了
编译后输出
aidldemo用作服务端演示项目
在服务端
step1:创建AIDL文件,内容如下
IPerson.aidl
// IPerson.aidl
package com.thundersoft.aidldemo;
// Declare any non-default types here with import statements
interface IPerson {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
String queryPerson(int num);
}
编译生成的IPerson.java(生成的文件不允许修改)
IPerson.java
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.thundersoft.aidldemo;
// Declare any non-default types here with import statements
public interface IPerson extends android.os.IInterface
{
/** Default implementation for IPerson. */
public static class Default implements com.thundersoft.aidldemo.IPerson
{
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public java.lang.String queryPerson(int num) throws android.os.RemoteException
{
return null;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.thundersoft.aidldemo.IPerson
{
private static final java.lang.String DESCRIPTOR = "com.thundersoft.aidldemo.IPerson";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.thundersoft.aidldemo.IPerson interface,
* generating a proxy if needed.
*/
public static com.thundersoft.aidldemo.IPerson asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.thundersoft.aidldemo.IPerson))) {
return ((com.thundersoft.aidldemo.IPerson)iin);
}
return new com.thundersoft.aidldemo.IPerson.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_queryPerson:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.queryPerson(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.thundersoft.aidldemo.IPerson
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public java.lang.String queryPerson(int num) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(num);
boolean _status = mRemote.transact(Stub.TRANSACTION_queryPerson, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().queryPerson(num);
}
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.thundersoft.aidldemo.IPerson sDefaultImpl;
}
static final int TRANSACTION_queryPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.thundersoft.aidldemo.IPerson impl) {
// Only one user of this interface can use this function
// at a time. This is a heuristic to detect if two different
// users in the same process use this function.
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.thundersoft.aidldemo.IPerson getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public java.lang.String queryPerson(int num) throws android.os.RemoteException;
}
这里我们关注的只是asInterface(IBinder)
和我们定义的接口中的queryPerson()
方法!
该方法会把IBinder类型的对象转换成IPerson类型的,必要时生成一个代理对象返回结果
step2:自定义我们的Service类,主要完成以下任务:
继承Service类,同时也自定义了一个PersonQueryBinder类用来继承IPerson.Stub类 就是实现了IPerson接口和IBinder接口
实例化自定义的Stub类,并重写Service的onBind方法,返回一个binder对象
AIDLService.java
package com.thundersoft.aidldemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
/**
* @ClassName AIDLService
* @Description
* @Author Yu
* @Date 2022/7/2 13:00
* @Version 1.0
**/
public class AIDLService extends Service {
private IBinder binder=new PersonQueryBinder();
private final String [] data={"数据一","数据二","数据三","数据四","数据五","数据六"};
private String query(int num){
if (num>0&&num<7){
return data[num-1];
}
return null;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
private final class PersonQueryBinder extends IPerson.Stub {
@Override
public String queryPerson(int num) throws RemoteException {
Log.i("Service",num+"");
return query(num);
}
}
}
step3: 在AndroidManifest.xml文件中注册Service
<service android:name=".AIDLService"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.AIDLService"/>
<category android:name="android.intent.category.DEFAULT"/>
intent-filter>
service>
客户端
把服务端的aidl文件复制过来,然后直接在MainActivity中完成剩余操作,和绑定本地Service的操作
有点类似
bindService(service,conn,BIND_AUTO_CREATE);
iPerson = IPerson.Stub.asInterface(service);
MainActivity.java
package com.thundersoft.aidlclient;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.thundersoft.aidldemo.IPerson;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button btn;
private TextView resulttv;
private EditText tosearch;
private IPerson iPerson;
private PersonConnection connection=new PersonConnection();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createViews();
Intent service = new Intent("android.intent.action.AIDLService");
service.setPackage("com.thundersoft.aidldemo");
//远程绑定服务
bindService(service,connection,BIND_AUTO_CREATE);
btn.setOnClickListener(this);
}
//绑定组件
private void createViews() {
btn=findViewById(R.id.btn_search);
resulttv=findViewById(R.id.result_tv);
tosearch=findViewById(R.id.ed);
}
@Override
public void onClick(View v) {
Integer num = Integer.valueOf(tosearch.getText().toString());
try {
String result = iPerson.queryPerson(num);
resulttv.setText(result);
} catch (RemoteException e) {
e.printStackTrace();
}
tosearch.setText(num+"");
}
private final class PersonConnection implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iPerson = IPerson.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iPerson=null;
}
}
}
上面的例子我们传递的只是要给int类型的参数,然后服务端返回一个String类型的参数,看似满足 我们的基本需求,不过实际开发中,我们可能需要考虑传递复杂数据类型的情况!下面我们来学习下 如何向服务端传递复杂数据类型的数据!开始之前我们先来了解Parcelable接口
Parcelable接口简介:
相信用过序列化的基本上都知道这个接口了,除了他还有另外一个Serializable,同样是用于序列化的, 只是Parcelable更加轻量级,速度更快!但是写起来就有点麻烦了,当然如果你用的as的话可以用 的插件来完成序列化,比如:Android Parcelable Code Generator 当然,这里我们还是手动实现这个接口
首先需要实现:writeToParcel和readFromPacel方法 写入方法将对象写入到包裹(parcel)中,而读取方法则从包裹中读取对象, 请注意,写入属性顺序需与读取顺序相同
接着需要在:该类中添加一个名为CREATOR的static final属性 改属性需要实现:android.os.Parcelable.Creator接口
再接着需要从写接口中的两个方法: createFromParcel(Parcel source)方法:实现从source创建出JavaBean实例的功能 newArray(int size):创建一个类型为T,长度为size的数组,只有一个简单的return new T[size]; (这里的T是Person类)
最后,describeContents():这个我也不知道是拿来干嘛的,直接返回0即可
step1:创建两个实体类Person
和Salary
,这两个类都实现Parcelable接口,便于传输。
Person.java
package com.thundersoft.complexaidl;
import android.os.Parcel;
import android.os.Parcelable;
/**
* @ClassName Person
* @Description TODO
* @Author Yu
* @Date 2022/7/2 16:03
* @Version 1.0
**/
public class Person implements Parcelable {
private Integer ID;
private String Name;
public Person() {
}
public Person(Integer ID, String name) {
this.ID = ID;
Name = name;
}
public Integer getID() {
return ID;
}
public void setID(Integer ID) {
this.ID = ID;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
//因为我们集合取出元素的时候是根据Person对象来取得,所以比较麻烦,
//需要我们重写hashCode()和equals()方法
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((Name == null) ? 0 : Name.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (Name == null)
{
if (other.Name != null)
return false;
}
else if (!Name.equals(other.Name))
return false;
return true;
}
//必须提供一个名为CREATOR的static final属性 该属性需要实现
//android.os.Parcelable.Creator接口
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
//从Parcel中读取数据,返回Person对象
@Override
public Person createFromParcel(Parcel source) {
return new Person(source.readInt(),source.readString());
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
//把对象所包含的数据写入到parcel中
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ID);
dest.writeString(Name);
}
}
Salary.java
package com.thundersoft.complexaidl;
import android.os.Parcel;
import android.os.Parcelable;
/**
* @ClassName Salary
* @Description TODO
* @Author Yu
* @Date 2022/7/2 16:43
* @Version 1.0
**/
public class Salary implements Parcelable {
private String occ;
private Integer salary;
public Salary() {
}
public Salary(String occ, Integer salary) {
this.occ = occ;
this.salary = salary;
}
public String getOcc() {
return occ;
}
public void setOcc(String occ) {
this.occ = occ;
}
public Integer getSalary() {
return salary;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(occ);
dest.writeInt(salary);
}
public static final Parcelable.Creator<Salary> CREATOR=new Parcelable.Creator<Salary>(){
@Override
public Salary createFromParcel(Parcel source) {
return new Salary(source.readString(),source.readInt());
}
@Override
public Salary[] newArray(int size) {
return new Salary[size];
}
};
@Override
public String toString() {
return "职业:"+occ+" "+"薪资"+salary;
}
}
step2:编写对应的AIDL文件
AIDL文件一般有两种类型,一种只包含一个序列化对象 供其他的AIDL文件使用;另一种包含定义的各种方法接口
只包含序列化对象的AIDL文件
// Person.aidl
package com.thundersoft.complexaidl;
// Declare any non-default types here with import statements
parcelable Person;
// Salary.aidl
package com.thundersoft.complexaidl;
// Declare any non-default types here with import statements
parcelable Salary;
包含接口的AIDL文件
// ISalary.aidl
package com.thundersoft.complexaidl;
import com.thundersoft.complexaidl.Person;
import com.thundersoft.complexaidl.Salary;
// Declare any non-default types here with import statements
interface ISalary {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
Salary getInfo(in Person p);
}
如果使用的是自定义的数据类型的话,需要import
step3:编写服务端:定义一个SalaryBinder类继承Stub,从而实现ISalary和IBinder接口;定义一个存储信息的Map集合! 重新onBind方法,返回SalaryBinder类的对象实例
AIDLService.java
package com.thundersoft.complexaidl.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.Nullable;
import com.thundersoft.complexaidl.ISalary;
import com.thundersoft.complexaidl.Person;
import com.thundersoft.complexaidl.Salary;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName AIDLService
* @Description TODO
* @Author Yu
* @Date 2022/7/2 17:01
* @Version 1.0
**/
public class AIDLService extends Service {
private static Map<Person, Salary> map=new HashMap<>();
private SalaryBinder binder;
static {
map.put(new Person(1001,"Ming"),new Salary("前台",6007));
map.put(new Person(1002,"Ling"),new Salary("程序员",15000));
map.put(new Person(1003,"Han"),new Salary("摄影师",10000));
}
@Override
public void onCreate() {
Log.i("Service","服务启动!");
super.onCreate();
binder=new SalaryBinder();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
private class SalaryBinder extends ISalary.Stub{
@Override
public Salary getInfo(com.thundersoft.complexaidl.Person p) throws RemoteException {
Log.i("客户端传来的数据",p.getName()+","+p.getID());
return map.get(p);
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("Service","服务结束!");
}
}
在AndroidManifest.xml中注册Service
<service android:name=".service.AIDLService"
android:exported="true">
<intent-filter>
<action android:name="com.thundersoft.aidl" />
<category android:name="android.intent.category.DEFAULT" />
intent-filter>
service>
step4:客户端编写
首先把服务端项目中的AIDL文件拷贝到客户端项目下,这里使用AndroidStudio有个大坑,因为要保证AIDL文件以及其对应的java文件所在的包名是一致的,于是在很多老文章里都告诉你把这些文件都放到aidl包下的同一个包里,实际上这也没什么问题,而且方便随时移动文件。但是在 Android Studio 里并不是这样。如果这样做的话,系统根本就找不到 xxx.java 文件,从而在其他的AIDL文件里面使用 xxx 对象的时候会报 Symbol not found 的错误。为什么会这样呢?因为 Gradle 。大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度——问题就出在这里。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。
解决方法也很简单,详情参考AIDL
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
客户端实现: 定义一个ServciceConnection对象,重写对应方法,和前面的普通数据的类似 再接着在bindService,然后再Button的点击事件中获取Salary对象并显示出来
MainActivity.java
package com.thundersoft.complexaidlclient;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.thundersoft.complexaidl.ISalary;
import com.thundersoft.complexaidl.Person;
import com.thundersoft.complexaidl.Salary;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button btn;
private TextView resulttv;
private EditText tosearch;
private ISalary iSalary;
private ServiceConnection connection =new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(getLocalClassName(), "服务已连接");
iSalary = ISalary.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iSalary=null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createViews();
Intent service = new Intent("com.thundersoft.aidl");
service.setPackage("com.thundersoft.complexaidl");
//远程绑定服务
bindService(service,connection,BIND_AUTO_CREATE);
btn.setOnClickListener(this);
}
//绑定组件
private void createViews() {
btn=findViewById(R.id.btn_search);
resulttv=findViewById(R.id.result_tv);
tosearch=findViewById(R.id.ed);
}
@Override
public void onClick(View v) {
String name = tosearch.getText().toString();
try {
Salary info = iSalary.getInfo(new Person(1001, name));
resulttv.setText(name+":"+info.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
上面讲过Android可以通过Binder的onTrensact方法来完成通信,我们来通过一个demo来实现一下
服务端
IPCService.java
package com.thundersoft.ipcdemo.ipcserver;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* @ClassName IPCService
* @Description TODO
* @Author Yu
* @Date 2022/7/3 12:48
* @Version 1.0
**/
public class IPCService extends Service {
private static final String DESCRIPTOR="IPCService";
private final String [] datas={"数据一","数据二","数据三","数据四","数据五","数据六"};
private MyBinder binder=new MyBinder();
public class MyBinder extends Binder{
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
switch (code){
case 0x001:
data.enforceInterface(DESCRIPTOR);
int num = data.readInt();
reply.writeNoException();
reply.writeString(datas[num]);
return true;
}
return super.onTransact(code, data, reply, flags);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
AndroidManifest.xml
<service android:name=".ipcserver.IPCService"
android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.IPCService"/>
<category android:name="android.intent.category.DEFAULT"/>
intent-filter>
service>
客户端
MainActivity.java
package com.thundersoft.ipcdemoclient;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button btn;
private TextView resulttv;
private EditText tosearch;
private IBinder binder;
private ServiceConnection connection=new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binder=service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
binder=null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createViews();
Intent service = new Intent("android.intent.action.IPCService");
service.setPackage("com.thundersoft.ipcdemo");
//远程绑定服务
bindService(service,connection,BIND_AUTO_CREATE);
btn.setOnClickListener(this);
}
//绑定组件
private void createViews() {
btn=findViewById(R.id.btn_search);
resulttv=findViewById(R.id.result_tv);
tosearch=findViewById(R.id.ed);
}
@Override
public void onClick(View v) {
Integer num = Integer.valueOf(tosearch.getText().toString());
if (binder == null)
{
Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show();
} else {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
String _result = null;
try{
_data.writeInterfaceToken("IPCService");
_data.writeInt(num);
binder.transact(0x001, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
resulttv.setText(_result);
}catch (RemoteException e)
{
e.printStackTrace();
} finally
{
_reply.recycle();
_data.recycle();
}
}
}
}
如果你的targetSdkVersion在30及以上,按照上述的步骤创建项目后,客户端是拉不起来服务端的,因为在高版本上面,原来的那套逻辑,谷歌单独做了处理,真tm坑。
那么怎么解决呢?
android {
compileSdk 32
defaultConfig {
applicationId "com.ts.timerservice"
minSdk 21
targetSdk 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
<queries>
<package android:name="你的服务所在的包"/>
queries>