首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 操作系统 >

Android 4.3实现相仿iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果

2013-10-12 
Android 4.3实现类似iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果目前Android的实现是:有

Android 4.3实现类似iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果

目前Android的实现是:有来电时,音乐声音直接停止,铃声直接直接使用设置的铃声音量进行铃声播放。

Android 4.3实现类似iOS在音乐播放过程中如果有来电则音乐声音渐小铃声渐大的效果。



如果要实现这个效果,首先要搞清楚两大问题;

1、来电时的代码主要实现流程。

2、主流音乐播放器在播放过程中,如果有来电,到底在收到了什么事件后将音乐暂停了?


一:来电时的代码主要实现流程

我不是第一研究来电代码的人,网上已经有高手对这个流程剖析过,不是不完全符合我的要求,我参考过的比较有价值的是如下两个文档:

Android来电时停止音乐播放的流程

Android源码分析:Telephony部分–phone进程有参考价值,但都分析很比较粗略,只能自己再一步一步跟源码进一步了解。


因为我做的事情主要是有来电时,修改铃音的效果,所以不用从头跟进,从响铃通知到达Phone.apk中分析起即可,更细可以参考下上面的两个链接。

分析之前,还是有必要对Phone整体的初始化流程有个基本认识,不然后面跟到沟里去。

Phone.apk 的AndroidManifest.xml中的application的说明:

    <application android:name="PhoneApp"                 android:persistent="true"                 android:label="@string/phoneAppLabel"                 android:icon="@mipmap/ic_launcher_phone">
那再看看PhoneApp的实现:

