您好,登錄后才能下訂單哦!
【本文轉(zhuǎn)載自美團(tuán)技術(shù)團(tuán)隊(duì)微信公眾號(hào),作者:維康 少杰 曉飛,轉(zhuǎn)載和授權(quán)請聯(lián)系原作者】
Crash率是衡量一個(gè)App好壞的重要指標(biāo)之一。如果你忽略了它的存在,它就會(huì)得寸進(jìn)尺,愈演愈烈,最后造成大量用戶的流失,進(jìn)而給公司帶來無法估量的損失。本文講述美團(tuán)外賣Android客戶端團(tuán)隊(duì)在將App的Crash率從千分之三做到萬分之二過程中所做的大量實(shí)踐工作,拋磚引玉,希望能夠?yàn)槠渌麍F(tuán)隊(duì)提供一些經(jīng)驗(yàn)和啟發(fā)。
面臨的挑戰(zhàn)和成果
面對用戶使用頻率高,外賣業(yè)務(wù)增長快,Android碎片化嚴(yán)重這些問題,美團(tuán)外賣Android App如何持續(xù)的降低Crash率,是一項(xiàng)極具挑戰(zhàn)的事情。通過團(tuán)隊(duì)的全力全策,美團(tuán)外賣Android App的平均Crash率從千分之三降到了萬分之二,最優(yōu)值萬一左右(Crash率統(tǒng)計(jì)方式:Crash次數(shù)/DAU)。
美團(tuán)外賣自2013年創(chuàng)建以來,業(yè)務(wù)就以指數(shù)級(jí)的速度發(fā)展。美團(tuán)外賣承載的業(yè)務(wù),從單一的餐飲業(yè)務(wù),發(fā)展到餐飲、超市、生鮮、果蔬、藥品、鮮花、蛋糕、跑腿等十多個(gè)大品類業(yè)務(wù)。目前美團(tuán)外賣日完成訂單量已突破2000萬,成為美團(tuán)點(diǎn)評最重要的業(yè)務(wù)之一。美團(tuán)外賣客戶端所承載的業(yè)務(wù)模塊越來越多,產(chǎn)品復(fù)雜度越來越高,團(tuán)隊(duì)開發(fā)人員日益增加,這些都給App降低Crash率帶來了巨大的挑戰(zhàn)。
Crash的治理實(shí)踐
對于Crash的治理,我們盡量遵守以下三點(diǎn)原則:
由點(diǎn)到面。一個(gè)Crash發(fā)生了,我們不能只針對這個(gè)Crash的去解決,而要去考慮這一類Crash怎么去解決和預(yù)防。只有這樣才能使得這一類Crash真正被解決。
異常不能隨便吃掉。隨意的使用try-catch,只會(huì)增加業(yè)務(wù)的分支和隱蔽真正的問題,要了解Crash的本質(zhì)原因,根據(jù)本質(zhì)原因去解決。catch的分支,更要根據(jù)業(yè)務(wù)場景去兜底,保證后續(xù)的流程正常。
預(yù)防勝于治理。當(dāng)Crash發(fā)生的時(shí)候,損失已經(jīng)造成了,我們再怎么治理也只是減少損失。盡可能的提前預(yù)防Crash的發(fā)生,可以將Crash消滅在萌芽階段。
常規(guī)的Crash治理
常規(guī)Crash發(fā)生的原因主要是由于開發(fā)人員編寫代碼不小心導(dǎo)致的。解決這類Crash需要由點(diǎn)到面,根據(jù)Crash引發(fā)的原因和業(yè)務(wù)本身,統(tǒng)一集中解決。常見的Crash類型包括:空節(jié)點(diǎn)、角標(biāo)越界、類型轉(zhuǎn)換異常、實(shí)體對象沒有序列化、數(shù)字轉(zhuǎn)換異常、Activity或Service找不到等。這類Crash是App中最為常見的Crash,也是最容易反復(fù)出現(xiàn)的。在獲取Crash堆棧信息后,解決這類Crash一般比較簡單,更多考慮的應(yīng)該是如何避免。下面介紹兩個(gè)我們治理的量比較大的Crash。
NullPointerException
NullPointerException是我們遇到最頻繁的,造成這種Crash一般有兩種情況:
對象本身沒有進(jìn)行初始化就進(jìn)行操作。
對象已經(jīng)初始化過,但是被回收或者手動(dòng)置為null,然后對其進(jìn)行操作。
針對第一種情況導(dǎo)致的原因有很多,可能是開發(fā)人員的失誤、API返回?cái)?shù)據(jù)解析異常、進(jìn)程被殺死后靜態(tài)變量沒初始化導(dǎo)致,我們可以做的有:
對可能為空的對象做判空處理。
養(yǎng)成使用@NonNull和@Nullable注解的習(xí)慣。
盡量不使用靜態(tài)變量,萬不得已使用SharedPreferences來存儲(chǔ)。
考慮使用Kotlin語言。
針對第二種情況大部分是由于Activity/Fragment銷毀或被移除后,在Message、Runnable、網(wǎng)絡(luò)等回調(diào)中執(zhí)行了一些代碼導(dǎo)致的,我們可以做的有:
Message、Runnable回調(diào)時(shí),判斷Activity/Fragment是否銷毀或被移除;加try-catch保護(hù);Activity/Fragment銷毀時(shí)移除所有已發(fā)送的Runnable。
封裝LifecycleMessage/Runnable基礎(chǔ)組件,并自定義Lint檢查,提示使用封裝好的基礎(chǔ)組件。
在BaseActivity、BaseFragment的onDestory()里把當(dāng)前Activity所發(fā)的所有請求取消掉。
IndexOutOfBoundsException
這類Crash常見于對ListView的操作和多線程下對容器的操作。
針對ListView中造成的IndexOutOfBoundsException,經(jīng)常是因?yàn)橥獠恳渤钟辛薃dapter里數(shù)據(jù)的引用(如在Adapter的構(gòu)造函數(shù)里直接賦值),這時(shí)如果外部引用對數(shù)據(jù)更改了,但沒有及時(shí)調(diào)用notifyDataSetChanged(),則有可能造成Crash,對此我們封裝了一個(gè)BaseAdapter,數(shù)據(jù)統(tǒng)一由Adapter自己維護(hù)通知, 同時(shí)也極大的避免了The content of the adapter has changed but ListView did not receive a notification,這兩類Crash目前得到了統(tǒng)一的解決。
另外,很多容器是線程不安全的,所以如果在多線程下對其操作就容易引發(fā)IndexOutOfBoundsException。常用的如JDK里的ArrayList和Android里的SparseArray、ArrayMap,同時(shí)也要注意有一些類的內(nèi)部實(shí)現(xiàn)也是用的線程不安全的容器,如Bundle里用的就是ArrayMap。
系統(tǒng)級(jí)Crash治理
眾所周知,Android的機(jī)型眾多,碎片化嚴(yán)重,各個(gè)硬件廠商可能會(huì)定制自己的ROM,更改系統(tǒng)方法,導(dǎo)致特定機(jī)型的崩潰。發(fā)現(xiàn)這類Crash,主要靠云測平臺(tái)配合自動(dòng)化測試,以及線上監(jiān)控,這種情況下的Crash堆棧信息很難直接定位問題。下面是常見的解決思路:
嘗試找到造成Crash的可疑代碼,看是否有特異的API或者調(diào)用方式不當(dāng)導(dǎo)致的,嘗試修改代碼邏輯來進(jìn)行規(guī)避。
通過Hook來解決,Hook分為Java Hook和Native Hook。Java Hook主要靠反射或者動(dòng)態(tài)代理來更改相應(yīng)API的行為,需要嘗試找到可以Hook的點(diǎn),一般Hook的點(diǎn)多為靜態(tài)變量,同時(shí)需要注意Android不同版本的API,類名、方法名和成員變量名都可能不一樣,所以要做好兼容工作;Native Hook原理上是用更改后方法把舊方法在內(nèi)存地址上進(jìn)行替換,需要考慮到Dalvik和ART的差異;相對來說Native Hook的兼容性更差一點(diǎn),所以用Native Hook的時(shí)候需要配合降級(jí)策略。
如果通過前兩種方式都無法解決的話,我們只能嘗試反編譯ROM,尋找解決的辦法。
我們舉一個(gè)定制系統(tǒng)ROM導(dǎo)致Crash的例子,根據(jù)Crash平臺(tái)統(tǒng)計(jì)數(shù)據(jù)發(fā)現(xiàn)該Crash只發(fā)生在vivo V3Max這類機(jī)型上,Crash堆棧如下:
我們發(fā)現(xiàn)原生系統(tǒng)上對應(yīng)系統(tǒng)版本的AbsListView里并沒有UpdateBottomFlagTask類,因此可以斷定是vivo該版本定制的ROM修改了系統(tǒng)的實(shí)現(xiàn)。我們在定位這個(gè)Crash的可疑點(diǎn)無果后決定通過Hook的方式解決,通過源碼發(fā)現(xiàn)AsyncTask$SerialExecutor是靜態(tài)變量,是一個(gè)很好的Hook的點(diǎn),通過反射添加try-catch解決。因?yàn)樾薷牡氖莊inal對象所以需要先反射修改accessFlags,需要注意ART和Dalvik下對應(yīng)的Class不同,代碼如下:
美團(tuán)外賣App用上述方法解決了對應(yīng)的Crash,但是美團(tuán)App里的外賣頻道因?yàn)槠脚_(tái)的限制無法通過這種方式,于是我們嘗試反編譯ROM。
Android ROM編譯時(shí)會(huì)將framework、app、bin等目錄打入system.img中,system.img是Android系統(tǒng)中用來存放系統(tǒng)文件的鏡像 (image),文件格式一般為yaffs2或ext。但Android 5.0開始支持dm-verity后,system.img不再提供,而是提供了三個(gè)文件system.new.dat,system.patch.dat,system.transfer.list,因此我們首先需要通過上述的三個(gè)文件得到system.img。但我們將vivo ROM解壓后發(fā)現(xiàn)廠商將system.new.dat進(jìn)行了分片,如下圖所示:
上面的配置就可以將App代碼(包括第三方庫)里所有的Intent.getXXXExtra調(diào)用替換成IntentUtil類中的安全版實(shí)現(xiàn)。當(dāng)然,并不是所有的異常都只需要catch住就萬事大吉,如果真的有邏輯錯(cuò)誤肯定需要在開發(fā)和測試階段及時(shí)暴露出來,所以在IntentUtil中會(huì)對App的運(yùn)行環(huán)境做判斷,Debug下會(huì)將異常直接拋出,開發(fā)同學(xué)可以根據(jù)Crash堆棧分析問題,Release環(huán)境下則在捕獲到異常時(shí)返回對應(yīng)的默認(rèn)值然后將異常上報(bào)到服務(wù)器。
依賴庫的問題
Android App經(jīng)常會(huì)依賴很多AAR, 每個(gè)AAR可能有多個(gè)版本,打包時(shí)Gradle會(huì)根據(jù)規(guī)則確定使用的最終版本號(hào)(默認(rèn)選擇最高版本或者強(qiáng)制指定的版本),而其他版本的AAR將被丟棄。如果互相依賴的AAR中有不兼容的版本,存在的問題在打包時(shí)是不能發(fā)現(xiàn)的,只有在相關(guān)代碼執(zhí)行時(shí)才會(huì)出現(xiàn),會(huì)造成NoClassDefFoundError、NoSuchFieldError、NoSuchMethodError等異常。
如圖所示,order和store兩個(gè)業(yè)務(wù)庫都依賴了platform.aar,一個(gè)是1.0版本,一個(gè)是2.0版本,默認(rèn)最終打進(jìn)APK的只有platform 2.0版本,這時(shí)如果order庫里用到的platform庫里的某個(gè)類或者方法在2.0版本中被刪除了,運(yùn)行時(shí)就可能發(fā)生異常,雖然SDK在升級(jí)時(shí)會(huì)盡量做到向下兼容,但很多時(shí)候尤其是第三方SDK是沒法得到保證的,在美團(tuán)外賣Android App v6.0版本時(shí)因?yàn)檫@個(gè)原因?qū)е聼嵝迯?fù)功能喪失,因此為了提前發(fā)現(xiàn)問題,我們接入了依賴檢查插件Defensor。
網(wǎng)絡(luò)層統(tǒng)一處理API臟數(shù)據(jù)
客戶端的很大一部分的Crash是因?yàn)锳PI返回的臟數(shù)據(jù)。比如當(dāng)API返回空值、空數(shù)組或返回不是約定類型的數(shù)據(jù),App收到這些數(shù)據(jù),就極有可能發(fā)生空指針、數(shù)組越界和類型轉(zhuǎn)換錯(cuò)誤等Crash。而且這樣的臟數(shù)據(jù),特別容易引起線上大面積的崩潰。
最早我們的工程的網(wǎng)絡(luò)層用法是:頁面監(jiān)聽網(wǎng)絡(luò)成功和失敗的回調(diào),網(wǎng)絡(luò)成功后,將JSON數(shù)據(jù)傳遞給頁面,頁面解析Model,初始化View,如圖所示。這樣的問題就是,網(wǎng)絡(luò)雖然請求成功了,但是JSON解析Model這個(gè)過程可能存在問題,例如沒有返回?cái)?shù)據(jù)或者返回了類型不對的數(shù)據(jù),而這個(gè)臟數(shù)據(jù)導(dǎo)致問題會(huì)出現(xiàn)在UI層,直接反應(yīng)給用戶。
從圖上可以看出,重構(gòu)后的網(wǎng)絡(luò)層負(fù)責(zé)請求網(wǎng)絡(luò)和數(shù)據(jù)解析,如果存在臟數(shù)據(jù)的話,在網(wǎng)絡(luò)層就會(huì)發(fā)現(xiàn)問題,不會(huì)影響到UI層,返回給UI層的都是校驗(yàn)成功的數(shù)據(jù)。這樣改造后,我們發(fā)現(xiàn)這類的Crash率有了極大的改善。
大圖監(jiān)控
上面講到大對象是導(dǎo)致OOM的主要原因之一,而Bitmap是App里最常見的大對象類型,因此對占用內(nèi)存過大的Bitmap對象的監(jiān)控就很有必要了。
我們用AOP方式Hook了三種常見圖片庫的加載圖片回調(diào)方法,同時(shí)監(jiān)控圖片庫加載圖片時(shí)的兩個(gè)維度:
加載圖片使用的URL。外賣App中除靜態(tài)資源外,所有圖片都要求發(fā)布到專用的圖片CDN服務(wù)器上,加載圖片時(shí)使用正則表達(dá)式匹配URL,除了限定CDN域名之外還要求所有圖片加載時(shí)都要添加對應(yīng)的動(dòng)態(tài)縮放參數(shù)。
最終加載出的圖片結(jié)果(也就是Bitmap對象)。我們知道Bitmap對象所占內(nèi)存和其分辨率大小成正比,而一般情況下在ImageView上設(shè)置超過自身尺寸的圖片是沒有意義的,所以我們要求顯示在ImageView中的Bitmap分辨率不允許超過View自身的尺寸(為了降低誤報(bào)率也可以設(shè)定一個(gè)報(bào)警閾值)。
開發(fā)過程中,在App里檢測到不合規(guī)的圖片時(shí)會(huì)立即高亮出錯(cuò)的ImageView所在的位置并彈出對話框提示ImageView所在的Activity、XPath和加載圖片使用的URL等信息,如下圖,輔助開發(fā)同學(xué)定位并解決問題。在Release環(huán)境下可以將報(bào)警信息上報(bào)到服務(wù)器,實(shí)時(shí)觀察數(shù)據(jù),有問題及時(shí)處理。
資源重復(fù)檢查
在之前的文章《美團(tuán)外賣Android平臺(tái)化架構(gòu)演進(jìn)實(shí)踐》中講述了我們的平臺(tái)化演進(jìn)過程,在這個(gè)過程中大家很大的一部分工作是下沉,但是下沉不完全就會(huì)導(dǎo)致一些類和資源的重復(fù),類因?yàn)橛邪南拗撇粫?huì)出現(xiàn)問題。但是一些資源文件如layout、drawable等如果同名則下層會(huì)被上層覆蓋,這時(shí)layout里view的id發(fā)生了變化就可能導(dǎo)致空指針的問題。
為了避免這種問題,我們寫了一個(gè)Gradle插件通過hook MergeResource這個(gè)Task,拿到所有l(wèi)ibrary和主庫的資源文件,如果檢查到重復(fù)則會(huì)中斷編譯過程,輸出重復(fù)的資源名及對應(yīng)的library name,同時(shí)避免有些資源因?yàn)闃邮降仍虼_實(shí)需要覆蓋,因此我們設(shè)置了白名單。同時(shí)在這個(gè)過程中我們也拿到了所有的的圖片資源,可以順手做圖片大小的本地監(jiān)控,如下圖所示:
止損
盡管我們在前面做了那么多,但是Crash還是無法避免的,例如,在灰度階段因?yàn)榱考?jí)不夠,有些Crash沒有被暴露出來;又或者某些功能客戶端比后臺(tái)更早上線,而這些功能在灰度階段沒有被覆蓋到;這些情況下,如果出現(xiàn)問題就需要考慮如何止損了。
問題發(fā)生時(shí)首先需要評估重要性,如果問題不是很嚴(yán)重而且修復(fù)成本較高可以考慮在下個(gè)版本再修復(fù),相反如果問題比較嚴(yán)重,對用戶體驗(yàn)或下單有影響時(shí)就必須要修復(fù)。修復(fù)時(shí)首先考慮業(yè)務(wù)降級(jí),主要看該部分異常的業(yè)務(wù)是否有兜底或者A/B策略,這樣是最穩(wěn)妥也是最有效的方式。
如果業(yè)務(wù)不能降級(jí)就需要考慮熱修復(fù)了,目前美團(tuán)外賣Android App接入的熱修復(fù)框架是自研的Robust,可以修復(fù)90%以上的場景,熱修成功率也達(dá)到了99%以上。如果問題發(fā)生在熱修復(fù)無法覆蓋的場景,就只能強(qiáng)制用戶升級(jí)。強(qiáng)制升級(jí)因?yàn)楦采w周期長,同時(shí)影響用戶的體驗(yàn),只在萬不得已的情況下才會(huì)使用。
展望
Crash的自我修復(fù)
我們在做新技術(shù)選型時(shí)除了要考慮是否能滿足業(yè)務(wù)需求、是否比現(xiàn)有技術(shù)更優(yōu)秀和團(tuán)隊(duì)學(xué)習(xí)成本等因素之外,兼容性和穩(wěn)定性也非常重要。但面對國內(nèi)非富多彩的Android系統(tǒng)環(huán)境,在體量百萬級(jí)以上的的App中幾乎不可能實(shí)現(xiàn)毫無瑕疵的技術(shù)方案和組件,所以一般情況下如果某個(gè)技術(shù)實(shí)現(xiàn)方案可以達(dá)到0.01‰以下的崩潰率,而其他方案也沒有更好的表現(xiàn),我們就認(rèn)為它是可以接受的。但是哪怕僅僅十萬分之一的崩潰率,也代表還有用戶受到影響,而我們認(rèn)為Crash對用戶來說是最糟糕的體驗(yàn),尤其是涉及到交易的場景,所以我們必須本著每一單都很重要的原則,盡最大努力保證用戶順利執(zhí)行流程。
實(shí)際情況中有一些技術(shù)方案在兼容性和穩(wěn)定性上做了一定妥協(xié)的場景,往往是因?yàn)榭紤]到性能或擴(kuò)展性等方面的優(yōu)勢。這種情況下我們其實(shí)可以再多做一些,進(jìn)一步提高App的可用性。就像很多操作系統(tǒng)都有“兼容模式”或者“安全模式”,很多自動(dòng)化機(jī)械機(jī)器都配套有手動(dòng)操作模式一樣,App里也可以實(shí)現(xiàn)備用的降級(jí)方案,然后設(shè)置特定條件的觸發(fā)策略,從而達(dá)到自動(dòng)修復(fù)Crash的目的。
舉例來講,Android 3.0中引入了硬件加速機(jī)制,雖然可以提高繪制幀率并且降低CPU占用率,但是在某些機(jī)型上還是會(huì)有繪制錯(cuò)亂甚至Crash的情況,這時(shí)我們就可以在App中記錄硬件加速相關(guān)的Crash問題或者使用檢測代碼主動(dòng)檢測硬件加速功能是否正常工作,然后主動(dòng)選擇是否開啟硬件加速,這樣既可以讓絕大部分用戶享受硬件加速帶來的優(yōu)勢,也可以保障硬件加速功能不完善的機(jī)型不受影響。還有一些類似的可以做自動(dòng)降級(jí)的場景,比如:
部分使用JNI實(shí)現(xiàn)的模塊,在SO加載失敗或者運(yùn)行時(shí)發(fā)生異常則可以降級(jí)為Java版實(shí)現(xiàn)。
RenderScript實(shí)現(xiàn)的圖片模糊效果,也可以在失敗后降級(jí)為普通的Java版高斯模糊算法。
在使用Retrofit網(wǎng)絡(luò)庫時(shí)發(fā)現(xiàn)OkHttp3或者HttpURLConnection網(wǎng)絡(luò)通道失敗率高,可以主動(dòng)切換到另一種通道。
這類問題都需要根據(jù)具體情況具體分析,如果可以找到準(zhǔn)確的判定條件和穩(wěn)定的修復(fù)方案,就可以讓App穩(wěn)定性再上一個(gè)臺(tái)階。
特定Crash類型日志自動(dòng)回?fù)?/strong>
外賣業(yè)務(wù)發(fā)展迅速,即使我們在開發(fā)時(shí)使用各種工具、措施來避免Crash的發(fā)生,但Crash還是不可避免。線上某些怪異的Crash發(fā)生后,我們除了分析Crash堆棧信息之外,還可以使用離線日志回?fù)?、下發(fā)動(dòng)態(tài)日志等工具來還原Crash發(fā)生時(shí)的場景,幫助開發(fā)同學(xué)定位問題,但是這兩種方式都有它們各自的問題。
離線日志顧名思義,它的內(nèi)容都是預(yù)先記錄好的,有時(shí)候可能會(huì)漏掉一些關(guān)鍵信息,因?yàn)樵诖a中加日志一般只是在業(yè)務(wù)關(guān)鍵點(diǎn),在大量的普通方法中不可能都加上日志。動(dòng)態(tài)日志(Holmes)存在的問題是每次下發(fā)只能針對已知UUID的一個(gè)用戶的一臺(tái)設(shè)備,對于大量線上Crash的情況這種操作并不合適,因?yàn)槲覀儾⒉荒苤滥膫€(gè)發(fā)生Crash的用戶還會(huì)再次復(fù)現(xiàn)這次操作,下發(fā)配置充滿了不確定性。
我們可以改造Holmes使其支持批量甚至全量下發(fā)動(dòng)態(tài)日志,記錄的日志等到發(fā)生特定類型的Crash時(shí)才上報(bào),這樣一來可以減少日志服務(wù)器壓力,同時(shí)也可以極大提高定位問題的效率,因?yàn)槲覀兛梢源_定上報(bào)日志的設(shè)備最后都真正發(fā)生了該類型Crash,再來分析日志就可以做到事半功倍。
總結(jié)
業(yè)務(wù)的快速發(fā)展,往往不可能給團(tuán)隊(duì)充足的時(shí)間去治理Crash,而Crash又是App最重要的指標(biāo)之一。團(tuán)隊(duì)需要由一個(gè)個(gè)Crash個(gè)例,去探究每一個(gè)Crash發(fā)生的最本質(zhì)原因,找到最合理解決這類Crash的方案,建立解決這一類Crash的長效機(jī)制,而不能飲鴆止渴。只有這樣,隨著版本的不斷迭代,我們才能在Crash治理之路上離目標(biāo)越來越近。
參考資料
Crash率從2.2%降至0.2%,這個(gè)團(tuán)隊(duì)是怎么做到的?
Android運(yùn)行時(shí)ART加載OAT文件的過程分析
Android動(dòng)態(tài)日志系統(tǒng)Holmes
Android Hook技術(shù)防范漫談
美團(tuán)外賣Android Lint代碼檢查實(shí)踐
作者簡介
維康,美團(tuán)高級(jí)工程師,2016年校招加入美團(tuán),目前作為外賣Android App主力開發(fā),主要負(fù)責(zé)App Crash治理和集成構(gòu)建相關(guān)工作。
少杰,美團(tuán)高級(jí)工程師,2017年加入美團(tuán),目前作為外賣Android App技術(shù)負(fù)責(zé)人,主要負(fù)責(zé)App監(jiān)控相關(guān)工作。
曉飛,美團(tuán)技術(shù)專家,2015年加入美團(tuán),是外賣Android的早期開發(fā)者之一,目前作為外賣Android App負(fù)責(zé)人,主要負(fù)責(zé)版本管理和業(yè)務(wù)架構(gòu)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。