寫(xiě)在前面
今天在開(kāi)發(fā)中,有設(shè)計(jì)新的模塊和功能读跷。
需求是這樣的:在項(xiàng)目產(chǎn)品中會(huì)增加一種新的模塊,叫做精選套餐(項(xiàng)目類(lèi)似于美團(tuán)禾唁,大眾點(diǎn)評(píng)效览,不過(guò)更加專(zhuān)注于精品餐吧和套餐的推送,也專(zhuān)注于夜生活相關(guān)的內(nèi)容荡短,叫做《夜夜》app丐枉,感興趣可以去應(yīng)用市場(chǎng)下載了解下)。這一個(gè)模塊會(huì)涉及到支付掘托, 也就是購(gòu)買(mǎi)瘦锹。在這一個(gè)項(xiàng)目中只是集成支付寶支付和微信支付這兩種比較主流的線(xiàn)上支付方式。
然而我在完成精選套餐模塊的編寫(xiě)之后烫映,模擬了用戶(hù)購(gòu)買(mǎi)套餐沼本,向服務(wù)器發(fā)起了創(chuàng)建訂單的請(qǐng)求,并得到了response锭沟,當(dāng)我把獲得的charge交付給Ping++之后抽兆,我發(fā)現(xiàn)了一個(gè)神奇的想象,就是:
在調(diào)起第三方支付的一瞬間族淮,app被kill掉了辫红!被kill了凭涂!kill了!
what贴妻?what the hell ?what is happening?
這完全是不予許發(fā)生的事情好么切油?而且,更加神奇的是名惩,app被kill之后澎胡,第三方支付竟然還很正常的調(diào)起了,也可以很正常的支付C漯摹9ニ!
這么詭異的現(xiàn)象弯予,生平僅見(jiàn)捌莼隆!不過(guò)稀奇歸稀奇锈嫩,問(wèn)題總是還得解決的受楼。
重啟app之后,去到了訂單中心呼寸,發(fā)現(xiàn)訂單真的是支付成功了艳汽,正安安靜靜的躺在已支付列表里面呢!也就是說(shuō)支付這一個(gè)流程并沒(méi)有錯(cuò)对雪,請(qǐng)求服務(wù)器生成回來(lái)的訂單以及支付的憑證charge也貌似沒(méi)有問(wèn)題骚灸,不然是不會(huì)支付成功的。但是app的確是在調(diào)起支付的一瞬間被kill的慌植,這無(wú)論怎么看都跟支付流程脫不了嫌疑。
竟然在懷疑支付流程义郑,那么就來(lái)試一下:
//startActivityForResult(payIntent);
我把發(fā)起支付的最后一句代碼注釋了一下蝶柿,運(yùn)行之后,發(fā)現(xiàn)app很正常非驮,并沒(méi)有被kill交汤,當(dāng)然同時(shí)并沒(méi)有調(diào)起支付。
好吧劫笙,確實(shí)是跟支付有關(guān)系芙扎。
那么,到底又是有什么關(guān)系呢填大?
回去翻了一下Ping++的文檔戒洼,方向文檔寫(xiě)著服務(wù)端生成的charge是有一定的規(guī)格的,例如title不能超過(guò)30Unicode字符允华,content也有限制圈浇,description也有限制寥掐。那么難道是這一個(gè)套餐的中文名字太長(zhǎng)然后引發(fā)的血案?
但我把支付的憑據(jù)charge log出來(lái)之后發(fā)現(xiàn)磷蜀,其實(shí)都沒(méi)有超過(guò)限制召耘。也就是說(shuō),這個(gè)鍋褐隆,ping++不背污它。
然后仔細(xì)梳理了一下思緒,想起app被kill是在調(diào)起第三方支付的一瞬間發(fā)生庶弃,也就是說(shuō)這一瞬間一定發(fā)生了什么事情衫贬,到時(shí)app掛掉了。但是調(diào)起支付的一瞬間無(wú)非就是app暫時(shí)進(jìn)入后臺(tái)虫埂,然后調(diào)起第三方支付界面祥山,支付完成支付又回到app。那么就是說(shuō)調(diào)起支付一瞬間掉伏,app要暫時(shí)的進(jìn)入后臺(tái)缝呕,之后又要回來(lái),也就是說(shuō)會(huì)調(diào)用onSaveInstanceState()對(duì)app activity當(dāng)前的狀態(tài)進(jìn)行保存斧散,app又在這一瞬間被kill供常,那么問(wèn)題就出現(xiàn)在onSaveInstanceState()的這一個(gè)環(huán)節(jié)上。
真相已經(jīng)很接近了
由于想到是在精選套餐的支付環(huán)節(jié)上才會(huì)出現(xiàn)的問(wèn)題鸡捐,精選套餐又是新增的模塊栈暇,那么問(wèn)題就很可能出現(xiàn)這上面。
由于項(xiàng)目中也有其他的模塊有支付的動(dòng)作箍镜,于是就去測(cè)試了一下源祈。果然,其他所有的支付流程都是正常的色迂。那么罪魁禍?zhǔn)拙褪蔷x套餐模塊了香缺。
通過(guò)對(duì)精選套餐的分析,以及在onSaveInstanceState()進(jìn)行debug歇僧,發(fā)現(xiàn)在保存當(dāng)前activity的時(shí)候图张,保存到變量 mealDetail 終于報(bào)錯(cuò)了同時(shí)app掛掉了。在報(bào)錯(cuò)信息總看到诈悍,原來(lái)是mealDetail 繼承于Serializable祸轮,得以可以序列化。然而mealDetail的一個(gè)內(nèi)部類(lèi)Recommend卻沒(méi)有繼承于Serializable侥钳。因此在app退入后臺(tái)适袜,進(jìn)行事件狀態(tài)保存的一瞬間,對(duì)局部變量進(jìn)行序列化保存時(shí)遇到不能序列化的對(duì)象從而導(dǎo)致的crash慕趴。
所以當(dāng)我把mealDetail的內(nèi)部類(lèi)Recommend繼承于Serializable之后痪蝇,精選套餐的支付流程果然很正常的執(zhí)行了鄙陡。
關(guān)于onSaveInstanceState可以點(diǎn)擊查看說(shuō)明。
基本作用就是
Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法躏啰,它們不同于 onCreate()趁矾、onPause()等生命周期方法,它們并不一定會(huì)被觸發(fā)给僵。當(dāng)應(yīng)用遇到意外情況(如:內(nèi)存不足毫捣、用戶(hù)直接按Home鍵)由系統(tǒng)銷(xiāo)毀一個(gè)Activity時(shí),onSaveInstanceState() 會(huì)被調(diào)用帝际。但是當(dāng)用戶(hù)主動(dòng)去銷(xiāo)毀一個(gè)Activity時(shí)蔓同,例如在應(yīng)用中按返回鍵,onSaveInstanceState()就不會(huì)被調(diào)用蹲诀。因?yàn)樵谶@種情況下斑粱,用戶(hù)的行為決定了不需要保存Activity的狀態(tài)。通常onSaveInstanceState()只適合用于保存一些臨時(shí)性的狀態(tài)脯爪,而onPause()適合用于數(shù)據(jù)的持久化保存则北。
在activity被殺掉之前調(diào)用保存每個(gè)實(shí)例的狀態(tài),以保證該狀態(tài)可以在onCreate(Bundle)或者onRestoreInstanceState(Bundle) (傳入的Bundle參數(shù)是由onSaveInstanceState封裝好的)中恢復(fù)。這個(gè)方法在一個(gè)activity被殺死前調(diào)用痕慢,當(dāng)該activity在將來(lái)某個(gè)時(shí)刻回來(lái)時(shí)可以恢復(fù)其先前狀態(tài)尚揣。
例如,如果activity B啟用后位于activity A的前端掖举,在某個(gè)時(shí)刻activity A因?yàn)橄到y(tǒng)回收資源的問(wèn)題要被殺掉快骗,A通過(guò)onSaveInstanceState將有機(jī)會(huì)保存其用戶(hù)界面狀態(tài),使得將來(lái)用戶(hù)返回到activity A時(shí)能通過(guò)onCreate(Bundle)或者onRestoreInstanceState(Bundle)恢復(fù)界面的狀態(tài)塔次。
關(guān)于onSaveInstanceState ()方篮,是在函數(shù)里面保存一些View有用的數(shù)據(jù)到一個(gè)Parcelable對(duì)象并返回。在Activity的onSaveInstanceState(Bundle outState)中調(diào)用View的onSaveInstanceState ()励负,返回Parcelable對(duì)象恭取,
接著用Bundle的putParcelable方法保存在Bundle ?savedInstanceState中。
當(dāng)系統(tǒng)調(diào)用Activity的的onRestoreInstanceState(Bundle savedInstanceState)時(shí)熄守,?同過(guò)Bundle的getParcelable方法得到Parcelable對(duì)象,然后把該P(yáng)arcelable對(duì)象傳給View的onRestoreInstanceState (Parcelable state)耗跛。在的View的onRestoreInstanceState中從Parcelable讀取保存的數(shù)據(jù)以便View使用裕照。
這就是onSaveInstanceState() 和?onRestoreInstanceState() 兩個(gè)函數(shù)的基本作用和用法。
總結(jié)
到最后才發(fā)現(xiàn)调塌,原來(lái)所謂的bug還是自己的粗心造成的晋南,只是忽略了onSaveInstanceState()函數(shù)對(duì)于變量的要求從而導(dǎo)致的一系列問(wèn)題的出現(xiàn)。同時(shí)也是自己偷懶羔砾,在創(chuàng)建mealDetail 這一個(gè)數(shù)據(jù)model的時(shí)候用了AndroidStudio的插件GsonFormat直接進(jìn)行生成然后卻沒(méi)有認(rèn)真地檢查负间。(GsonFormat確實(shí)是很吊的一款插件偶妖,很推薦使用,可以省下大量的時(shí)間政溃,當(dāng)然也要細(xì)心的使用趾访,例如我這次的例子)
by the way,擼代碼是一件嚴(yán)肅董虱,細(xì)致扼鞋,一絲不茍的事情,真的馬虎不得愤诱。這次把這么蠢的crash事件碼了出來(lái)云头,就是讓大家嘲笑一下我,這是對(duì)我很好的一個(gè)鞭策淫半。同時(shí)也幫助一下那些可能也遇到這個(gè)問(wèn)題的童鞋們溃槐。
好吧,以后我要認(rèn)真啦科吭,哈哈