fmr 收音机源代码相关分

前阵子,刚好遇到个问题:

收听FM,当前频道显示为105.3,拔出耳机.在FM界面选择89.9频道,再次插入耳机收听的FM不是选中的89.9频道,而是105.3频道。

这个的根本原因是拔出耳机后,fmr会监听action为ACTION_HEADSET_PLUG 的广播,然后在activity中禁用相关的view控件(这些控件只能在插入耳机,也就是能正常使用收音机的情况下使用)。但是由于广播会延迟,拔出耳机后并不是立即就会收到广播,而且相关的方法处理也会耗时,所以,拔出耳机后的二到三秒之间是可以操作view的,这个view被用户操作后,值就会变化,但在service解除绑定并被销毁的时候,有些数据是没有被保存的。

因此,当再一次插入耳机之后,按照之前的配置还原,有一定的误差。

解决办法: 可以使用,当拔出耳机后直接退出fm,但这样貌似不太友好,可能客户只是不小心拔出了呢?另外就是处理如何规避广播延迟的影响呢?

基于这个问题,特地看了下fmr的源代码。没有具体去看,只看了个大概。

收音机插拔耳机处理

当耳机插入和拔出,framework层AudioService.java都有广播发出。

对应的广播Action(in Intent.java, android.media.AudioManager.java)

public static final String ACTION_HEADSET_PLUG =android.media.AudioManager.ACTION_HEADSET_PLUG;

public static final String ACTION_HEADSET_PLUG =

           "android.intent.action.HEADSET_PLUG";

 

Fmr在FMRadioService.java中注册耳机插拔的广播。

插拔耳机的广播intent,会携带两个参数,state  取值 0 拔出;1 插入  ,micphone  1  携带micphone的耳机,0 不携带micphone的耳机。

<span style="font-size:12px;">mReceiver = new BroadcastReceiver() {
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (action == null) {
                    return;
                }
                Log.i(TAG, "Received intent: " + action);
                                                                //#########ACTION_HEADSET_PLUG begin
                if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
                    if (mFMServiceState != FM_SERVICE_ON) {
                                                                                                //the service is running good.
                        return;
                    }
                    final boolean headsetPlugin = (intent.getIntExtra("state",
                            0) == 1);//in 1 ,out 0
                    final boolean supportInAntenna = Util
                            .supportInternalAntenna(context);//headset model
 
                    if (DEBUG) Log.d(TAG, "HEADSET is pluged in? : " + headsetPlugin);
                    if (!supportInAntenna && !headsetPlugin) {
                        // The headset is plugged out, stop FMR.
                        Intent Stopintent = new Intent(
                                "android.intent.action.STOP_FM");
                        sendBroadcast(Stopintent);
                        mFMServiceState = FM_SERVICE_OPENED;//when exit the fm
 
                                                                                                //update the widget .
                        if (mAppWidgetProvider != null) {
                            mAppWidgetProvider.notifyChange(
                                    FMRadioService.this, FM_CLOSED);
                        }
 
                        /*enable touch sound when headset plugged out, add by RC */
                        mAudioInterface.enableTouchSound();
                        /*enable touch sound when headset plugged out, add by RC */
                        clearFMService();// >>>
 
                        Toast.makeText(FMRadioService.this,
                                R.string.fmradio_no_headset_in_listen,
                                Toast.LENGTH_LONG).show();
                         //stopSelf(mServiceStartId);
                    }</span>


当FMRadioService.java接收到广播之后,会停止所有活动包括Service。但是,广播有一定的延迟,可能抽出耳机之后,UI界面在短时间内仍然可以操作(大概2s-5s)。

在注册广播代码中,

       iFilter.addAction(Intent.ACTION_HEADSET_PLUG);

       iFilter.addAction(FMAudioInterface.FM_ROUTE_HEADSET);

       iFilter.addAction(FMAudioInterface.FM_ROUTE_LOUDSPEAKER);

有FM_ROUTE_HEADSET和FM_ROUTE_LOUDSPEAKER  与耳机类型相关,耳机插拔后是否允许使用扬声器,通常是只能插入耳机才能收听。

二,监听sd卡插拔

In  Intent.java

public static final String ACTION_MEDIA_UNMOUNTED ="android.intent.action.MEDIA_UNMOUNTED"

<span style="font-size:12px;">mSdCardListener = new BroadcastReceiver() {
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (action == null) {
                    return;
                }
                if (DEBUG) Log.i(TAG, "Received intent: " + action);
                if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
                    if (mFMServiceState == FM_SERVICE_ON) {
                        Intent Stopintent = new Intent(
                                "android.intent.action.STOP_FM");
                        sendBroadcast(Stopintent);
                    }
                }
            }
        };</span>

三 ,aidl (androidinterface definition language)

FMRadio.java

FMRadioService.java

IFMRadioService.aidl

IRemoteServiceCallBack.aidl

AIDL文件主要声明相关接口,供client使用

而这些接口是在对应Service中实现的(Server)。

需要的几个步骤:

1.      创建AIDL文件,和java包在同一个包下面。

2.      对应service实现Stub

3.      客户端使用的Activity创建ServiceConnection,绑定

下面查看fmr中是如何实现的Service和Activity通信的。

FMRService.java   内部实现对应IFMRadioService.Stub()

