关于服务的详细博客请看这里:
https://blog.csdn.net/javazejian/article/details/52709857
以下是自己的笔记,记录的有点乱。。。
目录
android的四大组件都运行在主线程中
意图是四大组件的纽带,四大组件里都可以设置intent-filter
start方式开启服务:
使用Service的步骤:
1.定义一个类继承Service
2.在Manifest.xml
文件中配置该Service
3.使用Context的startService(Intent)
方法启动该Service
4.不再使用时,调用stopService(Intent)
方法或者stopSelf()方法停止该服务,如果不调用,服务会一直处于运行状态,可以在设置手动停止
“设置”---“开发人员选项”---“正在运行的服务”即可看到
start方式开启服务和开启activity类似
第一次点击按钮开启服务,服务执行onCreate()方法和onStartCommand()方法。
第二次及之后多次点击按钮再次开启服务,服务执行onStartCommand()方法。
start方式开启服务的缺陷:
缺陷: 我们不可以调用服务的方法, 不可以与服务进行通信.
使用这种start方式启动的Service的生命周期如下:
onCreate()
--->onStartCommand()
---> onDestory()
比如onResume()与显示界面有关,而服务没有界面,所以与界面有关的方法服务都没有。
电话监听器:
MainActivity.java
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List<String> list = new ArrayList<>();
private String[] permissions;
private MyMediaRecorder myMediaRecorder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 点击按钮开启服务
public void click(View view) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
list.add(Manifest.permission.READ_PHONE_STATE);
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
list.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
list.add(Manifest.permission.RECORD_AUDIO);
}
if (!list.isEmpty()) {
permissions = list.toArray(new String[list.size()]);
ActivityCompat.requestPermissions(this, permissions, 1);
} else {
startListen();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
int len = grantResults.length;
if (len > 0) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
for (int i = 0; i < len; ++i) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {
showResult(permissions[i]);
return ;
}
}
}
}
startListen();
} else {
Toast.makeText(this, "you denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
private void showResult(final String permission) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("警告");
builder.setCancelable(false);
builder.setMessage("这个权限涉及功能的使用,如拒绝需要手动在设置打开");
builder.setPositiveButton("授权", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission}, 1);
}
});
builder.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
}
private void startListen() {
Intent intent = new Intent(this, PhoneService.class);
startService(intent);
}
public void click1(View view) {
myMediaRecorder = new MyMediaRecorder();
myMediaRecorder.initMediaRecorder();
myMediaRecorder.mediaStart();
}
public void onclick2(View view) {
myMediaRecorder.mediaStop();
}
}
PhoneService.java
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
public class PhoneService extends Service {
private MyMediaRecorder myMediaRecorder;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
// 获取telephonemanager的实例
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
// 注册电话的监听
tm.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE);
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
// 定义一个类来监听电话的状态
private class MyPhoneStateListener extends PhoneStateListener {
private static final String TAG = "MyPhoneStateListener";
private boolean isStart = false;
// 当电话设置状态发生改变的时候调用
@Override
public void onCallStateChanged(int state, String phoneNumber) {
// 具体判断一下电话的状态
switch (state) {
case TelephonyManager.CALL_STATE_IDLE: // 空闲状态
if (myMediaRecorder == null) {
myMediaRecorder = new MyMediaRecorder();
}
if (isStart) {
myMediaRecorder.mediaStop();
isStart = false;
}
Log.d(TAG, "已空闲");
break;
case TelephonyManager.CALL_STATE_OFFHOOK: // 接听状态
Log.d(TAG, "开始录音");
myMediaRecorder.mediaStart();
isStart = true;
break;
case TelephonyManager.CALL_STATE_RINGING: // 电话响铃状态
Log.d(TAG, "准备录音");
myMediaRecorder.initMediaRecorder();
break;
}
}
}
}
MyMediaRecorder.java
import android.media.MediaRecorder;
import android.util.Log;
import java.io.IOException;
public class MyMediaRecorder {
private MediaRecorder recorder = null;
private static final String TAG = "MediaRecorderUtils";
void mediaStop() {
if (recorder != null) {
Log.d(TAG, "录音结束");
recorder.stop();
}
}
void mediaStart() {
if (recorder != null) {
Log.d(TAG, "开始录音: ");
recorder.start();
}
}
void initMediaRecorder() {
Log.d(TAG, "录音初始化");
// 创建MediaRecorder实例
recorder = new MediaRecorder();
// 设置音频的来源
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 设置输出的格式
recorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
// 设置音频的编码方式
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
// 设置存放的路径
recorder.setOutputFile("/mnt/sdcard/yuyin.mp3");
// 准备录音
try {
recorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="click"
android:text="开启服务" />
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn1"
android:onClick="click1"
android:text="开始录音" />
<Button
android:id="@+id/btn3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn2"
android:onClick="onclick2"
android:text="结束录音" />
</RelativeLayout>
华为荣耀v9真机和模拟器均测试成功,可以保存出来播放:
关于录音之后无法播放的问题,发现是音频编码问题,看这里https://blog.csdn.net/gh8609123/article/details/52963022
动态注册服务:
MainActivity.java
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ScreenReceiver receiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, ScreenService.class);
startService(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
}
ScreenService.java
import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
public class ScreenService extends Service {
private static final String TAG = "ScreenService";
private ScreenReceiver receiver;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
// 创建ScreenReceiver实例
receiver = new ScreenReceiver();
// 获取IntentFilter实例目的是添加action
IntentFilter intentFilter = new IntentFilter();
// 添加action
intentFilter.addAction("android.intent.action.SCREEN_ON");
intentFilter.addAction("android.intent.action.SCREEN_OFF");
// 动态注册广播接收者
registerReceiver(receiver, intentFilter);
Log.d(TAG, "=============onCreate: ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "===============onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
// 当服务销毁的时候,取消注册接收广播接收者
unregisterReceiver(receiver);
Log.d(TAG, "=================onDestroy: ");
}
}
ScreenReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class ScreenReceiver extends BroadcastReceiver {
private static final String TAG = "ScreenReceiver";
@Override
public void onReceive(Context context, Intent intent) {
// 获取当前事件类型
String action = intent.getAction();
if ("android.intent.action.SCREEN_ON".equals(action)){
Log.d(TAG, "屏幕解锁了");
} else if ("android.intent.action.SCREEN_OFF".equals(action)){
Log.d(TAG, "屏幕锁屏了");
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.dynamicregistrationservices">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".ScreenReceiver"></receiver>
<service android:name=".ScreenService" />
</application>
</manifest>
即使这个程序后退退出了,屏幕的锁屏解锁打印仍然有效,因为在服务里注册的广播接收者并没有取消,也就是说ScreenService里面的onDestroy并没有执行。只有在设置里查看正在运行的服务,手动停止运行,才会看到====onDestroy打印,也就是在服务里取消了广播接收器。
可以对比一下动态注册广播接收者:
IntentService的使用:
刚开始我们讲的服务都是默认运行在主线程中的,如果直接在服务里去处理一些耗时的操作,就很容易出现ANR(Application Not Responding)的情况。所以这个时候就需要用到Android多线程编程的技术了,我们应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理一些耗时的操作。因此一个比较标准的服务就可以写成如下的形式。
MainActivity.java
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View view) {
Intent intent = new Intent(this, MyService.class);
startService(intent);
}
}
MyService.java
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
public class MyService extends Service {
private static final String TAG = "MyService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread() {
@Override
public void run() {
// 处理具体的逻辑
Log.d(TAG, "onStartCommand里面的子线程");
stopSelf();// 或者stopService(),有人可能遗忘这句话
}
}.start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "MyService的服务成功销毁");
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:adnroid="http://schemas.android.com/apk/res/android"
adnroid:layout_width="match_parent"
adnroid:layout_height="match_parent">
<Button
adnroid:onClick="click"
adnroid:layout_width="wrap_content"
adnroid:layout_height="wrap_content"
adnroid:text="开启服务"/>
</LinearLayout>
清单文件添加:
<service android:name=".MyService" />
点击按钮运行结果:
可能有人会忘记开启线程,或者忘记调用stopSelf()或stopService()导致服务不结束。
为了简单的创建一个异步的、会自动停止的服务,android专门一共了一个IntentService类。
MyIntentService.java
import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.util.Log;
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
*/
public MyIntentService() {
super("MyIntentService"); // 必须调用父类的有参构造方法
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.d(TAG, "Thread id is " + Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
}
批注:
这里首先要提供一个无参的构造方法,并且必须在其内部调用父类的有参构造方法。然后要在子类中实现onHandleIntent()这个抽象方法,在这个方法中处理一些具体的逻辑,而且不用担心ANR的问题,因为这个方法已经是在子线程中运行的了。为了证明在子线程,我们在onHandleIntent()中打印当前线程的id,并将其与主线程id比较。另外根据IntentService的特性,这个服务在运行结束后是自动停止的,为了证明,我们重写onDestroy()方法,打印观察服务是否停止。
MainActivity.java
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View view) {
Intent intent = new Intent(this, MyService.class);
startService(intent);
}
public void click1(View view) {
Log.d(TAG, "Thread id is " + Thread.currentThread().getId());
Intent intent = new Intent(this, MyIntentService.class);
startService(intent);
}
}
清单文件添加如下:
<service android:name=".MyIntentService" />
点击按钮运行结果如下:
bindService方式开启服务:
1.第一次点击按钮,会执行服务的onCreate()方法和onBind()方法
2.当onBind方法返回为null时,onServiceConnected方法是不执行的
3.第二次点击按钮,服务没有做出任何改变
4.服务不可以多次解绑,否则报异常
5.onBind只能绑定一次,不可多次绑定
6.通过bind方式开启服务,服务不能在设置页面找到,相当于是一个隐形的服务。
采用bind的方式开启服务,使用Service的步骤:
1.定义一个类继承Service
2.在Manifest.xml
文件中配置该Service
3.使用Contex
t的bindService(Intent, ServiceConnection, int)
方法启动该Service
4.不再使用时,调用unbindService(ServiceConnection)
方法停止该服务
使用这种bind方式启动的Service的生命周期如下:
onCreate()
--->onBind()
--->onunbind()
--->onDestory()
注意:绑定服务不会调用onstartcommand()
方法
特点:bind的方式开启服务,绑定服务,activity挂了,服务也会跟着挂掉。而start方式开启服务,必须手动去调用stopService(),否则只有在设置强制关掉,或者卸载程序才能关闭服务,哪怕activity挂了退出了,服务一直在后台运行。
bind方式可以间接的调用到服务里面的方法, 可以与服务进行通信.
两种方式启动服务的例子(观察生命周期):
MainActivity.java
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private MyConn myConn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 点击按钮开启服务,通过startservice
public void click1(View view) {
Intent intent = new Intent(this, DemoService.class);
startService(intent);
}
public void click2(View view) {
Intent intent = new Intent(this, DemoService.class);
stopService(intent);
}
// 点击按钮绑定服务,开启服务的第二种方式,不会调用onStartCommand
public void click3(View view) {
Intent intent = new Intent(this, DemoService.class);
// 连接到DemoService这个服务
myConn = new MyConn();
bindService(intent, myConn, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 当activity销毁的时候需要解绑服务
// unbindService(myConn);
}
// 点击按钮手动解绑服务
public void click4(View view) {
unbindService(myConn);// 如果onDestroy再解绑,解绑多次报异常
}
// 定义一个类,用来监视服务的状态
private class MyConn implements ServiceConnection {
// 当服务连接成功调用
// 当onBind方法返回null的时候,这个onServiceConnected是不执行的
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: ");
}
// Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。
// 注意:当客户端取消绑定时,系统“绝对不会”调用该方法。
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected: ");
}
}
}
DemoService.java
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
public class DemoService extends Service {
private static final String TAG = "DemoService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind: ");
return null;
}
// 当服务第一次创建的时候调用
@Override
public void onCreate() {
Log.d(TAG, "onCreate: ");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
// 当服务销毁的时候调用
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy: ");
super.onDestroy();
}
}
批注:
onBind()方法是Service抽象类里面唯一的抽象方法。
真机调试:
点击第一个按钮start-service,运行如下
接着多点几次,就只会多显示几次onStartCommand: ,onCreate只会执行一次
点击stop-service按钮,运行如下:
现在点击bind-service按钮,运行如下:
再点一次这个按钮的话,看不到任何现象,此时解绑一次看不到现象,解绑第二次就抛出异常
Caused by: java.lang.IllegalArgumentException:
Service not registered: com.example.serviceintroduction.MainActivity$MyConn@60b9d14
如果没有解绑就退出,更准确的意思是没有间接或直接的执行unbindService()方法,那么会报如下异常:
android.app.ServiceConnectionLeaked: Activity com.example.serviceintroduction.MainActivity has leaked ServiceConnection com.example.serviceintroduction.MainActivity$MyConn@6697c52 that was originally bound here
如果多次解绑,报异常如下:
Caused by: java.lang.IllegalArgumentException: connection is null
如果按照规则正常解绑,运行如下:
批注:
public abstract boolean bindService (Intent service, ServiceConnection conn, int flags)
连接到应用程序服务,需要时创建它。这定义了应用程序和服务之间的依赖关系。给出conn将在创建服务对象时接收服务对象,如果服务对象死亡并重新启动,将被告知服务对象。只有在调用上下文存在的情况下,系统才会认为服务是必需的。例如,如果此上下文是已停止的活动,则在该活动恢复之前,将不需要该服务继续运行。
如果服务不支持绑定,则可能从它onBind()
方法返回null
。如果返回null,那么ServiceConnection的onNullBinding()
方法将被调用,而不是onServiceConnected()
.
如果调用应用程序没有绑定到给定服务的权限,这个方法会抛出SecurityException
参数 | |
---|---|
service |
Intent: 标识要连接到的服务。意图必须指定显式组件名称。
|
conn |
ServiceConnection: 在服务启动和停止时接收信息。这必须是一个有效的ServiceConnection对象;它不能是NULL。
|
flags |
int: 绑定的操作选项。可能是0,BIND_AUTO_CREATE , BIND_DEBUG_UNBIND , BIND_NOT_FOREGROUND , BIND_ABOVE_CLIENT , BIND_ALLOW_OOM_MANAGEMENT ,或BIND_WAIVE_PRIORITY .
值是 |
返回 | |
---|---|
boolean |
true-- 如果系统正在启动您的客户有权绑定到的服务;false-- 如果系统找不到服务,或者您的客户没有绑定到它的权限。如果此值是true ,你以后应该调用unbindService(ServiceConnection) 释放连接。 |
通过bindService方式调用服务的方法
MainActivity.java
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private CertificationService.MyBinder myBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, CertificationService.class);
// 连接服务
MyConn myConn = new MyConn();
bindService(intent, myConn, BIND_AUTO_CREATE);
}
// 点击按钮
public void onclick(View view) {
myBinder.callBanZheng(10);
}
private class MyConn implements ServiceConnection {
// 当服务连接成功调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (CertificationService.MyBinder) service;
}
// Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。
// 注意:当客户端取消绑定时,系统“绝对不会”调用该方法。
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
CertificationService.java
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.widget.Toast;
public class CertificationService extends Service {
public CertificationService() {
}
// 把定义的中间人对象返回
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
// 办证的方法
private void banZheng(int money) {
if (money > 1000) {
Toast.makeText(this, "我是领导,把证给你办了", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "这点钱还想办事...", Toast.LENGTH_SHORT).show();
}
}
public class MyBinder extends Binder {
public void callBanZheng(int money){
// 调用办证的方法
banZheng(money);
}
}
}
创建服务的时候androidstudio自动在Manifest里配置了。
<service
android:name=".CertificationService"
android:enabled="true"
android:exported="true"></service>
运行图:
通过接口的形式调用服务里面的方法:
现在中间代理人是领导的小蜜,可以代替领导办证,也可以陪领导打麻将,洗桑拿。但是领导只希望向外暴露办证的功能,小蜜的打麻将洗桑拿只能内部使用。那么定义一个接口,将需要暴露的方法写在接口里面。
Iservice.java
interface Iservice {
// 把领导想要暴露的方法都定义在接口里
void callBanZheng(int money);
}
MainActivity.java
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private Iservice myBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, DemoService.class);
// 连接服务
MyConn myConn = new MyConn();
bindService(intent, myConn, BIND_AUTO_CREATE);
}
// 点击按钮
public void onclick(View view) {
myBinder.callBanZheng(10);
// myBinder.callPlayMaJiang(); // 接口里没有这个方法
// myBinder.callXiSangNa();
}
private class MyConn implements ServiceConnection {
// 当服务连接成功调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (Iservice) service;
}
// Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。
// 注意:当客户端取消绑定时,系统“绝对不会”调用该方法。
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
DemoService.java
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class DemoService extends Service {
private static final String TAG = "DemoService";
// 把定义的中间人对象返回
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
// 办证的方法
private void banZheng(int money) {
if (money > 1000) {
Toast.makeText(this, "我是领导,把证给你办了", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "这点钱还想办事...", Toast.LENGTH_SHORT).show();
}
}
private void playMaJiang() {
Log.d(TAG, "陪领导打麻将");
}
private void xisangna() {
Log.d(TAG, "陪领导洗桑拿");
}
private class MyBinder extends Binder implements Iservice {
public void callBanZheng(int money) {
// 调用办证的方法,领导只想把这个方法暴露出去,别的方法内部使用
banZheng(money);
}
public void callPlayMaJiang() {
playMaJiang();
}
public void callXiSangNa() {
xisangna();
}
}
}
此时的运行结果也只能办证,如果需要陪打麻将就把callPlayMaJiang方法暴露在Iservice接口里面就行了。
混合方式开启服务:
需求就是既想让服务在后台长期运行,又想调用服务里面的方法
1.先调用startService方法开启服务,能够保证服务在后台长期运行。
2.调用bindService方法去获取中间人对象。
3.调用unbindService解绑服务。
4.调用stopService
来一个类似于音乐盒播放音乐的逻辑例子:
MainActivity.java
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Iservice iservice;
private MyConn myConn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 混合方式开启服务
Intent intent = new Intent(this, MusicSevice.class);
startService(intent);
// 调用bindService,为了回去定义的中间人对象
myConn = new MyConn();
bindService(intent, myConn, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 当activity销毁的时候调用,为了不报异常
unbindService(myConn);
Log.d(TAG, "onDestroy: ");
}
// 播放
public void click(View view) {
iservice.callPlayMusic();
}
// 暂停
public void click2(View view) {
iservice.callPauseMusic();
}
// 继续播放
public void click3(View view) {
iservice.callRePlayMusic();
}
// 监听服务的状态
private class MyConn implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iservice = (Iservice) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
MusicSevice.java
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class MusicSevice extends Service {
private static final String TAG = "MusicSevice";
public MusicSevice() {
}
// 把定义的中间人返回
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
// 播放音乐的方法
public void playMusic() {
Log.d(TAG, "音乐播放了");
}
// 暂停音乐的方法
public void pauseMusic() {
Log.d(TAG, "音乐暂停了");
}
// 继续播放
public void rePlayMusic() {
Log.d(TAG, "继续播放");
}
// 在服务的内部定义一个中间人对象(IBinder)
private class MyBinder extends Binder implements Iservice {
@Override
public void callPlayMusic() {
playMusic();
}
@Override
public void callPauseMusic() {
pauseMusic();
}
@Override
public void callRePlayMusic() {
rePlayMusic();
}
}
}
Iservice.java
interface Iservice {
// 把想暴露的方法都定义在接口中
void callPlayMusic();
void callPauseMusic();
void callRePlayMusic();
}
点击3个按钮,分别显示如下:
哪怕退出了,音乐仍然会播放,因为start开启的服务一直存在。
aidl的介绍:
这里用到本地服务和远程服务来演示。
需要实现进程间通信,简称IPC
aidl就是专门用来解决进程间的通信的。
进程间的通信(aidl):
假设有个远程端和本地端,先看远程端
建立aidl
接着,就可以在里面写方法了,有一点要注意的是,什么public,private,protected,在这个里面都是不认的。
下面,如果想在其他服务中实现该接口,直接Implements或者是extends,你会发现是找不到这个AIDL的,这个时候,需要编译一下,具体如下:
你会发现,在这个目录下,多了一个文件
这个时候,你就可以在其他service中实现该AIDL
这个Stub是编译后自动生成的Iservice文件里的
RemoteService.java
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import static com.example.remoteservice.Iservice.Stub;
public class RemoteService extends Service {
private static final String TAG = "RemoteService";
public RemoteService() {
}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public void methodService() {
Log.d(TAG, "我是远程服务里面的方法");
}
// 定义一个中间人对象
private class MyBinder extends Stub {
@Override
public void callMethodService() throws RemoteException {
methodService();
}
}
}
批注:之前这里是继承Binder,实现Iservice接口,因为这个Iservice.Stub已经继承了Binder实现了Iservice,所以直接继承Stub,在onBind方法还是返回new MyBinder();类似于之前的套路。我们定义的中间人对象继承Stub就行
在清单文件配置,加上intent-filter,和android:process属性
<service
android:name=".RemoteService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.example.remoteservice.action" />
</intent-filter>
</service>
再来看本地端:
此时将aidl复制到本地端:
不用更改本地端的包名
注意,需要两个应用的aidl文件是同一个,保证aidl文件的包名相同。
本地端代码:
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.support.v7.app.AppCompatActivity;
import android.view.View;
import com.example.remoteservice.Iservice;
public class MainActivity extends AppCompatActivity {
private MyConn myConn;
private Iservice iservice; // 中间人对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 调用bindService获取中间人对象
Intent intent = new Intent();
intent.setAction("com.example.remoteservice.action");
intent.setPackage("com.example.remoteservice");//Android5.0之后需要指定共享Service所在应用的应用包名,否则会抛异常
myConn = new MyConn();
// 绑定服务,目的是为了获取中间人对象
bindService(intent, myConn, BIND_AUTO_CREATE);
}
public void onclick(View view) throws RemoteException {
iservice.callMethodService();
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(myConn);
}
// 监视服务的状态
private class MyConn implements ServiceConnection {
// 注意,中间人对象变了,和之前不一样
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iservice = Iservice.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
点击本地端按钮,看到远程端运行结果
如果看不到现象请在远程日志切换一下包的日志看是不是自己弄错了。
欢乐斗地主买豆:
再来一个相似aidl的例子,加深印象,就是欢乐斗地主需要用钱包买豆子,因为豆子输完了。
钱包(远程服务端)
PayService.java
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import com.example.paymentwallet.Iservice.Stub;
public class PayService extends Service {
private static final String TAG = "PayService";
public PayService() {
}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
// 支付的方法
public boolean pay(String username, String pwd, int money) {
Log.d(TAG, "检查用户名密码是否正确");
Log.d(TAG, "检查密码通过C去实现");
Log.d(TAG, "检测斗地主应用是否携带病毒");
Log.d(TAG, ".........................");
if ("abc".equals(username) && "123".equals(pwd) && money <= 5000) {
return true;
} else {
return false;
}
}
private class MyBinder extends Stub {
public boolean callPay(String username, String pwd, int money) {
return pay(username, pwd, money);
}
}
}
在清单文件添加
<service
android:name=".PayService"
android:process=":remote"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.fightthelandlord" />
</intent-filter>
</service>
和之前一样创建AIDL
Iservice.aidl
interface Iservice {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
boolean callPay(String username, String pwd, int money);
}
编译后的包复制粘贴到客户端中,与java目录并级
欢乐斗地主(客户端)
MainActivity.java
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.example.paymentwallet.Iservice;
public class MainActivity extends AppCompatActivity {
private Iservice iservice;
private MyConn myConn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setAction("com.example.fightthelandlord");
intent.setPackage("com.example.paymentwallet");
// 调用bindservice获取中间人对象
myConn = new MyConn();
// 绑定服务,获取服务里面定义的中间人对象
bindService(intent, myConn, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(myConn);
}
// 点击按钮进行买豆
public void onclick(View view) {
try {
boolean result = iservice.callPay("abc", "123", 1000);
if (result){
Toast.makeText(this, "买豆成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "买豆失败", Toast.LENGTH_SHORT).show();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
private class MyConn implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获取中间人对象
iservice = Iservice.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
运行结果:
当然也可以设置其他金额演示买豆失败的场景
=========================Talk is cheap, show me the code========================