Android 系列笔记 八 - 服务

服务:长期后台运行的没有界面的组件
服务的目的:长期后台运行
系统不容易回收掉进程。即使回收了,内存充足的时候,会把进程重新创建。

创建服务

1.创建服务,并在清单文件中配置服务

1
2
3
4
5
6
class MyService extends Service{
@Override
public IBinder onBind(Intent intent){
return null;
}
}

2.开启服务

1
2
Intent intent = new Intent(this, MyService.class);
startService(intent);

3.关闭服务

1
2
Intent intent = new Intent(this, MyService.class);
stopService(intent);

进程分为5个等级的优先级:(从高到低)

1.Foreground process 前台进程 用户正在玩的应用程序对应的进程
2.Visible process 可视进程 用户仍然可以看到这个进程的页面
3.Service process 服务进程 应用程序有一个服务组件在后台运行
4.Background process 后台进程 应用程序没有服务在运行 并且最小化(activity onStop)
5.Empty process 空进程 没有任何正在运行的activity 任务栈空了

android系统进程管理是按照一定的规则的:
应用程序一旦被打开,通常情况下关闭后(清空任务栈)进程不会停止,方便下一次应用启动。
android系统有一套内存清理机制,按照优先级回收系统的内存。

服务的生命周期

onCreate():服务第一次创建时调用
onStartCommand():服务启动时调用
onDestory():服务销毁时调用

电话录音机

电话的状态
1.空闲状态
2.响铃状态
3.摘机状态(接听)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class RecordService extends Service{
@Override
public IBinder onBind(Intent intent){
return null;
}

@Override
public void onCreate(){
super.onCreate();
TelephonyManager tm = (TelephonyManager)getSystemServie(TELEPHONY_SERVICE);
//第二个参数决定监听什么内容
tm.listen(new MyListener(), PhoneStateListener.LISTEN_CALL_STATE);
}
}

class MyListener extends PhoneStateListener{
private MediaRecorder recorder;

@Override
onCallStateChanged(int state, String incomingNumber){
super.onCallStateChanged(state, incomingNumber);
switch(state){
case TelephonyManager.CALL_STATE_IDLE:
if(recorder != null){
recorder.stop();
recorder.release();
recorder = null;
}
break;
case TelephonyManager.CALL_STATE_RINGING:
//初始化录音机
if(recorder == null){
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setOutputFile(getFilesDir().getPath() + "/recorder.3gp");
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try{
recorder.prepare();
}catch(Exception e){
e.printStackTrace();
}
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
//开始录音
recorder.start();
break;
default:
break;
}
}
}

服务的两种启动方式及生命周期

  1. startService(): 启动服务所在的进程属于服务进程
    activity一旦启动服务,服务就跟activity没有关系了
    onCreate()->onStartCommand()->onDestory()
  2. bindService(): 启动服务所在的进程不属于服务进程
    activity一旦与服务建立连接,activity销毁,服务也会销毁

自定义一个接口:

1
2
3
4
interface  Person{
//定义公共访问的方法
public void visitService();
}

在Activity中代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
MyServiceConnection conn;
Intent intent;

public void onCreate(Bundle onSavedInstanceStste){
super.onCreate(onSavedInstanceStste);
setContentView(R.layout.main);
intent = new Intent(this, MyService.class);

conn = new MyServiceConnection();
bindService(intent, conn, BIND_AUTO_CREATE);
}

public void bind(View v){
//绑定服务 onCreate()->onBind()
bindService(intent, conn, BIND_AUTO_CREATE);
}

public boolean unbind(View v){
//解绑服务 onUnbind()->onDestory()
unbindService(conn);
}

Person p;

public void click(View v){
p.visitService();
}

class MyServiceConnection implements ServiceConnection{
//连接服务成功,第二个参数即为中间人对象
@Override
public void onServiceConnected(ComponentName name, IBinder service){
p = (Person)service;
}
@Override
public void onServiceDisconnected(ComponentName name){

}
}

自定义Service中代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class MyService extends Service{
//绑定时调用
@Override
public IBinder onBind(Intent intent){
//返回一个Binder对象,即中间人对象
return new XiaoLi();
}

//创建内部类作为中间人,来访问服务中方法
class XiaoLi extends Binder implements Person{
//实现接口中用于公共访问的方法
public void visitService(){
//访问service中的方法
help();
}

//自己的方法,不让外界访问
public void daMaJiang(){

}
}

//自定义服务的方法
public void help(){
System.out.println("帮人办事");
}
}

服务模拟音乐播放

1
2
3
4
interface MusicInterface{
public void controlPlay();
public void controlPause();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class MainActivity extends Activity{
MusicServiceConnection conn;
MusicInterface interface;
Intent intent;

public void onCreate(Bundle onSavedInstanceStste){
super.onCreate(onSavedInstanceStste);
setContentView(R.layout.main);
intent = new Intent(this, MusicService.class);
conn = new MusicServiceConnection();
//混合调用,为了把服务所在进程变为服务进程
startService(intent);
//为了拿到中间人对象
bindService(intent, conn, BIND_AUTO_CREATE);
}

public void play(View v){
interface.controlPlay();
}

public void pause(View v){
interface.controlPause();
}

class MusicServiceConnection implements ServiceConnection{
//连接服务成功,第二个参数即为中间人对象
@Override
public void onServiceConnected(ComponentName name, IBinder service){
interface = (MusicInterface)service;
}
@Override
public void onServiceDisconnected(ComponentName name){

}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MusicService extends Service{
//绑定时调用
@Override
public IBinder onBind(Intent intent){
//返回一个Binder对象,即中间人对象
return new MusicController();
}

class MusicController extends Binder implements MusicInterface{
public void controlPlay(){
play();
}
public void controlPause(){
pause();
}
}

public void play(){
System.out.println("开始播放音乐");
}

public void pause(){
System.out.println("暂停播放音乐");
}
}

服务的混合调用

onCreate()->onStartCommand()->onBind()->onUnbind()->onDestory()

使用代码配置广播接收者

  1. 使用清单文件注册
    广播一旦发出,系统就会去所有清单文件中寻找哪个广播接收者的action和广播的action是匹配的。如果找到了,就会把该广播接收者的进程启动起来
  2. 使用代码注册
    需要使用广播接收者时,执行注册的代码;不需要时,解除注册

特殊的广播接收者

安卓中有一些广播接收者,必须使用代码注册,清单文件注册是无效的
1). 屏幕锁屏与解锁
2). 电量改变

使用服务注册广播接收者

1
2
3
4
5
6
7
8
public class ScreenReceiver extends BroadcastReceiver{
public void onReceive(Context context, Intent intent){
String action = intent.getAction();
if(Intent.ACTION_SCREEN_ON,equals(action)){
//do something
}
}
}

自定义服务代码中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public MyService extends Service{
ScreenReceiver receiver;

@Override
public IBinder onBind(Intent intent){
return null;
}

@Override
public void onCreate(){
super.onCreate();
//创建广播接收者
receiver = new ScreenReceiver();
//创建intent-filter
IntentFilter filter= new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
//注册广播接收者
registerReceiver(receiver, filter);
}

@Override
public void onDestory(){
super.onDestory();
unregisterReceiver(receiver);
}
}

服务的分类

