轉載請註明本文出自Cym的博客(http://blog.csdn.net/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