转载请注明本文出自Cym的博客(http://blog.youkuaiyun.com/cym492224103),谢谢支持!
服务(service)
服务没有可视化的用户界面,而是在一段时间内在后台运行。比如说,一个服务可以在用户做其它事情的时候在后台播放背景音乐、从网络上获取一些数据或者计算一些东西并提供给需要这个运算结果的activity使用。每个服务都继承自Service基类。
服务的启动方式和生命周期
- //第一种启动方式
- startService();
生命周期:onCreate()-->onStart()-->onDestory()
访问者和服务是没有任何关系的
- //第二种启动方式
- bindService();
生命周期:onCreate()-->onBind()-->onUnbind()-->onDestory()
访问者和服务是绑定在一起的。如果访问者退出。服务一定要停止。同生共死。
访问者可以调用服务里面的方法
服务只会启动一个。
如果启动的服务再次启动也不会在启动
一个调用者只能和服务绑定一次。
如果绑定的服务再次绑定也不会在绑定
startService()启动的服务,只有stopService()才能停止。
bindService()启动的服务,只有unBindService()才能停止
使用服务
当activity要使用到服务里面的方法的时候。我们就应该使用bingService
不需要使用服务里面的方法发时候,我们就使用startService
startService
- //新建类继承Service
- public class MyService extends Service{}
- //在AndroidManifes.xml文件中注册Service
- <service android:name=".MyService">
- <intent-filter >
- <action android:name="com.huaao.service.MyService"/>
- </intent-filter>
- </service>
- //启动服务,参数是一个指向服务的意图
- startService(Intent service);
- //关闭服务,参数是一个指向服务的意图
- stopService(Intent name);
bindService
IBinder:这是一个能进行远程操作对象的基接口
- //服务连接实例
- ServiceConnection conn = newServiceConnection() {
- //服务断开连接时调用
- @Override
- public voidonServiceDisconnected(ComponentName name) {
- }
- //服务连接时调用
- //第二个参数是Ibinder,是负责和服务通信的
- @Override
- public void onServiceConnected(ComponentNamename, IBinder service) {
- }
- };
- //绑定服务
- bindService(Intent service,ServiceConnection conn, int flags);
- //解绑服务
- unbindService(ServiceConnection conn);
本地的音乐播放器
activity和serivce是在同一个应用
播放功能和暂停功能:
此时我们不需要用到服务里面的方法所以我们用startService
Activity:
- public class MainActivity extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- public void start(View v) {
- startService(new Intent(this, MyService.class));
- }
- public void stop(View v) {
- stopService(new Intent(this, MyService.class));
- }
- }
让音乐onCreate的时候就开启,onDestroy就关闭
MyService:
- public class MyService extends Service {
- private MediaPlayer mp;
- @Override
- public void onCreate() {
- // TODO Auto-generated method stub
- super.onCreate();
- try {
- // 媒体播放器
- mp = new MediaPlayer();
- // 重置
- mp.reset();
- File file = new File(Environment.getExternalStorageDirectory(),
- "xqx.mp3");
- // 设置播放文件
- mp.setDataSource(file.getAbsolutePath());
- // 准备
- mp.prepare();
- // 播放
- mp.start();
- } catch (Exception e) {
- // TODO Auto-generated catchblock
- e.printStackTrace();
- }
- }
- @Override
- public IBinder onBind(Intentintent) {
- // TODO Auto-generated method stub
- return null;
- }
- @Override
- public void onDestroy() {
- // TODO Auto-generated method stub
- super.onDestroy();
- // 停止播放
- mp.stop();
- }
如果我们需要添加几个功能:比如暂停,继续功能。这个时候我们就应该用到bindService
Activity:
- public class MainActivity extends Activity{
- private ServiceConnection conn;
- /**Called when the activity is first created. */
- private MusicI musici; // 接口
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- //使用服务来播放音乐
- //播放
- public void play(View v){
- Intent service = new Intent(this,MyService.class);
- conn = new MyServiceConnection();
- bindService(service, conn,Context.BIND_AUTO_CREATE);
- }
- //暂停
- public void pause(View v){
- musici.pause();
- }
- //继续播放
- public void continue_play(View v){
- musici.continue_play();
- }
- //停止
- public void stop(View v){
- Intent service = new Intent(this,MyService.class);
- unbindService(conn);
- }
- private class MyServiceConnection implements ServiceConnection{
- @Override
- public voidonServiceConnected(ComponentName name, IBinder service) {
- //TODO Auto-generated method stub
- musici = (MusicI) service;
- }
- @Override
- public voidonServiceDisconnected(ComponentName name) {
- //TODO Auto-generated method stub
- }
- }
- }
Interface:
- public interface MusicI {
- // 暂停
- public void pause();
- // 继续播放
- public void continue_mp();
- }
MyService:
- public class MyService extends Service {
- private MediaPlayer mp;
- @Override
- public void onCreate() {
- // TODO Auto-generated method stub
- super.onCreate();
- try {
- // 媒体播放器
- mp = new MediaPlayer();
- // 重置
- mp.reset();
- File file = new File(Environment.getExternalStorageDirectory(),
- "xqx.mp3");
- // 设置播放文件
- mp.setDataSource(file.getAbsolutePath());
- // 准备
- mp.prepare();
- // 播放
- mp.start();
- } catch (Exception e) {
- // TODO Auto-generated catchblock
- e.printStackTrace();
- }
- }
- @Override
- public IBinder onBind(Intentintent) {
- // TODO Auto-generated method stub
- return new MyIBinder();
- }
- @Override
- public void onDestroy() {
- // TODO Auto-generated method stub
- super.onDestroy();
- // 停止播放
- mp.stop();
- }
- class MyIBinder extends Binder implements MusicI {
- @Override
- public void pause() {
- // TODO Auto-generatedmethod stub
- service_pause();
- }
- @Override
- public voidcontinue_mp() {
- // TODO Auto-generatedmethod stub
- service_continue();
- }
- }
- public void service_pause() {
- mp.pause();
- }
- public void service_continue() {
- mp.start();
- }
- }
远程的音乐播放器
activity和serivce是不在同一个应用:
Service:
为了让跨进程访问服务。我们使用了aidl
项目菜单的视图(Package Explorer)切换成 Navigator 视图
将接口后缀改成 .aidl 语言会被编译器自动编译生成java代码
在切换到Package Explorer 在gen文件夹下面就可以看到,
接口类会自定义一个抽象的Stub然后继承Binder实现当前接口
public static abstract class Stub extends android.os.Binder implements com.cym.inter.MusicI
所以服务里面的MyIBinder 只要继承Stub 即可;
class MyIBinder extends Stub
Xml:
- <service android:name=".MyService">
- <intent-filter >
- 要配置名字让其他应用访问
- <action android:name="com.cym.myservice"/>
- </intent-filter>
- </service>
Client:
需要相同的文件aidl 就像是一份合同。调用者和访问都需要。并且他们所有都必须是一样的。包名都需要一样。
- bindService(newIntent("com.cym.myservice"), mc, Context.BIND_AUTO_CREATE);
- public class MyServiceConnection implements ServiceConnection{
- @Override
- public voidonServiceConnected(ComponentName name, IBinder service) {
- // Stub提供了asInterface方法 加载服务端返回的Binder对象
- m = Stub.asInterface(service);
- }
- @Override
- public voidonServiceDisconnected(ComponentName name) {
- // TODO Auto-generated methodstub
- }
- }
不仅可以使用无参的方法还可以使用有参的方法
void seekTo(in MusicInfo info);
传入实体, 但是实体要是 implements Parcelable
- public class MusicInfo implements Parcelable {
- public int position;
- @Override
- public int describeContents() {
- // TODO Auto-generated method stub
- return 0;
- }
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- // TODO Auto-generated method stub
- dest.writeInt(position);
- }
- public static finalParcelable.Creator<MusicInfo> CREATOR = new Parcelable.Creator<MusicInfo>() {
- public MusicInfo createFromParcel(Parcel in) {
- return new MusicInfo(in);
- }
- public MusicInfo[] newArray(int size) {
- return new MusicInfo[size];
- }
- };
- public MusicInfo() {
- super();
- }
- public MusicInfo(int position) {
- super();
- this.position = position;
- }
- private MusicInfo(Parcel in) {
- this.position = in.readInt();
- }
- }
Dial文件里面应用的对象也要是dial格式的;
- MusicInfo.aidl:
- packagecom.cym.domain;
- parcelableMusicInfo;
- MusicI.aidl:
- import com.cym.domain.MusicInfo;
- interface MusicI {
- void pause();
- void continue_mp();
- void seekTo(in MusicInfo info);
- }
aidl
进程间通信需要用到aidl(AndroidInterface ition Language)语言
aidl的作用:跨进程访问服务
如果在android里面要实现进程间通信,
aidl语言是一种新的语言。该语言会被编译器自动编译生成java代码。
Xxx.aidl
aidl 就像是一份合同。调用者和访问都需要。并且他们所有都必须是一样的。包名都需要一样。
特点:
1 很多关键字不认识 public
2 对于基本类型都支持
3 但不支持定义javabean
如果要支持就必须实现Parcelable接口
4 如果有参数必须做明确的说明。in out inout
framework开发
曾经做过framework层开发,一般都是对aidl文件进行一些修改。因为原生的接口有的功能是无法实现一些效果的。比如双卡双待机。有2张sim,
挂断电话的时候,一般就是对方法进行扩展,也就是添加几个参数。
服务里面也不能做耗时的操作。
要耗时。开子线程。
Service进程的重要级别
Android系统试图尽可能长地保持一个应用程序进程,但是当内存低时它最终还是需要移除旧的进程。为了决定保持那个进程及杀死那个进程,android将每个进程放入了一个基于运行于其中的组件的重要性等级和这些组件的状态。重要性最低的进程首先被杀死,然后是其次,以此类推。总共有5个层次级别。
1 前台进程:用户当前工作所需要的。一个进程如果满足下列任何条件被认为是前台进程:
1.1它正运行这个一个正在与用户交互的活动(Activity对象的onResume()方法已经被调用)
1.2它寄宿了一个服务,该服务与一个与用户交互的活动绑定
1.3它有一个Service对象执行它的生命周期回调(onCreate()、onStart()、onDestory())
1.4它有一个BroadcastReceiver对象执行他的onReceive()方法
2 可视进程:他没有任何前台组件,但是仍然能影响用户在屏幕上看到东西。一个进程满足下面任何一个条件都被认为是可视进程
2.1 它寄宿着一个不是前台的活动,但是它对用户仍可见(onPause()方法已经被调用)
2.2 它寄宿着一个服务,该服务绑定到了一个可视的活动
3 服务进程:它是一个运行着一个用startService()方法启动的服务,并且该服务没有落入上面2种分类。虽然服务进程没有直接关系到任何用户可见的,它们通常做用户关心的事(例如:在后台播放mp3或者从网络上下载数据)
4 后台进程:一个保持着一个当前对用户不可视的活动(已经调用Activity对象的onStop()方法)。这些进程没有直接影响用户体验,并且可以在任何时候被杀以收回内存用于一个前台、可视、服务进程。一般地有很多后台进程运行着,因此它们保持在一个LRU(least recently used,即最近最少使用,如果你对操作系统很熟悉,跟内存的页面置换算法LRU一样。)列表以确定最近使用过最多的活动的进程最后被杀。如果一个活动执行正确生命周期方法,且捕获它当前的状态,杀掉它对用户的体验没有伤害的影响。
5 空进程:是一个没有保持活跃的应用程序组件的进程。保持这个进程可用的唯一原因是作为一个cache以提高下次启动组件的速度。系统进程杀死这些进程,以在进程cache和潜在的内核cache之间平衡整个系统资源。
显示电话归属地及设置黑名单
工程结构:
所需权限:
- <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
- <uses-permission android:name="android.permission.READ_CONTACTS"/>
- <uses-permission android:name="android.permission.CALL_PHONE"/>
- <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
启动一个服务,
服务里面首先得到电话管理器,窗体管理器,布局管理器。
给电话管理器监听电话状态,如果是来电的话,我们就去查
询数据库什么号码相应的地区,以及是否是联系人,然后显示相应信息。
如果是黑名单的话,那我们就要调用电话服务的一个方法,想要拿到电话服务
返回的IBinder我们就必须在源码里面找到ServiceManager管理所有服务的
类,但是由于ServiceManager没有对外公开,所以我们只能用放射技术使用它的
getService方法,得到电话的服务的IBinder对象,然后转化成ITelephony(这个接口要在源码里面找到他的aidl文件因为是调用它的服务,以及要找到他接口里面使用的实体类dial)
接口就可以使用他的挂断电话方法了,挂掉电话之后删除黑名单号码通话记录,由于通话记录是在删除之后系统在添加所以我们要使用监听
- // 删除通话记录(通话记录的存储是异步的,可以使用ContentObserver)
- Uri uri = Calls.CONTENT_URI;
- getContentResolver().registerContentObserver(uri,false, new MyContentObserver(newHandler(), incomingNumber) );
- private class MyContentObserver extendsContentObserver{
- private String number;
- publicMyContentObserver(Handler handler, String number) {
- super(handler);
- this.number = number;
- // TODO Auto-generated constructor stub
- }
- @Override
- public void onChange(boolean selfChange) {
- // TODO Auto-generated method stub
- super.onChange(selfChange);
- Uri uri = Calls.CONTENT_URI;
- String where = Calls.NUMBER + "= ?";
- String[] selectionArgs =new String[]{number};
- getContentResolver().delete(uri,where, selectionArgs);
- // 取消监听
- getContentResolver().unregisterContentObserver(this);
- }
- }
电话内容提供者删除方法里面有通知监听事件,所以能启动我们自己写的监听。
我们想已开机就使用该功能我们还可以设置一个广播接受者:开机启动广播,广
播在调用服务
- <receiver android:name=".MyBroadcastReceiver">
- <intent-filter >
- <!-- 订阅开机广播-->
- <action android:name="android.intent.action.BOOT_COMPLETED"/>
- </intent-filter>
- </receiver>
- public class MainActivity extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- startService(new Intent(this, MyService.class));
- }
- }
- public class MyService extends Service {
- /*
- * 服务放回过来的IBinder那是不是就可以挂断电话(通过Ibinder调用服务内的方法)
- * 如果要调用该方法我们就应该找到一个aidl文件文件与之对应‘ frameworks\base\telephony\
- * java\com\android\internal\telephony\ITelephony.aidl
- *
- * 那么我们怎么拿到电话服务呢? android 里面所有的服务都是被一个类来进行管理
- *frameworks\base\core\java\android\os\ServiceManager
- * public static IBindergetService(String name) {
- try {
- IBinder service = sCache.get(name);
- if (service != null) {
- return service;
- } else {
- returngetIServiceManager().getService(name);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "error ingetService", e);
- }
- return null;
- }
- */
- private WindowManager wm;
- private LayoutInflater mInflater;
- private View view;
- // 判断是否显示了来电提醒
- boolean flag = false;
- @Override
- public void onCreate() {
- // TODO Auto-generated method stub
- super.onCreate();
- // 得到电话管理器
- TelephonyManager tm =(TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- // 窗体管理器
- wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
- // 布局加载器
- mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- // 监听电话的呼叫状态
- tm.listen(new MyPhoneStateListener(),
- PhoneStateListener.LISTEN_CALL_STATE);
- }
- private class MyPhoneStateListener extends PhoneStateListener {
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- super.onCallStateChanged(state,incomingNumber);
- switch (state) {
- case TelephonyManager.CALL_STATE_IDLE: // 闲置
- if (flag) {
- wm.removeView(view);
- flag = false;
- }
- break;
- case TelephonyManager.CALL_STATE_OFFHOOK: // 通话
- break;
- case TelephonyManager.CALL_STATE_RINGING: // 响铃
- // 黑名单
- if (incomingNumber.equals("110")) {
- endCall(incomingNumber);
- return;
- }
- // 拿Toast显示的源代码
- // 布局参数
- WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- // 高
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
- // 宽
- params.width = WindowManager.LayoutParams.WRAP_CONTENT;
- // 标识
- params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
- // 格式
- params.format = PixelFormat.TRANSLUCENT;
- // 类型和Toast一样
- params.type = WindowManager.LayoutParams.TYPE_TOAST;
- // 加载布局
- view = mInflater.inflate(R.layout.address, null);
- // 得到布局里面的控件
- TextView tv_number =(TextView) view
- .findViewById(R.id.tv_number);
- TextView tv_address =(TextView) view
- .findViewById(R.id.tv_address);
- // 先查询是否是联系人
- String name = null;
- // 得到查询联系人的Uri
- Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
- Uri.encode(incomingNumber));
- // 查询结果
- String[] projection = new String[] { PhoneLookup.DISPLAY_NAME };
- // 去数据库查询得到查询结构
- Cursor c =getContentResolver().query(uri, projection, null,
- null, null);
- if (c.moveToFirst()) {
- name = c.getString(0);
- }
- // 关闭
- c.close();
- // 如果查询到了就显示名字,如果查询到那么就显示号码
- if (name == null) {
- tv_number.setText(incomingNumber);
- } else {
- tv_number.setText(name);
- }
- // 查询号码归属地
- String address =AddressService.getAddress(incomingNumber);
- tv_address.setText(address);
- // 显示加载到窗体里面
- wm.addView(view, params);
- flag = true;
- break;
- }
- }
- private void endCall(StringincomingNumber) {
- // 由于ServiceManager没有对外公开
- // 但是我们知道它的类名,所以我们使用放射技术
- try {
- // 得到该类
- Class clazz =Class.forName("android.os.ServiceManager");
- // 拿到该方法
- Method method =clazz.getMethod("getService", String.class);
- // 使用该方法(拿到电话的服务返回的IBinder)
- IBinder ibinder =(IBinder)method.invoke(null, Context.TELEPHONY_SERVICE);
- // 通过ITelephony接口的Stub.asInterface 我们就可以直接使用该接口的方法了,也就是调用电话的服务内方法
- ITelephony itelephony =ITelephony.Stub.asInterface(ibinder);
- // 挂断电话
- itelephony.endCall();
- // 删除通话记录(通话记录的存储是异步的,可以使用ContentObserver)
- Uri uri = Calls.CONTENT_URI;
- getContentResolver().registerContentObserver(uri,false, new MyContentObserver(new Handler(), incomingNumber));
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- private class MyContentObserver extends ContentObserver{
- private String number;
- public MyContentObserver(Handlerhandler, String number) {
- super(handler);
- this.number = number;
- // TODO Auto-generated constructor stub
- }
- @Override
- public void onChange(boolean selfChange) {
- // TODO Auto-generated method stub
- super.onChange(selfChange);
- Uri uri = Calls.CONTENT_URI;
- String where = Calls.NUMBER + "= ?";
- String[] selectionArgs =new String[]{number};
- getContentResolver().delete(uri,where, selectionArgs);
- // 取消监听
- getContentResolver().unregisterContentObserver(this);
- }
- }
- @Override
- public IBinder onBind(Intentintent) {
- // TODO Auto-generated method stub
- return null;
- }
- }
应用到了Service:
- public class AddressService {
- public static String getAddress(String number) {
- String address = null;
- // 得到数据库
- File file = new File(Environment.getExternalStorageDirectory(),
- "address.db");
- SQLiteDatabase db =SQLiteDatabase.openDatabase(file.getAbsolutePath(),
- null, SQLiteDatabase.OPEN_READONLY);
- if (db.isOpen()) {
- // 判断号码是手机还是电话
- String regularExpression = "^1[358]\\d{9}$";
- // 如果是手机号码
- if(number.matches(regularExpression)) {
- // 那么就查询根据手机号码前7位
- String prefix_num =number.substring(0, 7);
- String selection = "mobileprefix =?";
- String[] selectionArgs =new String[] { prefix_num };
- Cursor c = db.query("info", new String[] { "city" }, selection,
- selectionArgs, null, null, null);
- if (c.moveToFirst()) {
- address = c.getString(0);
- }
- c.close();
- } else {
- // 不是 手机号码就是 电话号码
- // 10位 3区号 + 7位的号码
- if (number.length() == 10) {
- String prefix_num = number.substring(0, 3);
- String selection = "area = ?";
- String[] selectionArgs = new String[] { prefix_num };
- Cursor c = db.query("info", new String[] { "city" }, selection,
- selectionArgs, null, null, null);
- if(c.moveToFirst())
- {
- address =c.getString(0);
- }
- c.close();
- } else if (number.length() == 11) {
- String prefix_num = number.substring(0, 3);
- String selection = "area = ?";
- String[] selectionArgs = new String[] { prefix_num };
- Cursor c = db.query("info", new String[] { "city" }, selection,
- selectionArgs, null, null, null);
- if(c.moveToFirst())
- {
- address =c.getString(0);
- }
- c.close();
- String prefix_num1 = number.substring(0, 4);
- String selection1 = "area = ?";
- String[] selectionArgs1 = new String[] { prefix_num1 };
- Cursor c1 = db.query("info", new String[] { "city" }, selection1,
- selectionArgs1, null, null, null);
- if(c1.moveToFirst())
- {
- address =c1.getString(0);
- }
- c1.close();
- }else if(number.length() == 12) // 12位 4区号 + 8位的号码
- {
- String prefix_num1 = number.substring(0, 4);
- String selection1 = "area = ?";
- String[] selectionArgs1 = new String[] { prefix_num1 };
- Cursor c1 = db.query("info", new String[] { "city" }, selection1,
- selectionArgs1, null, null, null);
- if(c1.moveToFirst())
- {
- address =c1.getString(0);
- }
- c1.close();
- }else if(number.length() == 7 ||number.length() == 8){
- address = "本地号码";
- }else if(number.length() == 3){
- address = "紧急号码";
- }else if(number.length() == 4){
- address = "模拟器";
- }
- }
- // 关闭数据库连接
- db.close();
- }
- if(address == null){
- address = "未知号码";
- }
- return address;
- }
- }
课后问题
服务有什么特点?
服务没有可视化的用户界面,而是在一段时间内在后台运行。
服务只会启动一个。
如果启动的服务再次启动也不会在启动
一个调用者只能和服务绑定一次。
如果绑定的服务再次绑定也不会在绑定
startService()启动的服务,只有stopService()才能停止。
bindService()启动的服务,只有unBindService()才能停止
服务启动方式有几种,区别是什么?
startService
Bindservice
远程服务需要使用什么语言?
aidl