转载请标明出处:
http://blog.csdn.net/gj782128729/article/details/52510925;
本文出自:【高境的博客】
当一个程序第一次启动的时候,Android会启动一个Linux进程和一个主线程。默认的情况下,所有该程序的组件都将在该进程和线程中运行。Android会尽量保留一个正在运行进程,只在内存资源出现不足时,Android会尝试停止一些进程从而释放足够的资源给其他新的进程使用,也能保证用户正在访问的当前进程有足够的资源去及时地响应用户的事件。
我们可以将一些组件运行在其他进程中,并且可以为任意的进程添加线程。组件运行在哪个进程中是在manifest文件里设置的,其中
,
,
和
都有一个process属性来指定该组件运行在哪个进程之中。我们可以设置这个属性,使得每个组件运行在它们自己的进程中,或是几个组件共同享用一个进程,或是不共同享用。
元素也有一个process属性,用来指定所有的组件的默认属性。
Android会根据进程中运行的组件类别以及组件的状态来判断该进程的重要性,Android会首先停止那些不重要的进程。按照重要性从高到低一共有五个级别:
1.前台进程:
前台进程是用户当前正在使用的进程。只有一些前台进程可以在任何时候都存在。他们是最后一个被结束的,当内存低到根本连他们都不能运行的时候。一般来说,在这种情况下,设备会进行内存调度,中止一些前台进程来保持对用户交互的响应。
如果有以下的情形的那么就是前台进程:
(a)这个进程运行着一个正在和用户交互的Activity(这个Activity的onResume()方法被调用)。
(b)这个进程里有绑定到当前正在和用户交互的Activity的一个service。
(c)这个进程里有一个service对象,这个service对象正在执行一个它的生命周期的回调函数(onCreate(), onStart(), onDestroy())
(d)这个进程里有一个正在运行onReceive()方法的BroadCastReiver对象。
2.可见进程
可见进程是不包含前台的组件但是仍会影响用户在屏幕上所见内容的进程,除非前台进程需要获取它的资源,不然不会被中止。
如果有如下的一种情形就是可见进程:
(a)这个进程中含有一个不位于前台的Activity,但是仍然对用户是可见的(这个Activity的onPause()方法被调用),这是很可能发生的,例如,如果前台Activity是一个对话框的话,就会允许在它后面看到前一个Activity。
(b)这个进程里有一个绑定到一个可见的Activity的Service。
3.服务进程
运行着一个通过startService() 方法启动的service,它不会升级为上面两种级别。service所在的进程虽然对用户不是直接可见的,但是他们执行了用户非常关注的任务(比如播放mp3,从网络下载数据)。只要前台进程和可见进程有足够的内存,系统不会回收他们。
4.后台进程
运行着一个对用户不可见的activity(调用过 onStop() 方法)。这些进程对用户体验没有直接的影响,可以在服务进程、可见进程、前台进程需要内存的时候回收。通常,系统中会有很多不可见进程在运行,他们被保存在LRU (least recently used) 列表中,以便内存不足的时候被第一时间回收。如果一个activity正确的执行了它的生命周期,关闭这个进程对于用户体验没有太大的影响。
5.空进程
未运行任何程序组件。运行这些进程的唯一原因是作为一个缓存,缩短下次程序需要重新使用的启动时间。系统经常中止这些进程,这样可以调节程序缓存和系统缓存的平衡。
Android 对进程的重要性评级的时候,选取它最高的级别。例如,如果一个进程含有一个service和一个可视activity,进程将被归入一个可视进程而不是service进程。
应用程序启动时,系统会为它创建一个名为“main”的主线程。主线程非常重要,因为它负责把事件分发给相应的用户界面widget——包括屏幕绘图事件。它也是应用程序与Android UI组件包(来自android.widget和android.view包)进行交互的线程。因此,主线程有时也被叫做UI线程。
系统并不会为每个组件的实例都创建单独的线程。运行于同一个进程中的所有组件都是在UI线程中实例化的,对每个组件的系统调用也都是由UI线程分发的。因此,对系统回调进行响应的方法(比如报告用户操作的onKeyDown()或生命周期回调方法)总是运行在UI线程中。
如果UI线程需要处理每一件事情,那些耗时很长的操作——诸如访问网络或查询数据库等将会阻塞整个UI(线程)。一旦线程被阻塞,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,如果UI线程被阻塞超过一定时间(目前大约是5秒钟),用户就会被提示那个可恶的“应用程序没有响应”(ANR)对话框。
此外,Andoid的UI组件包并不是线程安全的。因此不允许从工作线程中操作UI——只能从UI线程中操作用户界面。于是,Andoid的单线程模式必须遵守两个规则:
(a)不要阻塞UI线程。
(b)不要在UI线程之外访问Andoid的UI组件包。
Service是Android中四大组件之一,在Android开发中有非常重要的作用。
Service(服务)是一个没有用户界面的在后台运行执行耗时操作的应用组件。其他应用组件能够启动Service,并且当用户切换到另外的应用场景,Service将持续在后台运行。另外,一个组件能够绑定到一个service与之交互(IPC机制),例如,一个service可能会处理网络操作,播放音乐,操作文件 I/O或者与内容提供者(content provider)交互,所有这些活动都是在后台进行。
Service有两种状态,“启动的”和“绑定”:
Started
通过startService() 启动的服务处于“启动的”状态,一旦启动,Service就在后台运行,即使启动它的应用组件已经被销毁了。通常started状态的Service执行单任务并且不返回任何结果给启动者。比如当下载或上传一个文件,当这项操作完成时,Service应该停止它本身。
Bound
还有一种“绑定”状态的Service,通过调用bindService()来启动,一个绑定的Service提供一个允许组件与Service交互的接口,可以发送请求、获取返回结果,还可以通过跨进程通信来交互(IPC)。绑定的Service只有当应用组件绑定后才能运行,多个组件可以绑定一个Service,当调用unbind()方法时,这个Service就会被销毁了。
另外,在官方的说明文档中还有一个警告:
Service 与Activity一样都存在与当前进程的主线程中,所以,一些阻塞UI的操作,比如耗时操作不能放在Service里进行,比如另外开启一个线程来处理 诸如网络请求的耗时操作。如果在Service里进行一些耗CPU和耗时操作,可能会引发ANR警告,这时应用会弹出强制关闭还是等待的对话框。所以,对Service的理解就是和Activity平级的,只不过是看不见的,在后台运行的一个组件,这也是为什么和Activity同被说为Android的基本组件。
1) 创建一个Service,需要继承Service类。
2) 覆盖一些回调函数来处理服务生命周期的一些关键要素,并且如果合适的话,需要提供一个机制让组件绑定到服务。这些最重要的需要覆盖的函数如下:
onStartCommand():
onStartCommand()方法当另一个组件(如Activity)通过调用startService()请求Service启动时调用。一旦这个方法执行,这个服务就被开启并且在后台无限期地运行。如果你实现了这个方法,你就有责任在服务工作完毕后通过调用stopSelf()或者stopService()关闭它(如果仅仅用来提供绑定,就不需要实现这个方法)。
onBind():
onBind()方法当另一个组件(如执行RPC)通过bindService()和这个服务绑定的时候调用。在这个方法的实现中,需要通过返回一个IBinder对象提供客户端用来和Service通讯的接口,你必须一致实现该方法,除非不想绑定服务,这时候需要返回null。
onCreate()
Service第一次创建的时候调用该方法来执行一次性安装程序(之前调用要么onStartCommand()要么onBind())。如果Service已经运行了,这个方法不会被调用。
onDestory()
当Service不再使用并且被销毁的时候调用。服务需要实现这个方法来清理资源如线程,注册的监听器,接受者等。这个方法是Service最后一个调用的。
3) 在清单文件中注册服务
... >
...
... >
".ExampleService" />
...
服务的第一种启动方式是调用startService()方法。
创建一个服务,必须实现onBind()方法,实现onCreate(),onStartCommand()和onDestory()方法:
public class DemoService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
System.out.println("onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
System.out.println("onDestroy");
super.onDestroy();
}
}
在清单文件中注册
<service android:name="com.itheima.servicedemo.DemoService">service>
public void click(View v){
Intent intent = new Intent(MainActivity.this,DemoService.class);
startService(intent);
}
查看Logcat日志发现开启服务调用onCreate()和onStartCommand()方法:
在此点击按钮,查看日志,发现onStartCommand()又执行了一次:
那么如何查看服务真的被启动了呢?我们可以在模拟器设置界面中的应用界面查看,如下图:
那么如何停止服务呢?点开上图标出的条目,跳转到如下图的另一个界面,点击停止按钮。如下图:
这时候,服务就停止了。我们可以查看Logcat日志,发现调用了onDestory()方法:
通过上面的操作可以得出调用startService()方法时Service的生命周期如下图:
本案例实现在后台监听电话状态,当手机来电并且接通后,开始录音,并且保存到sdcard中。
为什么需要在服务中监听电话状态呢?因为服务开启后可以在后台一直运行,如果放在Activity中监听电话状态,当Activity销毁后就不能监听到电话状态了。
使用Thread(){}.start()方法也可以在后台实现监听,那么为什么不使用子线程而使用Service呢?因为之前我们已经了解了进程的等级,当应用程序退出时,当前应用的进程就变成一个空进程,最容易被系统回收。开启服务是在服务进程中运行,服务进程的优先级比后台进程高。
监听电话状态需要使用TelephonyManager类。TelephonyManager类主要提供了一系列用于访问与手机通讯相关的状态和信息的get方法。其中包括手机SIM的状态和信息、电信网络的状态及手机用户的信息。
Context.getSystemService(Context.TELEPHONY_SERVICE)方法可以用来获取到TelephonyManager类的对象。需要注意的是有些通讯信息的获取,对应用程序的权限有一定的限制,在开发的时候需要为其添加相应的权限。
listen()方法,注册一个监听对象用来接收指定电话状态变化的通知。
在服务中利用TelephonyManager监听电话状态:
public class PhoneService extends Service {
private TelephonyManager tManager;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
tManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
//调用TelephonyManager的listen()方法监听电话状态。参数1表示电话状态监听器,参数2表示需要监听的电话事件
tManager.listen(new MyPhoneStateListener(),PhoneStateListener.LISTEN_CALL_STATE);
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
创建自定义电话状态监听器:
private class MyPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
//判断state状态
switch (state) {
case TelephonyManager.CALL_STATE_IDLE: // CALL_STATE_IDLE表示空闲状态
System.out.println("判断用户是否已经开启录音机,如果开启,上传");
break;
case TelephonyManager.CALL_STATE_OFFHOOK: // CALL_STATE_OFFHOOK表示接通状态
System.out.println("开始录");
break;
case TelephonyManager.CALL_STATE_RINGING: // CALL_STATE_RINGING表示电话响铃状态
System.out.println("电话响铃的时候 我就准备一个录音机 ");
break;
default:
break;
}
}
}
清单文件加入读取手机状态权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
开启服务
public void click(View view){
Intent intent = new Intent(this,PhoneService.class);
startService(intent);
}
运行结果
当开启服务后,没有来电,Logcat打印如下日志:
上面已经实现了电话各种状态的监听,现在可以在不同的状态下操作录音,将通话录音记录到sdcard,这里需要用到MediaRecorder对象。具体的介绍我们可以参考api。
在监听方法中实现录音功能:
//创建MediaRecorder
private MediaRecorder recorder;
......
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
//关闭Recorder,释放资源
if (recorder!=null) {
recorder.stop();
recorder.reset();
recorder.release();
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
System.out.println("开始录");
//开启录音
recorder.start();
break;
case TelephonyManager.CALL_STATE_RINGING:
//创建MediaRecorder对象
recorder = new MediaRecorder();
//设置音频来源
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置影音的输出格式
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
//设置音频文件的编码格式
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
//设置音频文件保存的路径
recorder.setOutputFile("/mnt/sdcard/luyin.3gp");
try {
//准备录音
recorder.prepare();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
break;
default:
break;
}
加入权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
运行结果,可以在sdcard中看到保存的录音luyin.3gp。
如何自动开启服务呢?可以在系统重启时,利用广播接受者接收系统重启广播,在广播中开启监听电话状态的服务。
定义广播接受者
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent intent2 = new Intent(context,PhoneService.class);
context.startService(intent2);
}
}
需要加入权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
开启服务的第二种方式是bindService()。第一种方式是startService()方法开启,与之对应的停止服务的方法是stopService(),bindService()对应的方法是unbindService()。
首先编写Service类:
public class TestService extends Service {
//当服务被成功绑定的时候调用
@Override
public IBinder onBind(Intent intent) {
System.out.println("onBind");
return null;
}
@Override
public void onCreate() {
System.out.println("onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
System.out.println("onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
System.out.println("onDestroy");
super.onDestroy();
}
}
然后实现Activity中按钮点击事件开启服务:
MainActivity中实现两种方式开启服务:
public class MainActivity extends Activity {
private MyConn conn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 采用start-service方式开启服务
public void click1(View v) {
Intent intent = new Intent(this, TestService.class);
startService(intent);
}
// 采用stop-service方式开启服务
public void click2(View v) {
Intent intent = new Intent(this, TestService.class);
stopService(intent);
}
// 采用bind-service方式开启服务
public void click3(View v) {
Intent intent = new Intent(this, TestService.class);
// conn 是ServiceConnection接口用来监视服务的状态 flags 为了绑定服务
conn = new MyConn();
//bindService()方法,用来绑定服务,当这个方法一调用,Servie中的onBind()方法就会执行。参数1表示需要传入的服务的intent,参数2便是一个ServiceConnection的实现类,用来监视服务的状态,参数3是绑定的操作选项
bindService(intent, conn, BIND_AUTO_CREATE);
}
// 采用unbind-service方式开启服务
public void click4(View v) {
unbindService(conn);
}
//MyConn类实现ServiceConnection接口,用来监视服务的状态。其中有两个回调方法,onServiceConnected()方法表示当服务器连接的时候调用,onServiceDisconnected()方法表示当服务失去连接时调用
private class MyConn implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onDestroy() {
//unbindService()方法,用来解绑服务
//unbindService(conn);
super.onDestroy();
}
}
首先点击bindService(),Service会调用onCreate()和onBind()方法,查看Logcat日志:
我们点击返回键,这样Activity就会销毁,这时候查看日志,发现Service调用了onDestory()方法,同时Logcat日志会报错,如下图:
出现这个错误的原因是由于当Activity销毁的时候会调用onDestory()方法,在Activity需要调用unbindService()方法将服务解绑。所以我们还需要在Activity的onDestory()方法中加入以下代码:
@Override
protected void onDestroy() {
unbindService(conn);
super.onDestroy();
}
接下来,我们重新部署项目,然后点击bind-service按钮,Service会调用onCreate()和onBind()方法,Logcat日志会打印如下结果:
这时候我们查看模拟器设置里的应用中查找我们的服务,发现找不到我们开启的服务,如下图:
然后点击unbind-service按钮,Service会调用onUnbind()和onDestory()方法,Logcat日志会打印以下结果:
接着,我们重新部署项目,连续点击两次bind-service按钮,然后我们点击unbind-service按钮,再次点击unbind-service按钮,这时候应用会崩溃,并且Logcat会输出以下错误日志:
总结:
(a)使用bindService()绑定服务,不会使进程变成服务进程;
(b)使用bindService()绑定服务,只可以绑定一次,不能多次绑定。
使用bindService()方法开启服务,Service的生命周期如下图:
创建一个Service,在Service中定义一个内部方法:
public class TestDemoService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
public void serviceMethod(){
Toast.makeText(this, "我是服务里面的方法", 1).show();
}
}
在MainActivity中点击按钮调用服务中的方法:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
//创建TestDemoService对象
TestDemoService testDemoService = new TestDemoService();
//调用serviceMethod()方法
testDemoService.serviceMethod();
}
}
应用程序会崩溃,并且Logcat会有如下日志输出:
定位到代码中,是Service中自定义的方法中弹出吐司出现了错误。由于这边调用Service服务中的方法是通过Service的一个实例来调用的,那么这个实例对象就是一个普通的类,普通的类中没有上下文,所以弹出吐司需要传递的上下文就为null,所以程序会报空指针异常。
当使用bindService()绑定服务的时候,需要传入一个参数,这个参数是ServiceConnection的一个实现类,这个实现类代码如下:
private class MyConn implements ServiceConnection{
@Override
//onServiceConnected()方法当绑定成功后调用,其中有一个IBinder对象
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
然后分析Service中的onBind()方法代码:
@Override
public IBinder onBind(Intent intent) {
return null;
}
通过onBind()方法发现这个方法返回值是IBinder类型,和我们上面ServiceConnection中绑定成功回调中的一个参数类型一致。其实,这边的两个对象是同一个对象,当服务绑定成功后,会返回IBinder对象给ServiceConnection中的回调函数。
查看IBinder类,发现这个类是一个接口,所以我们要么实现IBinder接口,要么继承它已知的子类,这里我们继承IBinder的一个子类Binder类。
public class TestDemoService extends Service {
@Override
public IBinder onBind(Intent intent) {
//返回自定义MyBinder对象
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
public void serviceMethod(){
Toast.makeText(this, "我是服务里面的方法", 1).show();
}
//创建MyBinder类继承Binder类
public class MyBinder extends Binder{
//创建callServiceMethod()方法
void callServiceMethod(){
//调用Service中的serviceMethod()方法
serviceMethod();
}
}
}
这时候当服务成功绑定后,ServiceConnection中的回调函数就能接收到这个对象,通过这个对象来调用服务中的方法。在MainActivity中绑定服务,并且通过IBinder调用服务中的方法:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
bindService(new Intent(this,TestDemoService.class), new MyConn(), BIND_AUTO_CREATE);
}
class MyConn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
//接收Service中onBind()返回的IBinder对象,这个IBinder对象就是我们自定义的MyBinder对象
MyBinder binder = (MyBinder) service;
//通过IBinder对象调用服务中的方法
binder.callServiceMethod();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
下图是简易的Activity中调用Service中的方法图:
这时候,问题又来了,如果Service中有很多方法,那么中间人IBinder就可以调用Service中的方法,那么如何控制IBinder中的方法呢?我们可以定义一个接口,将Service中的方法定义到接口中,让我们的自定义的Binder实现这个接口。
定义的接口:
public interface Iservice {
public void callBanZheng(int money);
public void callPlayMaJiang();
public void callXiSangNa();
}
自定义的MyBinder实现该接口:
private class Mybinder extends Binder implements IService {
@Override
public void callBanZheng(int money) {
banZheng(money);
}
@Override
public void callPlayMaJiang() {
playMaJiang();
}
@Override
public void callXiSangNa() {
xiSangna();
}
}
这样,自定义的MyBinder类就可以调用Service中的方法。如果Service不希望MyBinder具有其中某个方法,那么可以在IService接口中不提供该方法。
音乐播放器需要使用到服务,因为当音乐播放器需要在后台也能运行,当手机按Home键后,音乐播放器也需要能够播放,本案例实模拟音乐播放器的实现。在后台服务中,既要保证服务能够在后台一直运行,又要保证Activity中能够调用服务中的方法,这就需要利用两种启动方法混合使用。
定义一个服务MusicService用来在服务中播放音乐,暂停播放,继续播放等:
public class MusicService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
//定义开始播放方法
public void play(){
System.out.println("音乐开始播放了 ");
}
//定义音乐暂停方法
public void pause(){
System.out.println("音乐暂停了 ");
}
//定义音乐继续播放方法
public void rePlay(){
System.out.println("音乐继续播放 ");
}
private class MyBinder extends Binder implements IService{
@Override
public void callPlay() {
play();
}
@Override
public void callPause() {
pause();
}
@Override
public void callReplay() {
rePlay();
}
}
}
IService接口中定义方法:
public interface Iservice {
public void callPlay();
public void callPause();
public void callReplay();
}
在Activity中点击按钮,实现音乐播放器的功能:
public class MainActivity extends Activity {
private Myconn myconn;
private Iservice iservice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this,MusicService.class);
//调用startService()方法开启服务,保证服务在后台长期运行
startService(intent);
myconn = new Myconn();
//调用bindService()方法,目的是为了调用服务中的方法
bindService(intent, myconn, BIND_AUTO_CREATE);
}
public void click1(View v) {
iservice.callPlay();
}
public void click2(View v) {
iservice.callPause();
}
public void click3(View v) {
iservice.callReplay();
}
private class Myconn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
iservice = (Iservice) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onDestroy() {
unbindService(myconn);
super.onDestroy();
}
}
运行结果:
当应用程序一起动,就开启服务,并且一直运行。如下图:
首先点击播放,这时候音乐开始播放,Logcat输出如下日志:
然后点击暂停,这时候音乐暂停播放,Logcat输出如下日志:
接下来点击继续播放,这时候音乐继续播放,Logcat输出如下日志:
(a)采用startServie()方法开启服务,保证服务能够在后台长期运行;
(b)调用bindServie()方法,目的是能够调用服务中的方法;
(c)Activity销毁时调用unbindService()方法解绑;
(d)最后调用stopService()方法停止服务。
安卓中,有些广播需要广播接受者动态注册(采用代码的方式注册),比如电池电量低、解锁屏等特殊的广播。
定义广播接受者类ScreenReceiver:
public class ScreenReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if ("android.intent.action.SCREEN_OFF".equals(action)) {
System.out.println("屏幕off了");
}else if("android.intent.action.SCREEN_ON".equals(action)){
System.out.println("屏幕on了");
}
}
}
在清单文件中注册广播:
<receiver android:name="com.itheima.register.ScreenReceiver" >
<intent-filter>
<action android:name="android.intent.action.SCREEN_ON" />
<action android:name="android.intent.action.SCREEN_OFF" />
intent-filter>
receiver>
我们在模拟器上,解屏或者锁屏,发现Logcat没有日志输出,说明静态注册广播接受者不能接收到解锁屏的广播。
为什么不能接收到屏幕解锁屏的广播呢?因为手机一天会解锁屏很多次,所以广播接受者会不断的注册,这样就比较耗电,所以谷歌就对发送这种特殊的广播做了特殊处理,只有动态注册,才能监听到这类广播。
我们在Activity中注册广播:
public class MainActivity extends Activity {
private ScreenReceiver screenReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
screenReceiver = new ScreenReceiver();
//创建IntentFilter对象,给该对象设置Action
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.SCREEN_OFF");
filter.addAction("android.intent.action.SCREEN_ON");
//调用Context对象的registerReceiver()注册广播。参数1表示广播接受者,参数2表示意图过滤器
registerReceiver(screenReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
运行结果:
当点击锁屏按钮,屏幕黑屏,Logcat会输出如下日志:
这时候,如果点击返回按钮,应用程序退出,Logcat会报如下错误,提示我们需要调用unregisterReceiver()方法:
在Activity的onDestory()中调用unregisterReceiver()方法:
protected void onDestroy() {
unregisterReceiver(screenReceiver);
super.onDestroy();
}
因为在Activity中注册广播,当Activity被销毁后,就不能监听到解锁屏的广播,所以,可以在Service中后台一直监听广播。
public class ScreenService extends Service {
private ScreenReceiver screenReceiver;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
screenReceiver = new ScreenReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.SCREEN_OFF");
filter.addAction("android.intent.action.SCREEN_ON");
registerReceiver(screenReceiver, filter);
super.onCreate();
}
@Override
public void onDestroy() {
unregisterReceiver(screenReceiver);
super.onDestroy();
}
}
IPC(Inter-Process Communication):进程间通信。
Android系统中,每个应用程序都会有一个进程,由于应用程序之间不能共享内存,那么就需要进程间通信。
本地服务:服务和启动它的组件在同一个进程。
远程服务:服务和启动它的组件不在同一个进程。
远程服务只能隐式启动,类似隐式启动Activity,在清单文件中配置Service标签时,必须配置intent-filter子节点,并指定action子节点。
AIDL(Android Interface Definition Language):安卓接口定义语言。AIDL是安卓中用来跨进程通信的一种接口定义语言。
应用场景:远程服务中的中间人对象,其他应用是拿不到的,那么在通过绑定服务获取中间人对象时,就无法强制转换,使用aidl技术,就可以在其他应用中拿到中间人类所实现的接口。
创建一个远程应用项目,在项目中创建一个远程服务类RemoteService:
public class RemoteService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
public void remoteMethod(){
System.out.println("我是远程服务里面的方法");
}
private class MyBinder extends Binder implements Iservice{
@Override
public void callRemoteMethod() {
remoteMethod();
}
}
}
IService代码:
public interface Iservice {
public void callRemoteMethod();
}
配置远程服务:
<service android:name="com.itheima.remoteservice.RemoteService">service>
创建本地应用项目,在MainActivity中实现点击按钮事件:
public class MainActivity extends Activity {
private Iservice iservice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
bindService(intent, new MyConn(), BIND_AUTO_CREATE);
}
public void click(View v){
}
private class MyConn implements ServiceConnection{
//当服务被链接成功的时候调用
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
以上是调用本地服务的操作步骤,下面利用aidl实现本地应用访问远程服务。
首先,将定义方法的接口类Iservice文件的后缀名改成.aidl,这时候刷新下工程,Iservice文件会报错,如下图:
从上图可以看出,需要将public关键字去掉,这样我们的Iservice就不会报错。为什么不能使用public呢?既然Iservice这个aidl是用来解决进程间通信的,那么它肯定是public的,所以不需要加上public。
这时候到工程目录的gen目录下可以看到Iservice.java这个文件,打开看一下,如下图:
从上图可以看到生成的文件中,有一个Stub类,继承Binder类并且实现Iservice接口。这时候我们发现,自定义的中间人可以继承这个Stub类。
接下来我们要在本地应用中通过中间人来访问远程服务,那么如何保证本地应用的中间人和远程服务的中间人是同一个呢?谷歌规定,只要在本地应用中,包名和aidl文件名和远程服务一致就可以了。
远程服务的包名:
本地应用中需要创建和远程服务一样的包名,然后将Iservice.aidl文件拷贝到本地应用中创建的包中:
做完以上操作后,在本地应用的gen目录下就可以看到自动生成的Iservice.java文件,有了这个文件,就可以调用远程服务中的方法,如下图:
在本地应用的MainActivity中完善代码:
public class MainActivity extends Activity {
private Iservice iservice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
//通过设置intent的action来指定打开的服务,这边需要在远程服务的项目的清单文件中配置这个action
intent.setAction("com.itheima.remoteservice");
bindService(intent, new MyConn(), BIND_AUTO_CREATE);
}
public void click(View v){
try {
//调用中间人对象调用远程服务中的方法
iservice.callRemoteMethod();
} catch (RemoteException e) {
e.printStackTrace();
}
}
private class MyConn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
//通过Iservice.Stub.asInterface(service)方法获取IBinder对象
iservice = Iservice.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
在远程服务的项目的清单文件中配置这个action
<service android:name="com.itheima.remoteservice.RemoteService">
<intent-filter >
<action android:name="com.itheima.remoteservice"/>
intent-filter>
service>
运行效果:
首先部署远程服务到手机中,然后部署本地服务,点击按钮后,查看日志,发现调用了远程服务中的方法。如下图:
远程服务最常见的应用场景就是支付宝,本案例模拟支付宝支付过程。
创建Iservice接口,在接口中定义中间人对象需要实现的方法:
public interface Iservice {
public boolean callPay(String name,String pwd,int money);
}
将Iservice文件后缀名改为.aidl,取消访问修饰符public,如下图:
定义支付宝的服务,在服务中定义pay方法:
public class ALiPayService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
//定义pay方法,返回值为boolean类型,用来判断支付结果(成功或失败)
public boolean pay(String name,String pwd,int money){
System.out.println("检查用户名和密码是否正确....");
System.out.println("检查手机是否有病毒....");
System.out.println("检查余额是否够用....");
if ("abc".equals(name)&&"123".equals(pwd)&&money>5000) {
return true;
}else {
return false;
}
}
}
在服务中定义中间人对象MyBinder类,直接继承Stub类,实现方法:
private class MyBinder extends Stub{
@Override
public boolean callPay(String name, String pwd, int money) {
//调用Service中的pay方法
return pay(name, pwd, money);
}
}
注册支付宝服务,需要在AndroidManifest.xml文件中配置,并且加上意图过滤器:
<service android:name="com.itheima.alipay.ALiPayService">
<intent-filter >
<action android:name="com.itheima.alipay"/>
intent-filter>
service>
接下来模拟“欢乐斗地主”应用调用支付宝支付,在欢乐斗地主项目工程目录下创建与远程服务同名的包,将远程服务的aidl文件拷贝到新建的包下:
绑定到远程服务,点击按钮调用远程服务的支付方法支付:
public class MainActivity extends Activity {
private MyConn myConn;
private Iservice iservice
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setAction("com.itheima.alipay");
myConn = new MyConn();
bindService(intent, myConn, BIND_AUTO_CREATE);
}
public void click(View v){
try {
boolean result = iservice.callPay("abc", "123", 501);
if (result) {
Toast.makeText(getApplicationContext(), "够买欢乐豆成功", 0).show();
}else {
Toast.makeText(getApplicationContext(), "够买失败", 0).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) {
}
}
}