學習Android過程中遇到的問題及解決方法——電話監聽
也許有時你會有這樣一個需求:通電話時有一個重要的事需要記下來或者和一個陌生人特別是大騙子通話時,這是就想如果能把通話錄下來就方便多了。(這才是我寫這個程式碼的目的!!!)
在此過程中,犯了一個很大的錯誤。對電話狀態還不熟悉就開始程式設計,使得我就算編寫正確也出現各種bug。
先將程式碼列出來,供大家參考,然後解釋錯誤和相關知識。
activity_main.xml:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"3 xmlns:tools="http://schemas.android.com/tools" 4 android:id="@+id/container" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:orientation="vertical" 8 tools:context="com.lgqchinese.homework3.MainActivity" > 9 10 <Button11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content" 13 android:text="開啟服務" 14 android:id="@+id/btn_startService" 15 /> 16 17 <Button 18 android:layout_width="wrap_content" 19 android:layout_height="wrap_content"20 android:text="關閉服務" 21 android:id="@+id/btn_stopService" 22 /> 23 24 </LinearLayout>
MainAvtivity.java:
/** * 在這裡啟動服務 */ import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity implements OnClickListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn_startService = (Button) findViewById(R.id.btn_startService); Button btn_stopService = (Button) findViewById(R.id.btn_stopService); //監聽按鈕點選 btn_startService.setOnClickListener(this); btn_stopService.setOnClickListener(this); } @Override public void onClick(View v) { Intent service = new Intent(getApplicationContext(), MyService.class); switch (v.getId()) { case R.id.btn_startService: startService(service); // bindService(service, conn, flags 為了呼叫服務裡面方法 break; case R.id.btn_stopService: stopService(service); break; } } }
MyServier.java:
package com.lgqchinese.homework3; import android.app.Service; import android.content.Intent; import android.media.MediaRecorder; import android.os.IBinder; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import java.io.IOException; public class MyService extends Service { private MediaRecorder recorder; public MyService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return null; } @Override public void onCreate() { // TODO Auto-generated method stub System.out.println("onCreate"); // 監聽電話狀態 TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); tm.listen(new myListen(), PhoneStateListener.LISTEN_CALL_STATE); super.onCreate(); } public class myListen extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { // TODO Auto-generated method stub /** * Callback invoked when device call state changes. * * @see TelephonyManager#CALL_STATE_IDLE * @see TelephonyManager#CALL_STATE_RINGING * @see TelephonyManager#CALL_STATE_OFFHOOK */ switch (state) { case TelephonyManager.CALL_STATE_IDLE: // 空閒狀態 System.out.println("CALL_STATE_IDLE: //空閒狀態"); if (recorder != null) { recorder.stop(); recorder.reset(); // You can reuse the object by going back to// setAudioSource() step recorder.release(); // Now the object cannot be reused } break; case TelephonyManager.CALL_STATE_RINGING: // 響鈴狀態 System.out.println("CALL_STATE_RINGING: //響鈴狀態"); recorder = new MediaRecorder(); //音訊來源 System.out.println("聲音來源"); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); //音訊的輸出格式 recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); //3gp System.out.println("輸出格式"); //設定音訊編碼格式 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); System.out.println("編碼格式"); //音訊輸出檔案地址 recorder.setOutputFile("/mnt/sdcard/luyin.3gp"); System.out.println("檔案路徑"); //準備錄音 try { recorder.prepare(); System.out.println("正在準備"); } catch (IllegalStateException e) { // TODO Auto-generated catch block System.out.println("IllegalStateException異常"); e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println("IOException異常"); } break; case TelephonyManager.CALL_STATE_OFFHOOK: // 接聽狀態 System.out.println("CALL_STATE_OFFHOOK: //接聽狀態"); //開始錄音 recorder.start(); // Recording is now started break; } super.onCallStateChanged(state, incomingNumber); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub System.out.println("onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { // TODO Auto-generated method stub System.out.println("onDestroy"); super.onDestroy(); } }
Ok,程式碼完成。現在就讓我們拔打電話來試試吧。(此時很多讀過相關文件的小白一定都在笑話我吧)
咦~~,出現這麼個問題:
為啥不行!為啥不行!為啥不行!開始除錯:
找到了這個:
即在
//開始錄音
recorder.start(); // Recording is now started
出現問題,但裡面的system.out也執行了呀
然後你還會覺得是start()的問題嗎?——會!
應該會發現在響鈴狀態裡面的語句都沒執行。所以在start()前面的一些必備方法並沒有執行,導致錄音失敗。
我把響鈴狀態裡面的語句拿到空閒狀態,再撥打電話,成功了。。。。。不是真正的成功,雖然可以錄音。
此時,就在此時,我還沒有閱讀有關狀態的知識。
我進入CALL_STATE_RINGING所在類,發現裡面全部標紅:
然後。。我。。把sdk解除安裝重灌一邊——(此時智商已經下線了,明明是Android Studio還沒載入完)
Ok。Ok,趁這空吃個晚飯,回來後想到了錯誤:
經過閱讀發現,響鈴狀態,即CALL_STATE_RINGING並不是打電話的狀態,那就是接電話的狀態了。
敲了半天,不,敲了一天,原來是接電話。結果如下:
由於沒有理解電話狀態程式碼的含義,明明對的程式碼還在不斷的除錯。以後要避免這種低階的錯誤。
下面需要記錄下狀態碼的含義:
電話狀態:
狀態碼所在類是TelephonyManager:
CALL_STATE_IDLE 無任何狀態時
CALL_STATE_OFFHOOK 接起電話時
CALL_STATE_RINGING 電話進來時
資料活動狀態:
DATA_ACTIVITY_IN 資料連線狀態:活動,正在接受資料
DATA_ACTIVITY_OUT 資料連線狀態:活動,正在傳送資料
DATA_ACTIVITY_INOUT 資料連線狀態:活動,正在接受和傳送資料
DATA_ACTIVITY_NONE 資料連線狀態:活動,但無資料傳送和接受
資料連線狀態:
DATA_CONNECTED 資料連線狀態:已連線
DATA_CONNECTING 資料連線狀態:正在連線
DATA_DISCONNECTED 資料連線狀態:斷開
DATA_SUSPENDED 資料連線狀態:暫停
網路型別:
NETWORK_TYPE_CDMA 網路型別為CDMA
NETWORK_TYPE_EDGE 網路型別為EDGE
NETWORK_TYPE_EVDO_0 網路型別為EVDO0
NETWORK_TYPE_EVDO_A 網路型別為EVDOA
NETWORK_TYPE_GPRS 網路型別為GPRS
NETWORK_TYPE_HSDPA 網路型別為HSDPA
NETWORK_TYPE_HSPA 網路型別為HSPA
NETWORK_TYPE_HSUPA 網路型別為HSUPA
NETWORK_TYPE_UMTS 網路型別為UMTS
在中國,聯通的3G為UMTS或HSDPA,移動和聯通的2G為GPRS或EGDE,電信的2G為CDMA,電信的3G為EVDO
移動終端的型別:
PHONE_TYPE_CDMA 手機制式為CDMA,電信
PHONE_TYPE_GSM 手機制式為GSM,移動和聯通
PHONE_TYPE_NONE 手機制式未知
移動終端:
SIM_STATE_ABSENT SIM卡未找到
SIM_STATE_NETWORK_LOCKED SIM卡網路被鎖定,需要Network PIN解鎖
SIM_STATE_PIN_REQUIRED SIM卡PIN被鎖定,需要User PIN解鎖
SIM_STATE_PUK_REQUIRED SIM卡PUK被鎖定,需要User PUK解鎖
SIM_STATE_READY SIM卡可用 * SIM_STATE_UNKNOWN SIM卡未知
打電話的功能以後再寫。網上很多一起寫了,而且都很好。我寫一遍主要也是提高自己的水平。
這就完了,得做點什麼。我說過了,這才是我的目的。
新建一個類:StartPhoneReceiver.java用來進行開機自啟 ,按一下開啟服務多不方便呀
package com.lgqchinese.homework3; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; public class StartPhoneReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // TODO: This method is called when the BroadcastReceiver is receiving // an Intent broadcast. //開啟服務 Intent service = new Intent(context, MyService.class); context.startService(service); } }
在清單檔案裡註冊一下:
<receiver android:name=".StartPhoneReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver>
那你會想看到這個多餘的圖示嗎,在MainActivity.java中加上兩行黃底程式碼:
1 @Override 2 protected void onCreate(Bundle savedInstanceState) { 3 super.onCreate(savedInstanceState); 4 setContentView(R.layout.activity_main); 5 PackageManager p = getPackageManager(); 6 p.setComponentEnabledSetting(getComponentName(), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 7 8 ……………………………… 9 }
好了,這是我的目的。這次的錯誤讓我明白,一定要把文件的知識理解了在動手,不能像今天這麼傻X了