1.1,startService()的缺陷,不能直接调用服务的代码
startService()得不到服务的引用,因为服务是框架new出来的,Activiyi里面是得不到服务的引用.如果直接new 服务类,会出错:报空指针异常,无法获取上下文,自己new出来的服务得不到上下文,也就无法打印Toast信息.(Activity也不可以直接new)
1.2, bindService()绑定服务
可以间接的调用到服务里面的方法,可以与服务进行通讯(通过代理人间接的调用服务里面的方法,直接调用可能会出现风险(篡改数据?))
1.3 通过bindService()调用服务中的方法
①创建服务的类,配置信息....
onBind()方法返回communication channel to ther service 返回服务的通讯频道,在服务成功绑定的时候返回服务中的代理人(中间人)
②创建服务的代理人:
Public class MyBinder extends Binder(IBinder的实现类)/*implements (不推荐)IBinder(会实现大量方法)*/{
创建一个方法,在方法内调用服务类的方法
}
在onBind()方法里返回中间人
③在Activity中,启动服务的时候,接收中间人
Intent service = new Intent(xx,xx);
bindService(service,serviceConnection,flags)
;Service:意图对象
serviceConnection:通讯频道
Int flags:可选的标识:一般选BIND_AUTO_CREATE//如果绑定的服务不存在,就创建它
//创建一个实现类,实现ServiceConnection
//当服务被成功链接的时候调用
onServiceConnected(xx,IBinder(获得的是服务类中return的IBinder对象));
onServiceDisConnected()
注意:在这里可以定义一个成员变量来接收IBinder对象,然后可以在别的地方使用
1.4 绑定服务,调用服务方法的步骤
在服务类中
①编写服务代码,重写onBinder对象,返回自定义的IBinder对象
②在服务内部定义一个IBinder代理人对象(内部类)
③在代理人对象里创建一个方法,间接的调用服务里面的方法
在Activity中
①采用绑定的方法连接到服务
bindService(intent,new Myconn,BIND_AUTO_CREATE)
②在ServiceConnection的实现类中有一个方法,获取到服务类返回的代理人对象
onServiceConnected(xx,xx);
③强制类型转换,把IBinder转成MyBinder类型
④调用代理人里面的方法,相当于间接的调用服务里面的方法
⑤取消绑定服务unbindService(conn对象)
注意:解除绑定的时候不能使用绑定服务new出来的conn.
2.通过接口隐藏代码内部实现的细节
①定义一个接口,接口内定义一个方法,方法名与要隐藏的方法名一致(参数一致)
②需要隐藏的类(私有的)去实现接口,通过接口把这个方法暴露出来
其实返回过来的对象,就是这个被隐藏的类的对象,但是不能直接使用这个类的对象,而要通过这个接口去接收它.父类引用指向子类对象,只能看到重写的方法.
3.绑定服务的生命周期
onBind()//绑定服务的时候调用
onUnBind()//解除服务的时候调用
onCreate()//第一次创建服务的时候调用
onStart()//已被废弃,但是还是可以用,通过startService()方法启动服务的时候调用
onStartCommand()//不会在绑定服务的时候调用
onDestory()//销毁的时候,解除服务的时候会调用
额外①:多次绑定服务,onBind(),onCreate()都只会被执行一次
②开发的时候,是不能多次绑定服务的,如果需要调用服务的方法,就绑定服务,只能绑定一次,服务只可以被解绑一次,多次解绑同一个conn对象,会抛出异常
所以绑定服务,解绑服务,就放在Acivity中的onCreate()和onDestory()中即可
4.两种开启服务方式的区别
4.1采用Start的方式开启服务
服务一旦开启,就长期在后台运行,与开启者(Activity)没有直接的关系,开启者退出了,服务还是在后台长期运行,开启者不能直接调用(可以通过广播间接调用)服务里的方法.
start开启的服务,在系统设置界面里面可以观察到正在运行的服务
4.2 采用bindService的方式绑定服务
绑定服务,如果Activity退出了,服务也会一起销毁,同时可以通过中间人间接的调用服务里面的方法
在系统设置界面里面不可以观察到正在运行的服务
额外:①取消绑定服务之后还是可以继续调用方法,是因为虽然服务被销毁了,但是服务并不会立即被回收,所以中间人的引用对象还是继续存在,所以在销毁的时候,同时要把代理人对象重置为null;
②服务如果开启的同时被绑定,那么服务就停止不了,必须解除绑定才能停止服务.
③混合开启服务的应用:为了保证服务又能长期后台运行,又能调用到服务里面的方法,采用混合的方式开启服务.
请严格按照步骤混合方式编写代码:
①start的方式开启服务(保证后台的长期运行)
②bind的方式绑定服务(调用服务的方法)
③unbind的方式解除绑定服务
④stop的方式停止服务
这样出的BUG就比较少了
要注意的是,绑定服务是需要消耗时间的(异步的),不要在绑定之后马上就调用服务里面的方法,是有可能出现空指针异常的.
5.本地服务和远程服务
本地服务(local service):
服务代码在当前应用程序的内部
远程服务: remote service
服务的代码在另外一个应用程序里
①在远程服务类里,创建类继承service,清单文件配置,过滤器定义action标签,name属性自定义
②调用者的应用中,
创建意图对象,设置setAction(保持一致)
bindService(intent,conn,BIND_AUTO_CREATE);
创建自定义ServiceConntection继承类
③远程服务类中,反馈IBinder代理人,但是想要反馈接口,在调用者的代码里并没有他.
(可不可以通过反射获取?不可以,因为内存空间是相互独立的)
5.2 重要概念
①进程:操作系统分配的独立的内存空间
②IPC:inter process communication 进程间通讯
操作系统在底层分配了一块公共空间(Binder驱动),当一方把数据放置在公共空间中,另一方就可以在公共空间中提取数据
③aidl android interface definition language 安卓接口定义语言,用于进程间通信
5.3 aidl编程
①修改接口.java文件的后缀,aidl
②不需要权限修饰符,因为都是固定public的
③gen下会自动生成对应的java文件
④本地的代理人不再实现接口,而是继承接口中的内部类(这个类是自动生成的)
⑤调用者绑定远程服务,拷贝远程的规范(接口文件.aidl,包名要一致)
⑥在绑定成功的方法中,接口名.Stub.asInterface(service)
5.4 绑定远程服务调用服务方法的流程(远程服务是运行在子线程的,不能直接Toast提示)
①跟本地服务代码编写的流程是一致的,但是不能用显示意图调用(找Class文件)而是通过隐式意图(设置Action动作调用)
②远程文件的接口定义文件,java-aidl
③把接口定义文件的访问权限全部删除,public,private
④原来的代理人extends IBinder implements 接口
现在的代理人 extends 接口.Stub
⑤调用者先把远程服务.aidl文件拷贝到本地应用程序的工程目录里,包名要一致(包名可以看做是一把钥匙,在Binder驱动空间中找到它的途径)
⑥接口引用= 接口引用.Stub.asInterface(service)得到远程服务的代理对象
⑦通过代理对象调用远程服务的方法
5.5 绑定远程服务的应用场景(远程支付)
6.1,案例,模拟支付宝安全支付练习
步骤①定义远程服务,配置清单文件action动作,重写onCreate()ondeStory()方法,定义一个方法实现支付提交的功能(用户名,密码,支付金额,账单),方法内部还有算法加密的操作,这里就不做了,也没有条件来做.最后返回数据结果
②其它操作,定义接口aidl文件,定义代理人.
③使用者,正常的远程调用服务即可.创建意图的时候不再指定classs文件,而是设置动作.再进行绑定服务的操作,其它的类同,调用方法,获取返回值.
(绑定远程服务一定要掌握,比较常见的有在索尼手机的底部有一个人脸识别的服务,可以通过绑定这个服务,把人脸的位置信息反馈至服务器.)
6.2 系统服务:
View.inflate()转换xml为view对象的操作,
底层就是调用了一个getStreamService()调用系统服务的操作.
Zygote(受精卵):安卓手机启动加载的第一个进程.
BatteryService :电池相关的服务
SensorService:传感器相关的服务
PowerManagerService-JNI:电源管理服务
PackgeManager:包管理服务
SystemServier:系统服务...........................................................................................
安卓开机的开机过程,就是一个个服务的开启.
要看安卓的源码,最好跑到内存里面观察代码的执行.然后通过内存中显示的类名查找源码.
6.3 利用系统服务监听通话的状态
①创建一个服务类,配置xml文件,重写onCraete(),onDestory()方法
②新的API TelephonyManager(这个类的实例要通过getStreamService(TELEPHONY_SERVICE)获取.)
③tm.getCallState()//获得通话状态
tm.getDeviceID()//获得入网许可id
tm.getNetworkOperator()//得到运营商的名称
tm.getNetworkType()//获得网络内容联通3G,移动3G之类
.....................
Tm.listen(listener,PhoneStatusListener.LISTEM_CALL_STATE)//电话状态监听器
④创建一个类,继承PhoneStatusListener,自定义监听器
重写onCallStateChange(int state,incomingNumer(来电电话))方法
State:TelephoneManager.CALL_STATE_IDIE //空闲状态
CALL_STATE_RINGING//响铃状态
CALL_STATE_OFFHOOK//通过状态
⑤取消监听器:tm.listen(listener,null);
⑥需要添加权限READ_PHONE_STATE
6.4 多媒体录音机的开发
①添加权限:RECORD_AUDIO,WRITE_EXTERNAL_STORAGE(保存在存储卡中)
②拷贝文档的开始,结束录音功能
修改setAudioSource(MediaRecoder.AudioResource.MIC(这个只录了你的麦克风,)为VOICE_CALL(上行下行的通话信息都录入,但是这个参数在很多手机上不生效,因为国外很多法律规定不能使用双向录音,但是华为手机支持,模拟器是不支持的))
mRecorder.setOutputStream(MediaRecorder.Outputformat.THREE.GPP)
setOutputFile(“文件名”)//SystemCLock.uptimeMills()获取系统时间
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)//音频编码方式
注意:模拟器的音频有问题,长度有问题.
private void startRecording() {
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setOutputFile(mFileName);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
mRecorder.prepare();
} catch (IOException e) {
Log.e(LOG_TAG, "prepare() failed");
}
mRecorder.start();
}
private void stopRecording() {
mRecorder.stop();
mRecorder.release();
mRecorder = null;
}