  1. 本地服务
    服务与启动服务的activity在同一个进程中
  2. 远程服务
    服务与启动服务的activity不在同一个进程中

远程服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public MemoteService extends Service{

@Override
public IBinder onBind(Intent intent){
return null;
}

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

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

启动远程服务

1
2
3
4
5
Intent intent = new Intent();
intent.setAction("远程服务清单文件中配置的action");
//android 5.0 之后需要指定要启动的应用程序包名
intent.setPackage("com.jockio.learnandroid");
startService(intent);

AIDL: 进程间通信

Android Interface Definition Language
步骤:

  1. 将远程服务的方法抽取成一个单独的接口 java 文件
  2. 将接口文件的后缀名 java 改为 aidl,
  3. 在自动生成的接口 .java 文件中,有一个静态抽象类 Stub,它已经继承了 Binder 类,实现了抽取方法后的接口,这个类就是中间人
  4. 把 aidl 文件复制粘贴到要访问远程服务的项目中
    注意:aidl 包名跟原包名必须完全一致
  5. 在要访问远程服务的项目中,强转中间人对象时,直接使用 Stub.asInterface( Service service)

用 AIDL 模拟支付宝服务

PayInterface.aidl

1
2
3
interface PayInterface{
public void pay();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class PayService extends Service{
//绑定时调用
@Override
public IBinder onBind(Intent intent){
//返回一个Binder对象,即中间人对象
return new PayController();
}

class PayController extends Stub{
public void pay() throws RemoteException{
PayService.this.pay();
}
}

public void pay(){
System.out.println("完成支付");
}
}

远程调用模拟支付宝服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//把 PayInterface.aidl 文件复制粘贴到项目中
//注意:PayInterface.aidl 包名跟原包名必须完全一致
class MainActivity extends Activity{
PayServiceConnection conn;
Intent intent;
PayInterface interface;

public void onCreate(Bundle onSavedInstanceStste){
super.onCreate(onSavedInstanceStste);
setContentView(R.layout.main);

intent = new Intent();
intent.setAction("支付宝服务的action");
//android 5.0 之后需要指定要启动的应用程序包名
intent.setPackage("com.jockio.learnandroid");
conn = new PayServiceConnection();
//混合调用,为了把服务所在进程变为服务进程
startService(intent);
//为了拿到中间人对象
bindService(intent, conn, BIND_AUTO_CREATE);
}

public void click(View v){
interface.pay();
}

class PayServiceConnection implements ServiceConnection{
//连接服务成功,第二个参数即为中间人对象
@Override
public void onServiceConnected(ComponentName name, IBinder service){
interface = Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name){

}
}
}

进程优先级补充

前台进程

  1. 拥有一个正在与用户进行交互的activity(onResume方法调用)的进程
  2. 拥有一个与正在和用户交互的activity绑定的服务的进程
  3. 拥有一个正在“运行于前台”的服务–服务的startForeground方法调用
  4. 拥有一个正在执行以下三个生命周期方法中任意一个的服务
    onCreate() onStart() onDestory
  5. 拥有一个正在执行 onReceive 方法的广播接收者的进程

可见进程

  1. 拥有一个不在前台,但是用户依然可见的activity(onPause方法调用)的进程
  2. 拥有一个与可见(或前台)activity绑定的服务的进程

样式与主题

资源目录下,在 values 文件夹中,新建 styles.xml 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8">
<resources>
<style name="myStyle">
<item name="android:textSize">20sp</item>
</style>
<!--继承方式一-->
<style name="textviewStyle" parent="myStyle"/>
<!--继承方式二-->
<style name="myStyle.another"/>

<!--主题-->
<style name="themeStyle">
<item name="android:background">#f0f0ff</item>
</style>
</resources>

布局文件中

1
2
3
<TextView
android:text="标题"
style="@style/myStyle.another"/>

国际化

新建文件夹 values-en, 新建strings.xml

1
2
3
4
<?xml version="1.0" encoding="utf-8">
<resources>
<string name="app_name">hello</string>
</resources>

新建文件夹 values-zh, 新建strings.xml

1
2
3
4
<?xml version="1.0" encoding="utf-8">
<resources>
<string name="app_name">你好</string>
</resources>