/** * Top-level Application class for the Phone app. */public class PhoneApp extends Application {    PhoneGlobals mPhoneGlobals;    public PhoneApp() {    }    @Override    public void onCreate() {        if (UserHandle.myUserId() == 0) {            // We are running as the primary user, so should bring up the            // global phone state.            mPhoneGlobals = new PhoneGlobals(this);            mPhoneGlobals.onCreate();        }    }    @Override    public void onConfigurationChanged(Configuration newConfig) {        if (mPhoneGlobals != null) {            mPhoneGlobals.onConfigurationChanged(newConfig);        }        super.onConfigurationChanged(newConfig);    }
从源码来看,这个类非常的简单,主要就是对 mPhoneGlobals 属性进行了创建和初始化。再来分析 PhoneGlobals 是如何初始化的:

    public void PhoneGlobals.onCreate() {        ...        if (phone == null) {            // Initialize the telephony framework            PhoneFactory.makeDefaultPhones(this);            // Get the default phone            phone = PhoneFactory.getDefaultPhone();            // Start TelephonyDebugService After the default phone is created.            Intent intent = new Intent(this, TelephonyDebugService.class);            startService(intent);            mCM = CallManager.getInstance();            mCM.registerPhone(phone);            // Create the NotificationMgr singleton, which is used to display            // status bar icons and control other status bar behavior.            notificationMgr = NotificationMgr.init(this);            phoneMgr = PhoneInterfaceManager.init(this, phone);            mHandler.sendEmptyMessage(EVENT_START_SIP_SERVICE);            int phoneType = phone.getPhoneType();            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {                // Create an instance of CdmaPhoneCallState and initialize it to IDLE                cdmaPhoneCallState = new CdmaPhoneCallState();                cdmaPhoneCallState.CdmaPhoneCallStateInit();            }            ...            ringer = Ringer.init(this);            ...                        notifier = CallNotifier.init(this, phone, ringer, new CallLogAsync());            ...      }            ...   }
PhonePhoneGlobals.onCreate()  中干了很多事情,其中我列出的内容,都是我个人觉得比较重要的部分,建议重点看一下,后面会用得到。

PhoneFactory.makeDefaultPhones(this) 和 phone = PhoneFactory.getDefaultPhone() 这两个函数调用,建议也跟进去重点看一下,这里面做了比较重要的事情,

底层来电事件就是通过类似注册表注册机制做好一系列地注册之后,后面有不同事件过来后,将相应的消息分发特定的对象去处理。

我修改了Phone的源码,将日志全部放开,然后将重新编译得到的 Phone.apk 更新到手机中,真实地拨打了一个电话,

日志量比较大,只列出开头的一小部分,具体日志如下:

10-10 21:20:18.862: D/CallNotifier(814): RING before NEW_RING, skipping10-10 21:20:18.862: D/InCallScreen(814): Handler: handling message { what=123 when=0 obj=android.os.AsyncResult@418f38f8 } while not in foreground10-10 21:20:18.862: D/InCallScreen(814): onIncomingRing()...10-10 21:20:20.834: D/CallNotifier(814): PHONE_ENHANCED_VP_OFF...10-10 21:20:20.844: D/CallNotifier(814): RINGING... (new)10-10 21:20:20.844: D/CallNotifier(814): onNewRingingConnection(): state = RINGING, conn = {  incoming: true state: INCOMING post dial state: NOT_STARTED }10-10 21:20:20.844: D/CallNotifier(814): Incoming number is: 0255678123410-10 21:20:20.844: V/BlacklistProvider(814): Query uri=content://blacklist/bynumber/02556781234, match=210-10 21:20:20.864: D/CallNotifier(814): stopSignalInfoTone: Stopping SignalInfo tone player10-10 21:20:20.864: D/CallNotifier(814): - connection is ringing!  state = INCOMING10-10 21:20:20.864: D/CallNotifier(814): Holding wake lock on new incoming connection.10-10 21:20:20.864: D/PhoneApp(814): requestWakeState(PARTIAL)...10-10 21:20:20.864: D/PhoneUtils(814): PhoneUtils.startGetCallerInfo: new query for phone number......
从上面的日志可以看出,当有来电时,其实是 PHONE_NEW_RINGING_CONNECTION 这个事件交给了Phoe应用来处理了。

底层的流程大致如下,更详细的参见《Android来电时停止音乐播放的流程》:

        1).    RIL在接收到请求的时候会向GsmCallTracker广播消息,而GsmCallTracker在接收到该消息的时候会继续                向上层的CallManager广播        2).    CallManager在这个只充当了一个转播者的角色,它会继续将消息传播给CallNotifier        3).    而CallNotifier接收到消息后会判断来电是否需要查询,不查询则会直接设置声音模式(包含停止音乐播放并                开始响铃)并显示来电界面等待用户的下一步操作; 若需要查询则会在查询接收后执行此部分过程 从代码层面上,这个是如何体现的呢?

1、RIL怎么将消息传递给 GsmCallTracker 的,这个没有研究,跳过。

2、GsmCallTracker如何将消息向上层传播的?来看看代码:GsmCallTracker这个类本身是继承自Handler这个类的,看看handleMessage (Message msg)实现:

handleMessage (Message msg) {        AsyncResult ar;        switch (msg.what) {            case EVENT_POLL_CALLS_RESULT:                ar = (AsyncResult)msg.obj;                if (msg == lastRelevantPoll) {                    if (DBG_POLL) log(                            "handle EVENT_POLL_CALL_RESULT: set needsPoll=F");                    needsPoll = false;                    lastRelevantPoll = null;                    handlePollCalls((AsyncResult)msg.obj);                }            break;            ...        }    }

再看看handlePollCalls()的实现:

    protected synchronized void    handlePollCalls(AsyncResult ar) {        ...        if (newRinging != null) {            phone.notifyNewRingingConnection(newRinging);        }        ...        updatePhoneState();        ...    }

重点关注有来电相关的代码, GSMPhone.notifyNewRingingConnection(newRinging); -->  PhoneBase.notifyNewRingingConnectionP()

     --> PhoneBase.mNewRingingConnectionRegistrants.notifyRegistrants(ar) --> ...
一路跟下去,到 Registrant.internalNotifyRegistrant(),这个是这个 h 到底对应的是哪个Handler呢?

    /*package*/ void    internalNotifyRegistrant (Object result, Throwable exception)    {        Handler h = getHandler();        if (h == null) {            clear();        } else {            Message msg = Message.obtain();            msg.what = what;                        msg.obj = new AsyncResult(userObj, result, exception);                        h.sendMessage(msg);        }    }

我们在前面看的初始化相关的代码的作用就体现出来了,PhoneBase.mNewRingingConnectionRegistrants这个列表中的内容是何时放进去的呢?

    /** Private constructor; @see init() */    private CallNotifier(PhoneGlobals app, Phone phone, Ringer ringer, CallLogAsync callLog) {        mApplication = app;        mCM = app.mCM;        mCallLog = callLog;        mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);        registerForNotifications();        ...
    private void registerForNotifications() {        mCM.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);        ...

mCM就是CallManager对象,CallNotifier在初步化时将自己与PHONE_NEW_RINGING_CONNECTION事件的关系注册到了CallManager的mNewRingingConnectionRegistrants对象中。

    /**     * Notifies when a new ringing or waiting connection has appeared.<p>     *     *  Messages received from this:     *  Message.obj will be an AsyncResult     *  AsyncResult.userObj = obj     *  AsyncResult.result = a Connection. <p>     *  Please check Connection.isRinging() to make sure the Connection     *  has not dropped since this message was posted.     *  If Connection.isRinging() is true, then     *   Connection.getCall() == Phone.getRingingCall()     */    public void registerForNewRingingConnection(Handler h, int what, Object obj){        mNewRingingConnectionRegistrants.addUnique(h, what, obj);    }

CallNotifier也是继承了Handler的,在上面的 internalNotifyRegistrant() 中,最终也是将消息发送给 CallNotifier 对象去处理的,CallNotifier 的 handleMessage() 函数就会被间接地调用了。
下面进入CallNotifier 的 handleMessage(),看看它的实现:

                                sendEmptyMessageDelayed(INCREASE_RING_VOLUME, 200);
} break; case DECREASE_MUSIC_VOLUME: int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (musicVolume > 0) { musicVolume--; mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVolume, 0); sendEmptyMessageDelayed(DECREASE_MUSIC_VOLUME, 200); } break; } } }; }当然,你还要考虑一些细节,比如Music是否正在播放,铃声或音乐的音量大小是否是0,或最大等。

AudioManager中的一些说明,可以参见《Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息》。

当我修改完代码,并怀着十分期待的心情将Phone.apk替换原有的apk后,拨打被叫有来电时,正在播放的音乐一下就停止了,铃音是渐强的,哪里出了问题?

分析清楚这个问题花的时间比之前还要长,有空再写下面的内容吧。


二、主流音乐播放器在播放过程中,如果有来电,到底在收到了什么事件后将音乐暂停了?

 

热点排行