服务:长期后台运行的没有界面的组件
服务的目的:长期后台运行
系统不容易回收掉进程。即使回收了,内存充足的时候,会把进程重新创建。
创建服务
1.创建服务,并在清单文件中配置服务1
2
3
4
5
6class MyService extends Service{
public IBinder onBind(Intent intent){
return null;
}
}
2.开启服务1
2Intent intent = new Intent(this, MyService.class);
startService(intent);
3.关闭服务1
2Intent 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
53public class RecordService extends Service{
public IBinder onBind(Intent intent){
return null;
}
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;
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;
}
}
}
服务的两种启动方式及生命周期
- startService(): 启动服务所在的进程属于服务进程
activity一旦启动服务,服务就跟activity没有关系了
onCreate()->onStartCommand()->onDestory() - bindService(): 启动服务所在的进程不属于服务进程
activity一旦与服务建立连接,activity销毁,服务也会销毁
自定义一个接口:1
2
3
4interface 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
39MyServiceConnection 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{
//连接服务成功,第二个参数即为中间人对象
public void onServiceConnected(ComponentName name, IBinder service){
p = (Person)service;
}
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
27class MyService extends Service{
//绑定时调用
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 | interface MusicInterface{ |
1 | class MainActivity extends Activity{ |
1 | class MusicService extends Service{ |
服务的混合调用
onCreate()->onStartCommand()->onBind()->onUnbind()->onDestory()
使用代码配置广播接收者
- 使用清单文件注册
广播一旦发出,系统就会去所有清单文件中寻找哪个广播接收者的action和广播的action是匹配的。如果找到了,就会把该广播接收者的进程启动起来 - 使用代码注册
需要使用广播接收者时,执行注册的代码;不需要时,解除注册
特殊的广播接收者
安卓中有一些广播接收者,必须使用代码注册,清单文件注册是无效的
1). 屏幕锁屏与解锁
2). 电量改变
使用服务注册广播接收者
1 | public class ScreenReceiver extends BroadcastReceiver{ |
自定义服务代码中: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
27public MyService extends Service{
ScreenReceiver receiver;
public IBinder onBind(Intent intent){
return null;
}
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);
}
public void onDestory(){
super.onDestory();
unregisterReceiver(receiver);
}
}
服务的分类
- 本地服务
服务与启动服务的activity在同一个进程中 - 远程服务
服务与启动服务的activity不在同一个进程中
远程服务
1 | public MemoteService extends Service{ |
启动远程服务1
2
3
4
5Intent intent = new Intent();
intent.setAction("远程服务清单文件中配置的action");
//android 5.0 之后需要指定要启动的应用程序包名
intent.setPackage("com.jockio.learnandroid");
startService(intent);
AIDL: 进程间通信
Android Interface Definition Language
步骤:
- 将远程服务的方法抽取成一个单独的接口 java 文件
- 将接口文件的后缀名 java 改为 aidl,
- 在自动生成的接口 .java 文件中,有一个静态抽象类 Stub,它已经继承了 Binder 类,实现了抽取方法后的接口,这个类就是中间人
- 把 aidl 文件复制粘贴到要访问远程服务的项目中
注意:aidl 包名跟原包名必须完全一致 - 在要访问远程服务的项目中,强转中间人对象时,直接使用 Stub.asInterface( Service service)
用 AIDL 模拟支付宝服务
PayInterface.aidl1
2
3interface PayInterface{
public void pay();
}
1 | class PayService extends 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
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{
//连接服务成功,第二个参数即为中间人对象
public void onServiceConnected(ComponentName name, IBinder service){
interface = Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName name){
}
}
}
进程优先级补充
前台进程
- 拥有一个正在与用户进行交互的activity(onResume方法调用)的进程
- 拥有一个与正在和用户交互的activity绑定的服务的进程
- 拥有一个正在“运行于前台”的服务–服务的startForeground方法调用
- 拥有一个正在执行以下三个生命周期方法中任意一个的服务
onCreate() onStart() onDestory - 拥有一个正在执行 onReceive 方法的广播接收者的进程
可见进程
- 拥有一个不在前台,但是用户依然可见的activity(onPause方法调用)的进程
- 拥有一个与可见(或前台)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.xml1
2
3
4<?xml version="1.0" encoding="utf-8">
<resources>
<string name="app_name">hello</string>
</resources>
新建文件夹 values-zh, 新建strings.xml1
2
3
4<?xml version="1.0" encoding="utf-8">
<resources>
<string name="app_name">你好</string>
</resources>