// Implementation of IFMRadioService AIDL Interface
    private final IFMRadioService.Stub mBinder = new IFMRadioService.Stub() {
        public int getFMServiceStatus() {
            if (DEBUG) Log.i(TAG, "AIDL: getFMServiceStatus: " + mFMServiceState);
            return mFMServiceState;
        }
      ……
}

该binder对象和Service绑定

 @Override
    public IBinder onBind(Intent intent) {
        if (DEBUG) Log.i(TAG, "onBind() called");
        return mBinder;
    }


在客户端的Activity中,需要创建ServiceConnection 实例,实现onServiceConnected和onServiceDisconnected方法。FMRadio.java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "onCreate()");
        super.onCreate(savedInstanceState);
        ……
        // bind to FMRadioService
        bindToService();
        ……
 }


 

另外:

我们在绑定activity到service的时候,调用bindService方法,第一个参数中是个intent意图,参数是需要绑定的Service的报名+类名,第二个参数Context.BIND_AUTO_CREATE 为自动绑定机制,即使你不创建也会绑定。

private boolean bindToService() {
        Log.i(TAG, "Start to bind to FMRadio service");
        return bindService(new Intent("com.pekall.fmradio.FMRADIO_SERVICE"),
                mServConnection, Context.BIND_AUTO_CREATE);
    }


/#######test ###### bind
    private ServiceConnection mServConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                android.os.IBinder service) {
            Log.w(TAG, "onServiceConnected");
            mIsBound = true;
 
            //initialize FMRadioService
            //创建IFMRadioService对象
            mFMService = IFMRadioService.Stub.asInterface(service);
            if (mFMService == null) {
                Log.e(TAG, "onServiceConnected error, mFMService null");
                return;
            }
 
            updateTrackInfo();
            updateDisplayPanel(mCurrentFreq);
            updateSwithcButton();
 
            try {
                mFMService.registerCallback(mCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "", e);
            }
        }
 
        public void onServiceDisconnected(ComponentName className) {
            Log.i(TAG, "onServiceDisconnected");
            if (mFMService == null) {
                Log.e(TAG, "onServiceDisconnected error, mFMService null");
                return;
            }
 
            try {
                mFMService.unregisterCallback(mCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "", e);
            }
 
            mFMService = null;
            finish();
        }
    };


获取手机存储列表。internal or  sdcar

在fmr源码中,看到有个方式是获取手机的存储列表路径。

源代码如下,SDCardInfo.java

 private class SDCardInfo {
        public static final int INTERNAL_SD = 1;
        public static final int EXTERNAL_SD = 2;
        String path;
        String state;
        int type;

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public String getState() {
            return state;
        }

        public void setState(String state) {
            this.state = state;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }

        boolean isMounted() {
            if (Environment.MEDIA_MOUNTED.equals(this.state)) {
                return true;
            }
            return false;
        }

        public String toString() {
            return "[ SDCardInfo: path=" + path + ", state=" + state +
                    ", isMounted =" + isMounted() + ", type is " + type
                    + "]";
        }
    }

private ArrayList<SDCardInfo> initSDCardList() {
        StorageManager storageManager = (StorageManager)
                FMRadio.this.getSystemService(Context.STORAGE_SERVICE);
        ArrayList<SDCardInfo> sdCardInfos = new ArrayList<SDCardInfo>();
        StorageVolume[] storgerVolumeList = storageManager.getVolumeList();
        String[] storgerPaths = storageManager.getVolumePaths();
        if (storgerVolumeList != null) {
            for (int i = 0; i < storgerVolumeList.length; i++) {
                SDCardInfo sdCardInfo = new SDCardInfo();
                sdCardInfo.path = storgerPaths[i];
                boolean isCanRemoved = storgerVolumeList[i].isRemovable();
                if (isCanRemoved) {
                    sdCardInfo.type = SDCardInfo.EXTERNAL_SD;
                } else {
                    sdCardInfo.type = SDCardInfo.INTERNAL_SD;
                }
                sdCardInfo.state = storageManager.getVolumeState(storgerPaths[i]);
                sdCardInfos.add(sdCardInfo);
            }
        }
        Log.i(TAG, "getExternalStorageDirectory = " + Environment.getExternalStorageDirectory());
        Log.i(TAG, "sdCard info = " + sdCardInfos);
        return sdCardInfos;
    }

但是查看了StorageVolume.java后发现,这个类是hide的,sdk里面是用不到的。因此,我们可以使用java的反射机制来调用该类方法,从而达到我们的目的。


public String[] getPaths(Context ctx) {

		StorageManager manager = (StorageManager) ctx
				.getSystemService(Context.STORAGE_SERVICE);
		String path[] = null;
		try {
			Method method = manager.getClass().getMethod("getVolumePaths");
			path = (String[]) method.invoke(manager);

		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			Log.e("getPaths...<<<", "No such method name .<<<<");
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			Log.e("getPaths...<<<", "IllegalArgumentException .<<<<");
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			Log.e("getPaths...<<<", "IllegalAccessException .<<<<");
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			Log.e("getPaths...<<<", "InvocationTargetException .<<<<");
		}

		return path;

	}

比较奇怪为何在fmr里面是可以调用的呢?

你可能感兴趣的:(源码,Android开发)