寫在前面
今天在開發中,有設計新的模塊和功能。
需求是這樣的:在項目產品中會增加一種新的模塊,叫做精選套餐(項目類似于美團,大眾點評,不過更加專注于精品餐吧和套餐的推送,也專注于夜生活相關的內容,叫做《夜夜》app,感興趣可以去應用市場下載了解下)。這一個模塊會涉及到支付, 也就是購買。在這一個項目中只是集成支付寶支付和微信支付這兩種比較主流的線上支付方式。
然而我在完成精選套餐模塊的編寫之后,模擬了用戶購買套餐,向服務器發起了創建訂單的請求,并得到了response,當我把獲得的charge交付給Ping++之后,我發現了一個神奇的想象,就是:
在調起第三方支付的一瞬間,app被kill掉了!被kill了!kill了!
what?what the hell ?what is happening?
這完全是不予許發生的事情好么?而且,更加神奇的是,app被kill之后,第三方支付竟然還很正常的調起了,也可以很正常的支付?。。?/b>
這么詭異的現象,生平僅見?。〔贿^稀奇歸稀奇,問題總是還得解決的。
重啟app之后,去到了訂單中心,發現訂單真的是支付成功了,正安安靜靜的躺在已支付列表里面呢!也就是說支付這一個流程并沒有錯,請求服務器生成回來的訂單以及支付的憑證charge也貌似沒有問題,不然是不會支付成功的。但是app的確是在調起支付的一瞬間被kill的,這無論怎么看都跟支付流程脫不了嫌疑。
竟然在懷疑支付流程,那么就來試一下:
//startActivityForResult(payIntent);
我把發起支付的最后一句代碼注釋了一下,運行之后,發現app很正常,并沒有被kill,當然同時并沒有調起支付。
好吧,確實是跟支付有關系。
那么,到底又是有什么關系呢?
回去翻了一下Ping++的文檔,方向文檔寫著服務端生成的charge是有一定的規格的,例如title不能超過30Unicode字符,content也有限制,description也有限制。那么難道是這一個套餐的中文名字太長然后引發的血案?
但我把支付的憑據charge log出來之后發現,其實都沒有超過限制。也就是說,這個鍋,ping++不背。
然后仔細梳理了一下思緒,想起app被kill是在調起第三方支付的一瞬間發生,也就是說這一瞬間一定發生了什么事情,到時app掛掉了。但是調起支付的一瞬間無非就是app暫時進入后臺,然后調起第三方支付界面,支付完成支付又回到app。那么就是說調起支付一瞬間,app要暫時的進入后臺,之后又要回來,也就是說會調用onSaveInstanceState()對app activity當前的狀態進行保存,app又在這一瞬間被kill,那么問題就出現在onSaveInstanceState()的這一個環節上。
真相已經很接近了
由于想到是在精選套餐的支付環節上才會出現的問題,精選套餐又是新增的模塊,那么問題就很可能出現這上面。
由于項目中也有其他的模塊有支付的動作,于是就去測試了一下。果然,其他所有的支付流程都是正常的。那么罪魁禍首就是精選套餐模塊了。
通過對精選套餐的分析,以及在onSaveInstanceState()進行debug,發現在保存當前activity的時候,保存到變量 mealDetail 終于報錯了同時app掛掉了。在報錯信息總看到,原來是mealDetail 繼承于Serializable,得以可以序列化。然而mealDetail的一個內部類Recommend卻沒有繼承于Serializable。因此在app退入后臺,進行事件狀態保存的一瞬間,對局部變量進行序列化保存時遇到不能序列化的對象從而導致的crash。
所以當我把mealDetail的內部類Recommend繼承于Serializable之后,精選套餐的支付流程果然很正常的執行了。
關于onSaveInstanceState可以點擊查看說明。
基本作用就是
Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它們不同于 onCreate()、onPause()等生命周期方法,它們并不一定會被觸發。當應用遇到意外情況(如:內存不足、用戶直接按Home鍵)由系統銷毀一個Activity時,onSaveInstanceState() 會被調用。但是當用戶主動去銷毀一個Activity時,例如在應用中按返回鍵,onSaveInstanceState()就不會被調用。因為在這種情況下,用戶的行為決定了不需要保存Activity的狀態。通常onSaveInstanceState()只適合用于保存一些臨時性的狀態,而onPause()適合用于數據的持久化保存。
在activity被殺掉之前調用保存每個實例的狀態,以保證該狀態可以在onCreate(Bundle)或者onRestoreInstanceState(Bundle) (傳入的Bundle參數是由onSaveInstanceState封裝好的)中恢復。這個方法在一個activity被殺死前調用,當該activity在將來某個時刻回來時可以恢復其先前狀態。
例如,如果activity B啟用后位于activity A的前端,在某個時刻activity A因為系統回收資源的問題要被殺掉,A通過onSaveInstanceState將有機會保存其用戶界面狀態,使得將來用戶返回到activity A時能通過onCreate(Bundle)或者onRestoreInstanceState(Bundle)恢復界面的狀態。
關于onSaveInstanceState (),是在函數里面保存一些View有用的數據到一個Parcelable對象并返回。在Activity的onSaveInstanceState(Bundle outState)中調用View的onSaveInstanceState (),返回Parcelable對象,
接著用Bundle的putParcelable方法保存在Bundle ?savedInstanceState中。
當系統調用Activity的的onRestoreInstanceState(Bundle savedInstanceState)時,?同過Bundle的getParcelable方法得到Parcelable對象,然后把該Parcelable對象傳給View的onRestoreInstanceState (Parcelable state)。在的View的onRestoreInstanceState中從Parcelable讀取保存的數據以便View使用。
這就是onSaveInstanceState() 和?onRestoreInstanceState() 兩個函數的基本作用和用法。
總結
到最后才發現,原來所謂的bug還是自己的粗心造成的,只是忽略了onSaveInstanceState()函數對于變量的要求從而導致的一系列問題的出現。同時也是自己偷懶,在創建mealDetail 這一個數據model的時候用了AndroidStudio的插件GsonFormat直接進行生成然后卻沒有認真地檢查。(GsonFormat確實是很吊的一款插件,很推薦使用,可以省下大量的時間,當然也要細心的使用,例如我這次的例子)
by the way,擼代碼是一件嚴肅,細致,一絲不茍的事情,真的馬虎不得。這次把這么蠢的crash事件碼了出來,就是讓大家嘲笑一下我,這是對我很好的一個鞭策。同時也幫助一下那些可能也遇到這個問題的童鞋們。
好吧,以后我要認真啦,哈哈