教育培訓(xùn):

計(jì)算機(jī)、通信相關(guān)專(zhuān)業(yè)本科以上學(xué)歷。

工作經(jīng)驗(yàn):

熟悉CMMI/ISO9000等質(zhì)量管理體系;熟練使用VS2003、VSS、PD、ROSE等開(kāi)發(fā)、設(shè)計(jì)和管理工具;熟悉RUP及常規(guī)軟件過(guò)程模型,精通數(shù)據(jù)結(jié)構(gòu)、OOAD、webservice及業(yè)界流行常用設(shè)計(jì)模式等;掌握面向?qū)ο蟮木幊谭椒?,編程思路清晰,有良好的編程風(fēng)格;有豐富的軟件架構(gòu)設(shè)計(jì)經(jīng)驗(yàn),精通面向接口的分析設(shè)計(jì)技術(shù)和清晰敏銳的邏輯思考能力。

android高級(jí)架構(gòu)工程師造價(jià)信息

市場(chǎng)價(jià) 信息價(jià) 詢價(jià)
材料名稱 規(guī)格/型號(hào) 市場(chǎng)價(jià)
(除稅)
工程建議價(jià)
(除稅)
行情 品牌 單位 稅率 供應(yīng)商 報(bào)價(jià)日期
工程師 內(nèi)存:16GB;硬盤(pán):512GB+2T;臺(tái)式圖形工作站:i7-10700 P620 2G獨(dú)顯 3年上門(mén)服務(wù) 21.8寸顯示器 查看價(jià)格 查看價(jià)格

戴爾

臺(tái) 13% 廣東嶠宇科技有限公司
高級(jí)平衡籃球 籃球伸臂2.25m(可定制)籃圈上沿離地面高3.05m 查看價(jià)格 查看價(jià)格

達(dá)創(chuàng)

個(gè) 13% 河北達(dá)創(chuàng)體育器材有限公司
系統(tǒng)軟件即意智慧云B/S網(wǎng)頁(yè)架構(gòu) 無(wú)需任何安裝程序,用戶只需通過(guò)瀏覽器登錄即可査看實(shí)時(shí)監(jiān)測(cè)數(shù)據(jù)、實(shí)時(shí)/歷史預(yù)報(bào)警,遠(yuǎn)程控制,能耗分析等,2年內(nèi)免費(fèi)維護(hù)、升級(jí) 查看價(jià)格 查看價(jià)格

13% 上海振大電器成套集團(tuán)有限公司青海辦事處
構(gòu)管片 120×120 查看價(jià)格 查看價(jià)格

隆盛

環(huán) 13% 衡水市隆盛工程橡膠有限公司
構(gòu)管片 120×120 查看價(jià)格 查看價(jià)格

隆盛

m 13% 衡水市隆盛工程橡膠有限公司
PVC高級(jí)片塊材 Scala/5.0鎖扣 查看價(jià)格 查看價(jià)格

法國(guó)Gerflor潔弗樂(lè)

m2 13% 南寧市優(yōu)勝商貿(mào)有限公司
PVC高級(jí)片塊材 Saga2 / 4.6mm×500×500mm;LVT塑膠地板 查看價(jià)格 查看價(jià)格

法國(guó)Gerflor潔弗樂(lè)

m2 13% 南寧市優(yōu)勝商貿(mào)有限公司
PVC高級(jí)片塊材 Saga2 /connect 5.0mm×701.3×701.3mm;LVT塑膠地板 查看價(jià)格 查看價(jià)格

法國(guó)Gerflor潔弗樂(lè)

m2 13% 南寧市優(yōu)勝商貿(mào)有限公司
材料名稱 規(guī)格/型號(hào) 除稅
信息價(jià)
含稅
信息價(jià)
行情 品牌 單位 稅率 地區(qū)/時(shí)間
工程駁船 100T以內(nèi) 查看價(jià)格 查看價(jià)格

臺(tái)班 清遠(yuǎn)市英德市2015年4季度信息價(jià)
工程駁船 200T以內(nèi) 查看價(jià)格 查看價(jià)格

臺(tái)班 清遠(yuǎn)市英德市2015年3季度信息價(jià)
工程駁船 200T以內(nèi) 查看價(jià)格 查看價(jià)格

臺(tái)班 清遠(yuǎn)市英德市2015年2季度信息價(jià)
工程駁船 200T以內(nèi) 查看價(jià)格 查看價(jià)格

臺(tái)班 清遠(yuǎn)市英德市2014年2季度信息價(jià)
工程駁船 100T以內(nèi) 查看價(jià)格 查看價(jià)格

臺(tái)班 清遠(yuǎn)市英德市2014年1季度信息價(jià)
工程駁船 100T以內(nèi) 查看價(jià)格 查看價(jià)格

臺(tái)班 清遠(yuǎn)市英德市2013年4季度信息價(jià)
工程駁船 200T以內(nèi) 查看價(jià)格 查看價(jià)格

臺(tái)班 清遠(yuǎn)市英德市2013年2季度信息價(jià)
工程駁船 100T以內(nèi) 查看價(jià)格 查看價(jià)格

臺(tái)班 清遠(yuǎn)市英德市2013年1季度信息價(jià)
材料名稱 規(guī)格/需求量 報(bào)價(jià)數(shù) 最新報(bào)價(jià)
(元)
供應(yīng)商 報(bào)價(jià)地區(qū) 最新報(bào)價(jià)時(shí)間
工程師 工程師站|1套 3 查看價(jià)格 河南德?tīng)N電子科技有限公司 全國(guó)   2022-09-30
工程師 1.名稱:工程師2.規(guī)格、型號(hào):工控型,配置29"液晶顯示器,包含打印機(jī) 含正版操作系統(tǒng)及應(yīng)用軟件3.其他:滿足設(shè)計(jì)、相關(guān)圖集、標(biāo)準(zhǔn)及招標(biāo)技術(shù)要求|1套 3 查看價(jià)格 北京勤瑞恒科技有限公司 全國(guó)   2021-07-29
工程師 1.名稱:工程師2.規(guī)格、型號(hào):工控型,配置29"液晶顯示器,包含打印機(jī) 含正版操作系統(tǒng)及應(yīng)用軟件3.其他:滿足設(shè)計(jì)、相關(guān)圖集、標(biāo)準(zhǔn)及招標(biāo)技術(shù)要求|1套 3 查看價(jià)格 北京勤瑞恒科技有限公司 全國(guó)   2021-07-02
工程師 詳見(jiàn)技術(shù)要求|1臺(tái) 1 查看價(jià)格 深圳市莊銘科技有限公司 廣東   2022-01-14
工程師 i5 3 2G 工控機(jī) 液晶22 i5 3 2G|1套 3 查看價(jià)格 北京勤瑞恒科技有限公司 四川   2022-09-27
工程師 i7,3.10GHz及以上;內(nèi)存:4GB ;硬盤(pán)1T|1臺(tái) 1 查看價(jià)格 廣州思源網(wǎng)絡(luò)科技有限公司 廣東   2018-06-15
工程師 主流I7處理器 內(nèi)存8G 專(zhuān)業(yè)顯卡2G顯存 硬盤(pán)1TB|1套 1 查看價(jià)格 廣東岑安機(jī)電有限公司 湖南   2021-10-12
工程師 常見(jiàn)型號(hào)|2臺(tái) 2 查看價(jià)格 北京匯鑫盛泰科技有限公司 全國(guó)   2019-12-30

進(jìn)行安卓軟件產(chǎn)品需求分析及可行性分析、相關(guān)設(shè)計(jì)文檔的編寫(xiě);

構(gòu)建、設(shè)計(jì)、實(shí)現(xiàn)產(chǎn)品系統(tǒng)的服務(wù)器軟件架構(gòu);

進(jìn)行軟件開(kāi)發(fā)過(guò)程中所有流程與架構(gòu)的控制及管理;

詳細(xì)制定應(yīng)用程序接口( API )和各種不同模塊的定義,其中包括用戶界面( UI )、流程和商業(yè)邏輯,及其它具體到平臺(tái)的各種設(shè)置,解決架構(gòu)中的技術(shù)問(wèn)題;

進(jìn)行關(guān)鍵模塊的編碼;

在軟件產(chǎn)品測(cè)試階段或軟件制作前,發(fā)現(xiàn)和解決系統(tǒng)BUG;

輔導(dǎo)軟件工程師的產(chǎn)品開(kāi)發(fā)工作,設(shè)計(jì)出高品質(zhì)的軟件產(chǎn)品。

安卓軟件架構(gòu)工程師是根據(jù)軟件產(chǎn)品需求分析及可行性分析,進(jìn)行軟件開(kāi)發(fā)過(guò)程中所有流程與架構(gòu)的設(shè)計(jì)、控制及管理,并解決架構(gòu)中的技術(shù)問(wèn)題的專(zhuān)業(yè)技術(shù)人員。

android高級(jí)架構(gòu)工程師職業(yè)要求常見(jiàn)問(wèn)題

  • 什么是系統(tǒng)工程師、系統(tǒng)架構(gòu)工程師?

    系統(tǒng)工程師是指具備較高專(zhuān)業(yè)技術(shù)水平,能夠分析商業(yè)需求,并使用各種系統(tǒng)平臺(tái)和服務(wù)器軟件來(lái)設(shè)計(jì)并實(shí)現(xiàn)商務(wù)解決方案的基礎(chǔ)架構(gòu)的技術(shù)人員。系統(tǒng)架構(gòu)工程師:負(fù)責(zé)既定產(chǎn)品和項(xiàng)目的技術(shù)架構(gòu)設(shè)計(jì),從全局上把握技術(shù)方向...

  • 高級(jí)工程師職稱論文要求

    高級(jí)職稱對(duì)讠侖文發(fā)表期刊的要求比評(píng)中級(jí)職稱讠侖文發(fā)表的期刊要高。

  • android tv軟件工程師是做什么

    Android tv 軟件工程師是指從事Android移動(dòng)應(yīng)用操作系統(tǒng)、游戲和各種Android平臺(tái)功能的應(yīng)用、開(kāi)發(fā)和測(cè)試的技術(shù)人員。他的日常主要工作有:1、Android體系結(jié)構(gòu)和開(kāi)發(fā)環(huán)境2、And...

隨著移動(dòng)互聯(lián)網(wǎng)的到來(lái)和迅猛發(fā)展,移動(dòng)互聯(lián)網(wǎng)工程師的需求也是與日俱增。比如說(shuō)android市場(chǎng),國(guó)外Android市場(chǎng)正在如日中天的擴(kuò)展,據(jù)市場(chǎng)研究公司IDC最近發(fā)布研究報(bào)告稱,預(yù)計(jì)今年中國(guó)智能手機(jī)市場(chǎng)在全球市場(chǎng)上所占份額將會(huì)從去年的18.3%上升至26.5%,而美國(guó)市場(chǎng)所占份額則將從21.3%下降至17.8%。相信在不久的將來(lái)會(huì)有更多的用戶選擇Android系統(tǒng)的手機(jī)或是無(wú)線終端設(shè)備。近年來(lái),軟件領(lǐng)域也漸漸地流行起android軟件架構(gòu)工程師的角色,特別是對(duì)一些大型軟件產(chǎn)品或項(xiàng)目的開(kāi)發(fā),這一角色顯得很關(guān)鍵,因?yàn)槿狈玫能浖軜?gòu)工程師而導(dǎo)致項(xiàng)目失敗的例子不勝枚舉,一個(gè)沒(méi)有經(jīng)驗(yàn)和能力的軟件架構(gòu)工程師也會(huì)使項(xiàng)目失敗的速度加快。據(jù)智聯(lián)招聘統(tǒng)計(jì)薪資待遇在月薪為15000以上??梢?jiàn)android高級(jí)架構(gòu)工程師的發(fā)展前景極為可觀,待積累經(jīng)驗(yàn)后可向項(xiàng)目經(jīng)理發(fā)展。 2100433B

android高級(jí)架構(gòu)工程師職業(yè)要求文獻(xiàn)

《Android尋求的建筑》 《Android尋求的建筑》

格式:pdf

大?。?span id="luizccr" class="single-tag-height">240KB

頁(yè)數(shù): 6頁(yè)

評(píng)分: 4.4

高級(jí)工程師職位說(shuō)明書(shū)-高級(jí)工程師工作說(shuō)明書(shū) 高級(jí)工程師職位說(shuō)明書(shū)-高級(jí)工程師工作說(shuō)明書(shū)

格式:pdf

大小:240KB

頁(yè)數(shù): 1頁(yè)

評(píng)分: 4.5

職 位 說(shuō) 明 書(shū) 職位編號(hào):(HR 填寫(xiě)) 部 門(mén) 人力資源部信息室 職位名稱 高級(jí)工程師 職 位 概 要 溝通 對(duì)象 各部門(mén) 直接上級(jí)崗位 IT 室經(jīng)理 直接下級(jí)崗位 無(wú) 職位設(shè)置 目的 加強(qiáng)企業(yè)信息化的建設(shè), 開(kāi)發(fā)適應(yīng)制造業(yè)現(xiàn)行管理模式的應(yīng)用系統(tǒng), 整合信息孤島, 維護(hù)和完善現(xiàn)行系統(tǒng)的正常運(yùn)行,提高效率及管理水平 職位類(lèi)型 □管理類(lèi) □專(zhuān)業(yè)類(lèi) □行政類(lèi) □操作類(lèi) 職責(zé)概括 (并按重要性排序) 職責(zé)描述 (請(qǐng)列出本職位最關(guān)鍵的角色和職責(zé),不要超過(guò) 8項(xiàng)) 時(shí)間比 率 (%) 1、軟件項(xiàng)目 信息系統(tǒng)的調(diào)研、規(guī)劃、開(kāi)發(fā)、實(shí)施 50 2、桌面維護(hù) 制定標(biāo)準(zhǔn)流程,以服務(wù)心態(tài)保障所有電腦設(shè)備正常運(yùn)行 20 3、系統(tǒng)運(yùn)維 服務(wù)器、數(shù)據(jù)庫(kù)日常運(yùn)維、用戶問(wèn)題處理、培訓(xùn) 15 4、團(tuán)隊(duì)建設(shè) 關(guān)注團(tuán)隊(duì)成員能力提升、開(kāi)通團(tuán)隊(duì)成員晉升通道 10 5、信息支持 為所轄各部門(mén)日常工作中所需信息支持提供幫

立即下載

如果你覺(jué)得Java 7是一個(gè)過(guò)期的語(yǔ)言,并決定找一個(gè)更現(xiàn)代的語(yǔ)言代替。恭喜你!就如你知道的,雖然Java 8已經(jīng)發(fā)布了,它包含了很多我們期待的像現(xiàn)代語(yǔ)言中那樣的改善,但是我們Android開(kāi)發(fā)者還是被迫在使用Java 7.這是因?yàn)榉傻膯?wèn)題。但是就算沒(méi)有這個(gè)限制,并且新的Android設(shè)備從今天開(kāi)始使用新的能理解Java8的VM,在當(dāng)前的設(shè)備過(guò)期、幾乎沒(méi)有人使用它們之前我們也不能使用Java 8,所以恐怕我們不會(huì)很快等到這一天的到來(lái)。

但是并不是沒(méi)有補(bǔ)救的方法。多虧使用了JVM,我們可以使用任何語(yǔ)言去編寫(xiě)Android應(yīng)用,只要它能夠編譯成JVM能夠認(rèn)識(shí)的字節(jié)碼就可以了。

正如你所想,有很多選擇,比如Groovy,Scala,Clojure,當(dāng)然還有Kotlin。通過(guò)實(shí)踐,只有其中一些能夠被考慮來(lái)作為替代品。

上述的每一種語(yǔ)言都有它的利弊,如果你還沒(méi)有真正確定你該使用那種語(yǔ)言,我建議你可以去嘗試一下它們。

1. 什么是Kotlin?

Kotlin,如前面所說(shuō),它是JetBrains開(kāi)發(fā)的基于JVM的語(yǔ)言。JetBrains因?yàn)閯?chuàng)造了一個(gè)強(qiáng)大的Java開(kāi)發(fā)IDE被大家所熟知。Android Studio,官方的Android IDE,就是基于Intellij,作為一個(gè)該平臺(tái)的插件。

Kotlin是使用Java開(kāi)發(fā)者的思維被創(chuàng)建的,Intellij作為它主要的開(kāi)發(fā)IDE。對(duì)于Android開(kāi)發(fā)者,有兩個(gè)有趣的特點(diǎn):

對(duì)Java開(kāi)發(fā)者來(lái)說(shuō),Kotlin是非常直覺(jué)化的,并且非常容易學(xué)習(xí)。語(yǔ)言的大部分內(nèi)容都是與我們知道的非常相似,不同的地方,它的基礎(chǔ)概念也能迅速地掌握它。

它與我們?nèi)粘I钍褂玫腎DE無(wú)需配置就能完全整合。Android Studio能夠非常完美地理解、編譯運(yùn)行Kotlin代碼。而且對(duì)這門(mén)語(yǔ)言的支持來(lái)正是自于開(kāi)發(fā)了這個(gè)IDE的公司本身,所以我們Android開(kāi)發(fā)者是一等公民。

但是這僅僅是開(kāi)發(fā)語(yǔ)言和開(kāi)發(fā)工具之間的整合。相比Java 7的優(yōu)勢(shì)到底是什么呢?

它更加易表現(xiàn):這是它最重要的優(yōu)點(diǎn)之一。你可以編寫(xiě)少得多的代碼。

它更加安全:Kotlin是空安全的,也就是說(shuō)在我們編譯時(shí)期就處理了各種null的情況,避免了執(zhí)行時(shí)異常。如果一個(gè)對(duì)象可以是null,則我們需要明確地指定它,然后在使用它之前檢查它是否是null。你可以節(jié)約很多調(diào)試空指針異常的時(shí)間,解決掉null引發(fā)的bug。

它是函數(shù)式的:Kotlin是基于面向?qū)ο蟮恼Z(yǔ)言。但是就如其他很多現(xiàn)代的語(yǔ)言那樣,它使用了很多函數(shù)式編程的概念,比如,使用lambda表達(dá)式來(lái)更方便地解決問(wèn)題。其中一個(gè)很棒的特性就是Collections的處理方式。

它可以擴(kuò)展函數(shù):這意味著我們可以擴(kuò)展類(lèi)的更多的特性,甚至我們沒(méi)有權(quán)限去訪問(wèn)這個(gè)類(lèi)中的代碼。

它是高度互操作性的:你可以繼續(xù)使用所有的你用Java寫(xiě)的代碼和庫(kù),因?yàn)閮蓚€(gè)語(yǔ)言之間的互操作性是完美的。甚至可以在一個(gè)項(xiàng)目中使用Kotlin和Java兩種語(yǔ)言混合編程。

2. 我們通過(guò)Kotlin得到什么

不深入Kotlin語(yǔ)言(我們會(huì)在下一章再去學(xué)習(xí)),這里有一些Java中沒(méi)有的有趣的特性:

易表現(xiàn)

通過(guò)Kotlin,可以更容易地避免模版代碼因?yàn)榇蟛糠值牡湫颓闆r都在語(yǔ)言中默認(rèn)覆蓋實(shí)現(xiàn)了。舉個(gè)例子,在Java中,如果我們要典型的數(shù)據(jù)類(lèi),我們需要去編寫(xiě)(至少生成)這些代碼:

publicclassArtist{

privatelongid;

privateString name;

privateString url;

privateString mbid;

publiclonggetId(){

returnid;

}

publicvoidsetId(longid){

this.id = id;

}

publicString getName(){

returnname;

}

publicvoidsetName(String name){

this.name = name;

}

publicString getUrl(){

returnurl;

}

publicvoidsetUrl(String url){

this.url = url;

}

publicString getMbid(){

returnmbid;

}

publicvoidsetMbid(String mbid){

this.mbid = mbid;

}

@OverridepublicString toString(){

return"Artist{"+

"id="+ id +

", name='"+ name + '''+

", url='"+ url + '''+

", mbid='"+ mbid + '''+

'}';

}

}

使用Kotlin,我們只需要通過(guò)數(shù)據(jù)類(lèi):

data class Artist(

varid: Long,

varname: String,

varurl: String,

varmbid: String)

這個(gè)數(shù)據(jù)類(lèi),它會(huì)自動(dòng)生成所有屬性和它們的訪問(wèn)器,以及一些有用的方法,比如,toString()

空安全

當(dāng)我們使用Java開(kāi)發(fā)的時(shí)候,我們的代碼大多是防御性的。如果我們不想遇到NullPointerException,我們就需要在使用它之前不停地去判斷它是否為null。Kotlin,如很多現(xiàn)代的語(yǔ)言,是空安全的,因?yàn)槲覀冃枰ㄟ^(guò)一個(gè)安全調(diào)用操作符(寫(xiě)做?)來(lái)明確地指定一個(gè)對(duì)象是否能為空。

我們可以像這樣去寫(xiě):

// 這里不能通過(guò)編譯. Artist 不能是null

varnotNullArtist: Artist = null

// Artist 可以是 null

varartist: Artist? = null

// 無(wú)法編譯, artist可能是null,我們需要進(jìn)行處理

artist. print()

// 只要在artist != null時(shí)才會(huì)打印

artist?. print()

// 智能轉(zhuǎn)換. 如果我們?cè)谥斑M(jìn)行了空檢查,則不需要使用安全調(diào)用操作符調(diào)用

if(artist != null) {

artist. print()

}

// 只有在確保artist不是null的情況下才能這么調(diào)用,否則它會(huì)拋出異常

artist!!. print()

// 使用Elvis操作符來(lái)給定一個(gè)在是null的情況下的替代值

val name = artist?.name ?: "empty"

擴(kuò)展方法

我們可以給任何類(lèi)添加函數(shù)。它比那些我們項(xiàng)目中典型的工具類(lèi)更加具有可讀性。舉個(gè)例子,我們可以給fragment增加一個(gè)顯示toast的函數(shù):

fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {

Toast.makeText(getActivity(), message, duration).show()

}

我們現(xiàn)在可以這么做:

fragment.toast( "Hello world!")

函數(shù)式支持(Lambdas)

每次我們?nèi)ヂ暶饕粋€(gè)點(diǎn)擊所觸發(fā)的事件,可以只需要定義我們需要做些什么,而不是不得不去實(shí)現(xiàn)一個(gè)內(nèi)部類(lèi)?我們確實(shí)可以這么做,這個(gè)(或者其它更多我們感興趣的事件)我們需要感謝lambda:

view.setOnClickListener { toast( "Hello world!") }

這里只是挑選了很小一部分Kotlin可以簡(jiǎn)化我們代碼的事情?,F(xiàn)在你已經(jīng)知道這門(mén)語(yǔ)言的一些有趣的特性了,你可以考慮它是否是適合你的。如果你選擇繼續(xù),我們將在下一章開(kāi)始我們的實(shí)踐之旅。

距離活動(dòng)開(kāi)始還有一天,重慶的開(kāi)發(fā)者們趕快報(bào)名行動(dòng)起來(lái)吧!

最近的項(xiàng)目需要實(shí)現(xiàn)一個(gè) Android 手機(jī)之間無(wú)網(wǎng)絡(luò)傳輸文件的功能,就發(fā)現(xiàn)了 Wifi P2P(Wifi點(diǎn)對(duì)點(diǎn))這么一個(gè)功能,最后也實(shí)現(xiàn)了通過(guò) Wifi 隔空傳輸文件的功能,這里我也來(lái)整理下代碼,分享給大家。

Wifi P2P 是在 Android 4.0 以及更高版本系統(tǒng)中加入的功能,通過(guò) Wifi P2P 可以在不連接網(wǎng)絡(luò)的情況下,直接與配對(duì)的設(shè)備進(jìn)行數(shù)據(jù)交換。相對(duì)于藍(lán)牙,Wifi P2P 的搜索速度和傳輸速度更快,傳輸距離更遠(yuǎn)

實(shí)現(xiàn)的效果如下所示:

一般而言,開(kāi)發(fā)步驟分為以下幾點(diǎn):

在 AndroidManifest 中聲明相關(guān)權(quán)限(網(wǎng)絡(luò)和文件讀寫(xiě)權(quán)限) 獲取 WifiP2pManager ,注冊(cè)相關(guān)廣播監(jiān)聽(tīng)Wifi直連的狀態(tài)變化 指定某一臺(tái)設(shè)備為服務(wù)器(用來(lái)接收文件),創(chuàng)建群組并作為群主存在,在指定端口監(jiān)聽(tīng)客戶端的連接請(qǐng)求,等待客戶端發(fā)起連接請(qǐng)求以及文件傳輸請(qǐng)求 客戶端(用來(lái)發(fā)送文件)主動(dòng)搜索附近的設(shè)備,加入到服務(wù)器創(chuàng)建的群組,獲取服務(wù)器的IP地址,向其發(fā)起文件傳輸請(qǐng)求 校驗(yàn)文件完整性 一、聲明權(quán)限

Wifi P2P 技術(shù)并不會(huì)訪問(wèn)網(wǎng)絡(luò),但由于會(huì)使用到 Java socket,所以需要申請(qǐng)網(wǎng)絡(luò)權(quán)限。此外,由于是要實(shí)現(xiàn)文件互傳,所以也需要申請(qǐng)SD卡讀寫(xiě)權(quán)限。

<uses-permissionandroid:name="android.permission.ACCESS_WIFI_STATE"/><uses-permissionandroid:name="android.permission.CHANGE_WIFI_STATE"/><uses-permissionandroid:name="android.permission.CHANGE_NETWORK_STATE"/><uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE"/>二、注冊(cè)廣播

與 Wifi P2P 相關(guān)的廣播有以下幾個(gè):

WIFI_P2P_STATE_CHANGED_ACTION( 用于指示 Wifi P2P 是否可用 ) WIFI_P2P_PEERS_CHANGED_ACTION( 對(duì)等節(jié)點(diǎn)列表發(fā)生了變化 ) WIFI_P2P_CONNECTION_CHANGED_ACTION( Wifi P2P 的連接狀態(tài)發(fā)生了改變 ) WIFI_P2P_THIS_DEVICE_CHANGED_ACTION( 本設(shè)備的設(shè)備信息發(fā)生了變化 )

當(dāng)接收到這幾個(gè)廣播時(shí),我們都需要到 WifiP2pManager (對(duì)等網(wǎng)絡(luò)管理器)來(lái)進(jìn)行相應(yīng)的信息請(qǐng)求,此外還需要用到 Channel 對(duì)象作為請(qǐng)求參數(shù)

mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);mChannel = mWifiP2pManager.initialize( this, getMainLooper(), this);

當(dāng)收到 WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION廣播時(shí),可以判斷當(dāng)前 Wifi P2P是否可用

intstate = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, - 1); if(state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { mDirectActionListener.wifiP2pEnabled( true);} else{ mDirectActionListener.wifiP2pEnabled( false); }

當(dāng)收到 WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION廣播時(shí),意味設(shè)備周?chē)目捎迷O(shè)備列表發(fā)生了變化,可以通過(guò) requestPeers方法得到可用的設(shè)備列表,之后就可以選擇當(dāng)中的某一個(gè)設(shè)備進(jìn)行連接操作

mWifiP2pManager.requestPeers(mChannel, newWifiP2pManager.PeerListListener() { @OverridepublicvoidonPeersAvailable(WifiP2pDeviceList peers){ mDirectActionListener.onPeersAvailable(peers.getDeviceList()); }});

當(dāng)收到 WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION廣播時(shí),意味著 Wifi P2P 的連接狀態(tài)發(fā)生了變化,可能是連接到了某設(shè)備,或者是與某設(shè)備斷開(kāi)了連接

NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); if(networkInfo.isConnected()) { mWifiP2pManager.requestConnectionInfo(mChannel, newWifiP2pManager.ConnectionInfoListener() { @OverridepublicvoidonConnectionInfoAvailable(WifiP2pInfo info){ mDirectActionListener.onConnectionInfoAvailable(info); } }); Log.e(TAG, "已連接p2p設(shè)備");} else{ mDirectActionListener.onDisconnection(); Log.e(TAG, "與p2p設(shè)備已斷開(kāi)連接");}

如果是與某設(shè)備連接上了,則可以通過(guò) requestConnectionInfo方法獲取到連接信息

當(dāng)收到 WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION廣播時(shí),則可以獲取到本設(shè)備變化后的設(shè)備信息

(WifiP2pDevice) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)

可以看出 Wifi P2P 的接口高度異步化,到現(xiàn)在已經(jīng)用到了三個(gè)系統(tǒng)的回調(diào)函數(shù),一個(gè)用于 WifiP2pManager 的初始化,兩個(gè)用于在廣播中異步請(qǐng)求數(shù)據(jù),為了簡(jiǎn)化操作,此處統(tǒng)一使用一個(gè)自定義的回調(diào)函數(shù),方法含義與系統(tǒng)的回調(diào)函數(shù)一致

publicinterfaceDirectActionListenerextendsWifiP2pManager.ChannelListener{

voidwifiP2pEnabled(booleanenabled);

voidonConnectionInfoAvailable(WifiP2pInfo wifiP2pInfo);

voidonDisconnection();

voidonSelfDeviceAvailable(WifiP2pDevice wifiP2pDevice);

voidonPeersAvailable(Collection<WifiP2pDevice> wifiP2pDeviceList);}

所以,整個(gè)廣播接收器使用到的所有代碼是:

/** * 作者:chenZY * 時(shí)間:2018/2/9 17:53 * 描述: */

publicclassDirectBroadcastReceiverextendsBroadcastReceiver{

privatestaticfinalString TAG = "DirectBroadcastReceiver";

privateWifiP2pManager mWifiP2pManager;

privateWifiP2pManager.Channel mChannel;

privateDirectActionListener mDirectActionListener;

publicDirectBroadcastReceiver(WifiP2pManager wifiP2pManager, WifiP2pManager.Channel channel, DirectActionListener directActionListener){ mWifiP2pManager = wifiP2pManager; mChannel = channel; mDirectActionListener = directActionListener; }

publicstaticIntentFilter getIntentFilter(){ IntentFilter intentFilter = newIntentFilter(); intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);

returnintentFilter; }

@OverridepublicvoidonReceive(Context context, Intent intent){ Log.e(TAG, "接收到廣播: "+ intent.getAction());

if(!TextUtils.isEmpty(intent.getAction())) {

switch(intent.getAction()) {

// 用于指示 Wifi P2P 是否可用caseWifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION: {

intstate = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, - 1);

if(state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { mDirectActionListener.wifiP2pEnabled( true); } else{ mDirectActionListener.wifiP2pEnabled( false); List<WifiP2pDevice> wifiP2pDeviceList = newArrayList<>(); mDirectActionListener.onPeersAvailable(wifiP2pDeviceList); }

break; }

// 對(duì)等節(jié)點(diǎn)列表發(fā)生了變化caseWifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION: { mWifiP2pManager.requestPeers(mChannel, newWifiP2pManager.PeerListListener() {

@OverridepublicvoidonPeersAvailable(WifiP2pDeviceList peers){ mDirectActionListener.onPeersAvailable(peers.getDeviceList()); } });

break; }

// Wifi P2P 的連接狀態(tài)發(fā)生了改變caseWifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION: { NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);

if(networkInfo.isConnected()) { mWifiP2pManager.requestConnectionInfo(mChannel, newWifiP2pManager.ConnectionInfoListener() {

@OverridepublicvoidonConnectionInfoAvailable(WifiP2pInfo info){ mDirectActionListener.onConnectionInfoAvailable(info); } }); Log.e(TAG, "已連接p2p設(shè)備"); } else{ mDirectActionListener.onDisconnection(); Log.e(TAG, "與p2p設(shè)備已斷開(kāi)連接"); }

break; }

//本設(shè)備的設(shè)備信息發(fā)生了變化caseWifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION: { mDirectActionListener.onSelfDeviceAvailable((WifiP2pDevice) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));

break; } } } }} 三、服務(wù)器端創(chuàng)建群組

假設(shè)當(dāng)設(shè)備A搜索到了設(shè)備B,并與設(shè)備B連接到了一起,此時(shí)系統(tǒng)會(huì)自動(dòng)創(chuàng)建一個(gè)群組(Group)并隨機(jī)指定一臺(tái)設(shè)備為群主(GroupOwner)。此時(shí),對(duì)于兩臺(tái)設(shè)備來(lái)說(shuō),群主的IP地址是可知的(系統(tǒng)回調(diào)函數(shù)中有提供),但客戶端的IP地址需要再來(lái)通過(guò)其他方法來(lái)主動(dòng)獲取。例如,可以在設(shè)備連接成功后,客戶端主動(dòng)發(fā)起對(duì)服務(wù)器端的Socket連接請(qǐng)求,服務(wù)器端在指定端口監(jiān)聽(tīng)客戶端的連接請(qǐng)求,當(dāng)連接成功后,服務(wù)器端就可以獲取到客戶端的IP地址了

此處為了簡(jiǎn)化操作,直接指定某臺(tái)設(shè)備作為服務(wù)器端(群主),即直接指定某臺(tái)設(shè)備用來(lái)接收文件

因此,服務(wù)器端要主動(dòng)創(chuàng)建群組,并等待客戶端的連接

wifiP2pManager.createGroup(channel, newWifiP2pManager.ActionListener() {

@OverridepublicvoidonSuccess(){ Log.e(TAG, "createGroup onSuccess"); dismissLoadingDialog(); showToast( "onSuccess"); }

@OverridepublicvoidonFailure(intreason){ Log.e(TAG, "createGroup onFailure: "+ reason); dismissLoadingDialog(); showToast( "onFailure"); }});

此處,使用 IntentService 在后臺(tái)監(jiān)聽(tīng)客戶端的Socket連接請(qǐng)求,并通過(guò)輸入輸出流來(lái)傳輸文件。此處的代碼比較簡(jiǎn)單,就只是在指定端口一直堵塞監(jiān)聽(tīng)客戶端的連接請(qǐng)求,獲取待傳輸?shù)奈募畔⒛P?FileTransfer ,之后就進(jìn)行實(shí)際的數(shù)據(jù)傳輸

@OverrideprotectedvoidonHandleIntent(Intent intent){ clean(); File file = null;

try{ serverSocket = newServerSocket(); serverSocket.setReuseAddress( true); serverSocket.bind( newInetSocketAddress(PORT)); Socket client = serverSocket.accept(); Log.e(TAG, "客戶端IP地址 : "+ client.getInetAddress().getHostAddress()); inputStream = client.getInputStream(); objectInputStream = newObjectInputStream(inputStream); FileTransfer fileTransfer = (FileTransfer) objectInputStream.readObject(); Log.e(TAG, "待接收的文件: "+ fileTransfer); String name = newFile(fileTransfer.getFilePath()).getName();

//將文件存儲(chǔ)至指定位置file = newFile(Environment.getExternalStorageDirectory() + "/"+ name); fileOutputStream = newFileOutputStream(file);

bytebuf[] = newbyte[ 512];

intlen;

longtotal = 0;

intprogress;

while((len = inputStream.read(buf)) != - 1) { fileOutputStream.write(buf, 0, len); total += len; progress = ( int) ((total * 100) / fileTransfer.getFileLength()); Log.e(TAG, "文件接收進(jìn)度: "+ progress);

if(progressChangListener != null) { progressChangListener.onProgressChanged(fileTransfer, progress); } } serverSocket.close(); inputStream.close(); objectInputStream.close(); fileOutputStream.close(); serverSocket = null; inputStream = null; objectInputStream = null; fileOutputStream = null; Log.e(TAG, "文件接收成功,文件的MD5碼是:"+ Md5Util.getMd5(file)); } catch(Exception e) { Log.e(TAG, "文件接收 Exception: "+ e.getMessage()); } finally{ clean();

if(progressChangListener != null) { progressChangListener.onTransferFinished(file); }

//再次啟動(dòng)服務(wù),等待客戶端下次連接startService( newIntent( this, WifiServerService.class)); } }

因?yàn)榭蛻舳丝赡軙?huì)多次發(fā)起連接請(qǐng)求,所以當(dāng)此處文件傳輸完成后(不管成功或失?。?,都需要重新 startService ,讓服務(wù)再次堵塞等待客戶端的連接請(qǐng)求

FileTransfer 包含三個(gè)字段,MD5碼值用于校驗(yàn)文件的完整性,fileLength 是為了用于計(jì)算文件的傳輸進(jìn)度

publicclassFileTransferimplementsSerializable{

//文件路徑privateString filePath;

//文件大小privatelongfileLength;

//MD5碼privateString md5; ···}

為了將文件傳輸進(jìn)度發(fā)布到外部界面,所以除了需要啟動(dòng)Service外,界面還需要綁定Service,此處就需要用到一個(gè)更新文件傳輸狀態(tài)的接口

publicinterfaceOnProgressChangListener{

//當(dāng)傳輸進(jìn)度發(fā)生變化時(shí)voidonProgressChanged(FileTransfer fileTransfer, intprogress);

//當(dāng)傳輸結(jié)束時(shí)voidonTransferFinished(File file); }

因此,需要將 progressChangListener 作為參數(shù)傳給 WifiServerService ,并在進(jìn)度變化時(shí)更新進(jìn)度對(duì)話框

privateWifiServerService.OnProgressChangListener progressChangListener = newWifiServerService.OnProgressChangListener() {

@OverridepublicvoidonProgressChanged(finalFileTransfer fileTransfer, finalintprogress){ runOnUiThread( newRunnable() {

@Overridepublicvoidrun(){ progressDialog.setMessage( "文件名: "+ newFile(fileTransfer.getFilePath()).getName()); progressDialog.setProgress(progress); progressDialog.show(); } }); }

@OverridepublicvoidonTransferFinished(finalFile file){ runOnUiThread( newRunnable() {

@Overridepublicvoidrun(){ progressDialog.cancel();

if(file != null&& file.exists()) { openFile(file.getPath()); } } }); } }; 四、客戶端加入群組并發(fā)起文件傳輸請(qǐng)求

文件發(fā)送界面 SendFileActivity 需要實(shí)現(xiàn) DirectActionListener 接口

首先,需要先注冊(cè)P2P廣播,以便獲取周邊設(shè)備信息以及連接狀態(tài)

@OverrideprotectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState); setContentView(R.layout.activity_send_file); initView(); mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); mChannel = mWifiP2pManager.initialize( this, getMainLooper(), this); broadcastReceiver = newDirectBroadcastReceiver(mWifiP2pManager, mChannel, this); registerReceiver(broadcastReceiver, DirectBroadcastReceiver.getIntentFilter()); }

通過(guò) discoverPeers方法搜索周邊設(shè)備,回調(diào)函數(shù)用于通知方法是否調(diào)用成功

mWifiP2pManager.discoverPeers(mChannel, newWifiP2pManager.ActionListener() { @OverridepublicvoidonSuccess(){ showToast( "Success"); }

@OverridepublicvoidonFailure(intreasonCode){ showToast( "Failure"); loadingDialog.cancel(); }});

當(dāng)搜索結(jié)束后,系統(tǒng)就會(huì)觸發(fā) WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION廣播,此時(shí)就可以調(diào)用 requestPeers方法獲取設(shè)備列表信息,此處用 RecyclerView 展示列表,在 onPeersAvailable 方法刷新列表

mWifiP2pManager.requestPeers(mChannel, newWifiP2pManager.PeerListListener() {

@OverridepublicvoidonPeersAvailable(WifiP2pDeviceList peers){ mDirectActionListener.onPeersAvailable(peers.getDeviceList()); }}); @OverridepublicvoidonPeersAvailable(Collection<WifiP2pDevice> wifiP2pDeviceList){ Log.e(TAG, "onPeersAvailable :"+ wifiP2pDeviceList.size());

this.wifiP2pDeviceList.clear();

this.wifiP2pDeviceList.addAll(wifiP2pDeviceList); deviceAdapter.notifyDataSetChanged(); loadingDialog.cancel(); }

之后,通過(guò)點(diǎn)擊事件選中群主(服務(wù)器端)設(shè)備,通過(guò) connect方法請(qǐng)求與之進(jìn)行連接

privatevoidconnect(){ WifiP2pConfig config = newWifiP2pConfig();

if(config.deviceAddress != null&& mWifiP2pDevice != null) { config.deviceAddress = mWifiP2pDevice.deviceAddress; config.wps.setup = WpsInfo.PBC; showLoadingDialog( "正在連接 "+ mWifiP2pDevice.deviceName); mWifiP2pManager.connect(mChannel, config, newWifiP2pManager.ActionListener() {

@OverridepublicvoidonSuccess(){ Log.e(TAG, "connect onSuccess"); }

@OverridepublicvoidonFailure(intreason){ showToast( "連接失敗 "+ reason); dismissLoadingDialog(); } }); }}

此處依然無(wú)法通過(guò)函數(shù)函數(shù)來(lái)判斷連接結(jié)果,需要依靠系統(tǒng)發(fā)出的 WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION方法來(lái)獲取到連接結(jié)果,在此處可以通過(guò) requestConnectionInfo獲取到組連接信息,信息最后通過(guò) onConnectionInfoAvailable方法傳遞出來(lái),在此可以判斷當(dāng)前設(shè)備是否為群主,獲取群組IP地址

@Override

publicvoidonConnectionInfoAvailable(WifiP2pInfo wifiP2pInfo){ dismissLoadingDialog(); wifiP2pDeviceList.clear(); deviceAdapter.notifyDataSetChanged(); btn_disconnect.setEnabled( true); btn_chooseFile.setEnabled( true); Log.e(TAG, "onConnectionInfoAvailable"); Log.e(TAG, "onConnectionInfoAvailable groupFormed: "+ wifiP2pInfo.groupFormed); Log.e(TAG, "onConnectionInfoAvailable isGroupOwner: "+ wifiP2pInfo.isGroupOwner); Log.e(TAG, "onConnectionInfoAvailable getHostAddress: "+ wifiP2pInfo.groupOwnerAddress.getHostAddress()); StringBuilder stringBuilder = newStringBuilder();

if(mWifiP2pDevice != null) { stringBuilder.append( "連接的設(shè)備名:"); stringBuilder.append(mWifiP2pDevice.deviceName); stringBuilder.append( "n"); stringBuilder.append( "連接的設(shè)備的地址:"); stringBuilder.append(mWifiP2pDevice.deviceAddress); } stringBuilder.append( "n"); stringBuilder.append( "是否群主:"); stringBuilder.append(wifiP2pInfo.isGroupOwner ? "是群主": "非群主"); stringBuilder.append( "n"); stringBuilder.append( "群主IP地址:"); stringBuilder.append(wifiP2pInfo.groupOwnerAddress.getHostAddress()); tv_status.setText(stringBuilder);

if(wifiP2pInfo.groupFormed && !wifiP2pInfo.isGroupOwner) {

this.wifiP2pInfo = wifiP2pInfo; }}

至此服務(wù)器端和客戶端已經(jīng)通過(guò) Wifi P2P 連接在了一起,客戶端也獲取到了服務(wù)器端的IP地址,在選取好待發(fā)送的文件后就可以主動(dòng)發(fā)起對(duì)服務(wù)器端的連接請(qǐng)求了

發(fā)起選取文件的方法

Intent intent = newIntent(Intent.ACTION_GET_CONTENT);intent.setType( "*/*");intent.addCategory(Intent.CATEGORY_OPENABLE);startActivityForResult(intent, 1);

獲取選取的文件的實(shí)際路徑

@Override

protectedvoidonActivityResult(intrequestCode, intresultCode, Intent data){

super.onActivityResult(requestCode, resultCode, data);

if(requestCode == 1) {

if(resultCode == RESULT_OK) { Uri uri = data.getData();

if(uri != null) { String path = getPath( this, uri);

if(path != null) { File file = newFile(path);

if(file.exists() && wifiP2pInfo != null) { FileTransfer fileTransfer = newFileTransfer(file.getPath(), file.length()); Log.e(TAG, "待發(fā)送的文件:"+ fileTransfer);

newWifiClientTask( this, fileTransfer).execute(wifiP2pInfo.groupOwnerAddress.getHostAddress()); } } } } }}

privateString getPath(Context context, Uri uri){

if( "content".equalsIgnoreCase(uri.getScheme())) { Cursor cursor = context.getContentResolver().query(uri, newString[]{ "_data"}, null, null, null);

if(cursor != null) {

if(cursor.moveToFirst()) { String data = cursor.getString(cursor.getColumnIndex( "_data")); cursor.close();

returndata; } } } elseif("file".equalsIgnoreCase(uri.getScheme())) {

returnuri.getPath(); }

returnnull;}

文件的發(fā)送操作放到 AsyncTask 中處理,將服務(wù)器端的IP地址作為參數(shù)傳進(jìn)來(lái),在正式發(fā)送文件前,先發(fā)送包含文件信息(文件名,文件大小,文件MD5碼)的信息模型 FileTransfer ,并在發(fā)送文件的過(guò)程中同時(shí)更新進(jìn)度

/** * 作者:葉應(yīng)是葉 * 時(shí)間:2018/2/15 8:51 * 描述:客戶端發(fā)送文件 */

publicclassWifiClientTaskextendsAsyncTask<String, Integer, Boolean> {

privateProgressDialog progressDialog;

privateFileTransfer fileTransfer;

privatestaticfinalintPORT = 4786;

privatestaticfinalString TAG = "WifiClientTask";

publicWifiClientTask(Context context, FileTransfer fileTransfer){

this.fileTransfer = fileTransfer; progressDialog = newProgressDialog(context); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setCancelable( false); progressDialog.setCanceledOnTouchOutside( false); progressDialog.setTitle( "正在發(fā)送文件"); progressDialog.setMax( 100); }

@OverrideprotectedvoidonPreExecute(){ progressDialog.show(); }

@OverrideprotectedBoolean doInBackground(String... strings){ fileTransfer.setMd5(Md5Util.getMd5( newFile(fileTransfer.getFilePath()))); Log.e(TAG, "文件的MD5碼值是:"+ fileTransfer.getMd5()); Socket socket = null; OutputStream outputStream = null; ObjectOutputStream objectOutputStream = null; InputStream inputStream = null;

try{ socket = newSocket(); socket.bind( null); socket.connect(( newInetSocketAddress(strings[ 0], PORT)), 10000); outputStream = socket.getOutputStream(); objectOutputStream = newObjectOutputStream(outputStream); objectOutputStream.writeObject(fileTransfer); inputStream = newFileInputStream( newFile(fileTransfer.getFilePath()));

longfileSize = fileTransfer.getFileLength();

longtotal = 0;

bytebuf[] = newbyte[ 512];

intlen;

while((len = inputStream.read(buf)) != - 1) { outputStream.write(buf, 0, len); total += len;

intprogress = ( int) ((total * 100) / fileSize); publishProgress(progress); Log.e(TAG, "文件發(fā)送進(jìn)度:"+ progress); } outputStream.close(); objectOutputStream.close(); inputStream.close(); socket.close(); outputStream = null; objectOutputStream = null; inputStream = null; socket = null; Log.e(TAG, "文件發(fā)送成功");

returntrue; } catch(Exception e) { Log.e(TAG, "文件發(fā)送異常 Exception: "+ e.getMessage()); } finally{

if(outputStream != null) {

try{ outputStream.close(); } catch(IOException e) { e.printStackTrace(); } }

if(objectOutputStream != null) {

try{ objectOutputStream.close(); } catch(IOException e) { e.printStackTrace(); } }

if(inputStream != null) {

try{ inputStream.close(); } catch(IOException e) { e.printStackTrace(); } }

if(socket != null) {

try{ socket.close(); } catch(Exception e) { e.printStackTrace(); } } }

returnfalse; }

@OverrideprotectedvoidonProgressUpdate(Integer... values){ progressDialog.setProgress(values[ 0]); }

@OverrideprotectedvoidonPostExecute(Boolean aBoolean){ progressDialog.cancel(); Log.e(TAG, "onPostExecute: "+ aBoolean); }} 五、校驗(yàn)文件完整性

傳輸文件的完整性主要是通過(guò)計(jì)算文件的MD5碼值來(lái)保證了,在發(fā)送文件前,即在 WifiClientTask 的 doInBackground 方法中進(jìn)行計(jì)算,將MD5碼值賦給 FileTransfer 模型,通過(guò)如下方法計(jì)算得到

/** * 作者:葉應(yīng)是葉 * 時(shí)間:2018/2/14 21:16 * 描述: */

publicclassMd5Util{

publicstaticString getMd5(File file){ InputStream inputStream = null;

byte[] buffer = newbyte[ 2048];

intnumRead; MessageDigest md5;

try{ inputStream = newFileInputStream(file); md5 = MessageDigest.getInstance( "MD5");

while((numRead = inputStream.read(buffer)) > 0) { md5.update(buffer, 0, numRead); } inputStream.close(); inputStream = null;

returnmd5ToString(md5.digest()); } catch(Exception e) {

returnnull; } finally{

if(inputStream != null) {

try{ inputStream.close(); } catch(IOException e) { e.printStackTrace(); } } } }

privatestaticString md5ToString(byte[] md5Bytes){ StringBuilder hexValue = newStringBuilder();

for( byteb : md5Bytes) {

intval = (( int) b) & 0xff;

if(val < 16) { hexValue.append( "0"); } hexValue.append(Integer.toHexString(val)); }

returnhexValue.toString(); }}

因?yàn)榭蛻舳藭?huì)將 FileTransfer 傳給服務(wù)器端,所以服務(wù)器端在文件傳輸結(jié)束后,可以重新計(jì)算文件的MD5碼值,進(jìn)行對(duì)比以判斷文件是否完整。

代碼地址:https://github.com/leavesC/WifiP2P

大家都在看

距離活動(dòng)開(kāi)始還有兩天,深圳的開(kāi)發(fā)者們趕快報(bào)名行動(dòng)起來(lái)吧!

在我的上一篇文章:Android 如何實(shí)現(xiàn)無(wú)網(wǎng)絡(luò)傳輸文件,我介紹了通過(guò) Wifi Direct(Wifi 直連)實(shí)現(xiàn) Android 設(shè)備之間進(jìn)行文件傳輸?shù)姆椒ǎ梢栽跓o(wú)移動(dòng)網(wǎng)絡(luò)的情況下實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)的文件傳輸

本來(lái)覺(jué)得這樣也就夠了,可在要應(yīng)用到實(shí)際項(xiàng)目的時(shí)候,又考慮到用戶的設(shè)備系統(tǒng)版本可能并不都符合要求(Wifi Direct 是 Android 4.0 后支持的功能,話說(shuō)低于 4.4 版本的手機(jī)應(yīng)該都很少了吧?),而且我也不確定 IOS 系統(tǒng)是否支持 Wifi Direct,所以為了讓文件傳輸邏輯可以應(yīng)用到更多的設(shè)備上,就又實(shí)現(xiàn)了通過(guò) Wifi熱點(diǎn)進(jìn)行文件傳輸?shù)墓δ?/p>

相比于通過(guò) Wiif Direct 進(jìn)行文件傳輸,通過(guò) Wifi 熱點(diǎn)進(jìn)行設(shè)備配對(duì)更加方便,邏輯也更為直接,傳輸一個(gè)1G左右的壓縮包用了5分鐘左右的時(shí)間,平均傳輸速率有 3.5 M/S左右。此外,相對(duì)于上個(gè)版本,新版本除了提供傳輸進(jìn)度外,還提供了傳輸速率、預(yù)估完成時(shí)間、文件傳輸前后的MD5碼等數(shù)據(jù)

實(shí)現(xiàn)的效果如下所示:

開(kāi)啟Ap熱點(diǎn)接收文件

連接Wiif熱點(diǎn)發(fā)送文件

文件傳輸完成后校驗(yàn)文件完整性

開(kāi)發(fā)步驟分為以下幾點(diǎn):

在 AndroidManifest 中聲明相關(guān)權(quán)限(網(wǎng)絡(luò)和文件讀寫(xiě)權(quán)限) 文件接收端開(kāi)啟Ap熱點(diǎn),作為服務(wù)器端建立Socket,在指定端口等待客戶端的連接 文件發(fā)送端連接到Wifi熱點(diǎn),作為客戶端主動(dòng)連接到服務(wù)器端 文件發(fā)送端將待發(fā)送的文件信息模型(包括文件路徑,文件大小和文件MD5碼等信息)通過(guò)Socket發(fā)送給文件接收端 文件發(fā)送端發(fā)起實(shí)際的文件傳輸請(qǐng)求,接收端和發(fā)送端根據(jù)已接收到或已發(fā)送的的文件字節(jié)數(shù),計(jì)算文件傳輸進(jìn)度、文件傳輸速率和預(yù)估完成時(shí)間等數(shù)據(jù) 文件傳輸結(jié)束后,對(duì)比文件信息模型攜帶來(lái)的MD5碼值與本地文件重新計(jì)算生成的MD5碼是否相等,以此校驗(yàn)文件完整性 一、聲明權(quán)限

本應(yīng)用并不會(huì)消耗移動(dòng)數(shù)據(jù),但由于要使用到 Wifi 以及 Java Socket,所以需要申請(qǐng)網(wǎng)絡(luò)相關(guān)的權(quán)限。此外,由于是要實(shí)現(xiàn)文件互傳,所以也需要申請(qǐng)SD卡讀寫(xiě)權(quán)限。

<uses-permissionandroid:name="android.permission.ACCESS_WIFI_STATE"/>

<uses-permissionandroid:name="android.permission.CHANGE_WIFI_STATE"/>

<uses-permissionandroid:name="android.permission.CHANGE_NETWORK_STATE"/>

<uses-permissionandroid:name="android.permission.INTERNET"/>

<uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/>

<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE"/>

二、文件接收端

文件接收端作為服務(wù)器存在,需要主動(dòng)開(kāi)啟Ap熱點(diǎn)供文件發(fā)送端連接,此處開(kāi)啟Ap熱點(diǎn)的方法是通過(guò)反射來(lái)實(shí)現(xiàn),這種方法雖然方便,但并不保證在所有系統(tǒng)上都能成功,比如我在 7.1.2 版本系統(tǒng)上就開(kāi)啟不了,最好還是引導(dǎo)用戶去主動(dòng)開(kāi)啟

/**

* 開(kāi)啟便攜熱點(diǎn)

*

* @paramcontext 上下文

* @paramssid SSID

* @parampassword 密碼

* @return是否成功

*/

publicstaticbooleanopenAp(Context context, String ssid, String password){

WifiManager wifimanager = (WifiManager) context.getApplicationContext().getSystemService(WIFI_SERVICE);

if(wifimanager == null) {

returnfalse;

}

if(wifimanager.isWifiEnabled()) {

wifimanager.setWifiEnabled( false);

}

try{

Method method = wifimanager.getClass().getMethod( "setWifiApEnabled", WifiConfiguration.class, boolean.class);

method.invoke(wifimanager, null, false);

method = wifimanager.getClass().getMethod( "setWifiApEnabled", WifiConfiguration.class, boolean.class);

method.invoke(wifimanager, createApConfiguration(ssid, password), true);

returntrue;

} catch(Exception e) {

e.printStackTrace();

}

returnfalse;

}

/**

* 關(guān)閉便攜熱點(diǎn)

*

* @paramcontext 上下文

*/

publicstaticvoidcloseAp(Context context){

WifiManager wifimanager = (WifiManager) context.getApplicationContext().getSystemService(WIFI_SERVICE);

try{

Method method = wifimanager.getClass().getMethod( "setWifiApEnabled", WifiConfiguration.class, boolean.class);

method.invoke(wifimanager, null, false);

} catch(Exception e) {

e.printStackTrace();

}

}

此處需要先定義一個(gè)文件信息模型 FileTransfer ,F(xiàn)ileTransfer 包含三個(gè)字段,MD5碼值用于校驗(yàn)文件的完整性,fileLength 是為了用于計(jì)算文件的傳輸進(jìn)度和傳輸速率

publicclassFileTransferimplementsSerializable{

//文件路徑

privateString filePath;

//文件大小

privatelongfileLength;

//MD5碼

privateString md5;

···

}

Ap熱點(diǎn)開(kāi)啟成功后,就可以啟動(dòng)一個(gè)服務(wù)在后臺(tái)等待文件發(fā)送端來(lái)主動(dòng)連接了,這里使用 IntentService 在后臺(tái)監(jiān)聽(tīng)客戶端的Socket連接請(qǐng)求,并通過(guò)輸入輸出流來(lái)傳輸文件。此處的代碼比較簡(jiǎn)單,就只是在指定端口一直堵塞監(jiān)聽(tīng)客戶端的連接請(qǐng)求,獲取待傳輸?shù)奈募畔⒛P?FileTransfer ,之后就進(jìn)行實(shí)際的數(shù)據(jù)傳輸

文件傳輸速率是每一秒計(jì)算一次,根據(jù)這段時(shí)間內(nèi)接收的字節(jié)數(shù)與消耗的時(shí)間做除法,從而得到傳輸速率,再通過(guò)將剩余的未傳輸字節(jié)數(shù)與傳輸速率做除法,從而得到預(yù)估的剩余傳輸時(shí)間

@Override

protectedvoidonHandleIntent(Intent intent){

clean();

File file = null;

try{

serverSocket = newServerSocket();

serverSocket.setReuseAddress( true);

serverSocket.bind( newInetSocketAddress(Constants.PORT));

Socket client = serverSocket.accept();

Log.e(TAG, "客戶端IP地址 : "+ client.getInetAddress().getHostAddress());

inputStream = client.getInputStream();

objectInputStream = newObjectInputStream(inputStream);

FileTransfer fileTransfer = (FileTransfer) objectInputStream.readObject();

Log.e(TAG, "待接收的文件: "+ fileTransfer);

String name = newFile(fileTransfer.getFilePath()).getName();

//將文件存儲(chǔ)至指定位置

file = newFile(Environment.getExternalStorageDirectory() + "/"+ name);

fileOutputStream = newFileOutputStream(file);

bytebuf[] = newbyte[ 512];

intlen;

//文件大小

longfileSize = fileTransfer.getFileLength();

//當(dāng)前的傳輸進(jìn)度

intprogress;

//總的已接收字節(jié)數(shù)

longtotal = 0;

//緩存-當(dāng)次更新進(jìn)度時(shí)的時(shí)間

longtempTime = System.currentTimeMillis();

//緩存-當(dāng)次更新進(jìn)度時(shí)已接收的總字節(jié)數(shù)

longtempTotal = 0;

//傳輸速率(Kb/s)

doublespeed = 0;

//預(yù)估的剩余完成時(shí)間(秒)

longremainingTime;

while((len = inputStream.read(buf)) != - 1) {

fileOutputStream.write(buf, 0, len);

total += len;

longtime = System.currentTimeMillis() - tempTime;

//每一秒更新一次傳輸速率和傳輸進(jìn)度

if(time > 1000) {

//當(dāng)前的傳輸進(jìn)度

progress = ( int) (total * 100/ fileSize);

Logger.e(TAG, "---------------------------");

Logger.e(TAG, "傳輸進(jìn)度: "+ progress);

Logger.e(TAG, "時(shí)間變化:"+ time / 1000.0);

Logger.e(TAG, "字節(jié)變化:"+ (total - tempTotal));

//計(jì)算傳輸速率,字節(jié)轉(zhuǎn)Kb,毫秒轉(zhuǎn)秒 17:45:07

speed = ((total - tempTotal) / 1024.0/ (time / 1000.0));

//預(yù)估的剩余完成時(shí)間

remainingTime = ( long) ((fileSize - total) / 1024.0/ speed);

Logger.e(TAG, "傳輸速率:"+ speed);

Logger.e(TAG, "預(yù)估的剩余完成時(shí)間:"+ remainingTime);

//緩存-當(dāng)次更新進(jìn)度時(shí)已傳輸?shù)目傋止?jié)數(shù)

tempTotal = total;

//緩存-當(dāng)次更新進(jìn)度時(shí)的時(shí)間

tempTime = System.currentTimeMillis();

if(progressChangListener != null) {

progressChangListener.onProgressChanged(fileTransfer, progress, speed, remainingTime);

}

}

}

progressChangListener.onProgressChanged(fileTransfer, 100, 0, 0);

serverSocket.close();

inputStream.close();

objectInputStream.close();

fileOutputStream.close();

serverSocket = null;

inputStream = null;

objectInputStream = null;

fileOutputStream = null;

Log.e(TAG, "文件接收成功");

} catch(Exception e) {

Log.e(TAG, "文件接收 Exception: "+ e.getMessage());

} finally{

clean();

if(progressChangListener != null) {

FileTransfer fileTransfer = newFileTransfer();

if(file != null&& file.exists()) {

String md5 = Md5Util.getMd5(file);

fileTransfer.setFilePath(file.getPath());

fileTransfer.setFileLength(file.length());

fileTransfer.setMd5(md5);

Log.e(TAG, "文件的MD5碼是:"+ md5);

}

progressChangListener.onTransferFinished(fileTransfer);

}

//再次啟動(dòng)服務(wù),等待客戶端下次連接

startService( newIntent( this, FileReceiverService.class));

}

}

因?yàn)榭蛻舳丝赡軙?huì)多次發(fā)起連接請(qǐng)求,所以當(dāng)此處文件傳輸完成后(不管成功或失?。?,都需要重新 startService ,讓服務(wù)再次堵塞等待客戶端的連接請(qǐng)求

為了讓界面能夠?qū)崟r(shí)獲取到文件的傳輸狀態(tài),所以此處除了需要啟動(dòng)Service外,界面還需要綁定Service,所以需要用到一個(gè)更新文件傳輸狀態(tài)的接口

publicinterfaceOnProgressChangListener{

/**

* 當(dāng)傳輸進(jìn)度發(fā)生變化時(shí)回調(diào)

*

* @paramfileTransfer 文件發(fā)送方傳來(lái)的文件模型

* @paramprogress 文件傳輸進(jìn)度

* @paramspeed 文件傳輸速率

* @paramremainingTime 預(yù)估的剩余完成時(shí)間

*/

voidonProgressChanged(FileTransfer fileTransfer, intprogress, doublespeed, longremainingTime);

//當(dāng)傳輸結(jié)束時(shí)

voidonTransferFinished(FileTransfer fileTransfer);

}

在界面層刷新UI

privateFileReceiverService.OnProgressChangListener progressChangListener = newFileReceiverService.OnProgressChangListener() {

privateFileTransfer originFileTransfer;

@Override

publicvoidonProgressChanged(finalFileTransfer fileTransfer, finalintprogress, finaldoublespeed, finallongremainingTime){

this.originFileTransfer = fileTransfer;

runOnUiThread( newRunnable() {

@Override

publicvoidrun(){

progressDialog.setTitle( "正在接收的文件: "+ newFile(fileTransfer.getFilePath()).getName());

progressDialog.setMessage( "原始文件的MD5碼是:"+ fileTransfer.getMd5()

+ "n"+ "傳輸速率:"+ ( int) speed + " Kb/s"

+ "n"+ "預(yù)估的剩余完成時(shí)間:"+ remainingTime + " 秒");

progressDialog.setProgress(progress);

progressDialog.setCancelable( false);

progressDialog.show();

}

});

}

@Override

publicvoidonTransferFinished(finalFileTransfer fileTransfer){

runOnUiThread( newRunnable() {

@Override

publicvoidrun(){

progressDialog.setTitle( "傳輸結(jié)束");

progressDialog.setMessage( "原始文件的MD5碼是:"+ originFileTransfer.getMd5()

+ "n"+ "本地文件的MD5碼是:"+ fileTransfer.getMd5()

+ "n"+ "文件位置:"+ fileTransfer.getFilePath());

progressDialog.setCancelable( true);

}

});

}

};

三、文件發(fā)送端

文件發(fā)送端作為客戶端存在,需要主動(dòng)連接文件接收端開(kāi)啟的Wifi熱點(diǎn)

/**

* 連接指定Wifi

*

* @paramcontext 上下文

* @paramssid SSID

* @parampassword 密碼

* @return是否連接成功

*/

publicstaticbooleanconnectWifi(Context context, String ssid, String password){

String connectedSsid = getConnectedSSID(context);

if(!TextUtils.isEmpty(connectedSsid) && connectedSsid.equals(ssid)) {

returntrue;

}

openWifi(context);

WifiConfiguration wifiConfiguration = isWifiExist(context, ssid);

if(wifiConfiguration == null) {

wifiConfiguration = createWifiConfiguration(ssid, password);

}

WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);

if(wifiManager == null) {

returnfalse;

}

intnetworkId = wifiManager.addNetwork(wifiConfiguration);

returnwifiManager.enableNetwork(networkId, true);

}

/**

* 開(kāi)啟Wifi

*

* @paramcontext 上下文

* @return是否成功

*/

publicstaticbooleanopenWifi(Context context){

WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);

returnwifiManager != null&& (wifiManager.isWifiEnabled() || wifiManager.setWifiEnabled( true));

}

/**

* 獲取當(dāng)前連接的Wifi的SSID

*

* @paramcontext 上下文

* @returnSSID

*/

publicstaticString getConnectedSSID(Context context){

WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);

WifiInfo wifiInfo = wifiManager == null? null: wifiManager.getConnectionInfo();

returnwifiInfo != null? wifiInfo.getSSID().replaceAll( """, "") : "";

}

連接到指定Wifi后,在選擇了要發(fā)送的文件后,就啟動(dòng)一個(gè)后臺(tái)線程去主動(dòng)請(qǐng)求連接服務(wù)器端,然后就是進(jìn)行實(shí)際的文件傳輸操作了

發(fā)起選取文件請(qǐng)求的方法

Intent intent = newIntent(Intent.ACTION_GET_CONTENT);

intent.setType( "*/*");

intent.addCategory(Intent.CATEGORY_OPENABLE);

startActivityForResult(intent, CODE_CHOOSE_FILE);

獲取選取的文件的實(shí)際路徑,并啟動(dòng) AsyncTask 去進(jìn)行文件傳輸操作

@Override

protectedvoid onActivityResult(int requestCode, int resultCode, Intent data) {

if(requestCode == CODE_CHOOSE_FILE && resultCode == RESULT_OK) {

Uri uri = data.getData();

if(uri != null) {

String path = getPath( this, uri);

if(path != null) {

File file = new File(path);

if(file.exists()) {

FileTransfer fileTransfer = new FileTransfer(file.getPath(), file.length());

Log.e(TAG, "待發(fā)送的文件:"+ fileTransfer);

new FileSenderTask( this, fileTransfer).execute(WifiLManager.getHotspotIpAddress( this));

}

}

}

}

}

privateString getPath(Context context, Uri uri) {

if( "content".equalsIgnoreCase(uri.getScheme())) {

Cursor cursor = context.getContentResolver().query(uri, new String[]{ "_data"}, null, null, null);

if(cursor != null) {

if(cursor.moveToFirst()) {

String data= cursor.getString(cursor.getColumnIndex( "_data"));

cursor.close();

returndata;

}

}

} elseif( "file".equalsIgnoreCase(uri.getScheme())) {

returnuri.getPath();

}

returnnull;

}

將服務(wù)器端的IP地址作為參數(shù)傳給 FileSenderTask ,在正式發(fā)送文件前,先發(fā)送包含文件信息的 FileTransfer ,并在發(fā)送文件的過(guò)程中實(shí)時(shí)更新文件傳輸狀態(tài)

/**

* 作者:chenZY

* 時(shí)間:2018/2/24 10:21

* 描述:

*/

publicclassFileSenderTaskextendsAsyncTask<String, Double, Boolean> {

privateProgressDialog progressDialog;

privateFileTransfer fileTransfer;

privatestaticfinalString TAG = "FileSenderTask";

publicFileSenderTask(Context context, FileTransfer fileTransfer){

this.fileTransfer = fileTransfer;

progressDialog = newProgressDialog(context);

progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

progressDialog.setCancelable( false);

progressDialog.setCanceledOnTouchOutside( false);

progressDialog.setTitle( "發(fā)送文件:"+ fileTransfer.getFilePath());

progressDialog.setMax( 100);

}

@Override

protectedvoidonPreExecute(){

progressDialog.show();

}

@Override

protectedBoolean doInBackground(String... strings){

Logger.e(TAG, "開(kāi)始計(jì)算文件的MD5碼");

fileTransfer.setMd5(Md5Util.getMd5( newFile(fileTransfer.getFilePath())));

Log.e(TAG, "計(jì)算結(jié)束,文件的MD5碼值是:"+ fileTransfer.getMd5());

Socket socket = null;

OutputStream outputStream = null;

ObjectOutputStream objectOutputStream = null;

InputStream inputStream = null;

try{

socket = newSocket();

socket.bind( null);

socket.connect(( newInetSocketAddress(strings[ 0], Constants.PORT)), 10000);

outputStream = socket.getOutputStream();

objectOutputStream = newObjectOutputStream(outputStream);

objectOutputStream.writeObject(fileTransfer);

inputStream = newFileInputStream( newFile(fileTransfer.getFilePath()));

bytebuf[] = newbyte[ 512];

intlen;

//文件大小

longfileSize = fileTransfer.getFileLength();

//當(dāng)前的傳輸進(jìn)度

doubleprogress;

//總的已傳輸字節(jié)數(shù)

longtotal = 0;

//緩存-當(dāng)次更新進(jìn)度時(shí)的時(shí)間

longtempTime = System.currentTimeMillis();

//緩存-當(dāng)次更新進(jìn)度時(shí)已傳輸?shù)目傋止?jié)數(shù)

longtempTotal = 0;

//傳輸速率(Kb/s)

doublespeed;

//預(yù)估的剩余完成時(shí)間(秒)

doubleremainingTime;

while((len = inputStream.read(buf)) != - 1) {

outputStream.write(buf, 0, len);

total += len;

longtime = System.currentTimeMillis() - tempTime;

//每一秒更新一次傳輸速率和傳輸進(jìn)度

if(time > 1000) {

//當(dāng)前的傳輸進(jìn)度

progress = total * 100/ fileSize;

Logger.e(TAG, "---------------------------");

Logger.e(TAG, "傳輸進(jìn)度: "+ progress);

Logger.e(TAG, "時(shí)間變化:"+ time / 1000.0);

Logger.e(TAG, "字節(jié)變化:"+ (total - tempTotal));

//計(jì)算傳輸速率,字節(jié)轉(zhuǎn)Kb,毫秒轉(zhuǎn)秒

speed = ((total - tempTotal) / 1024.0/ (time / 1000.0));

//預(yù)估的剩余完成時(shí)間

remainingTime = (fileSize - total) / 1024.0/ speed;

publishProgress(progress, speed, remainingTime);

Logger.e(TAG, "傳輸速率:"+ speed);

Logger.e(TAG, "預(yù)估的剩余完成時(shí)間:"+ remainingTime);

//緩存-當(dāng)次更新進(jìn)度時(shí)已傳輸?shù)目傋止?jié)數(shù)

tempTotal = total;

//緩存-當(dāng)次更新進(jìn)度時(shí)的時(shí)間

tempTime = System.currentTimeMillis();

}

}

outputStream.close();

objectOutputStream.close();

inputStream.close();

socket.close();

outputStream = null;

objectOutputStream = null;

inputStream = null;

socket = null;

Log.e(TAG, "文件發(fā)送成功");

returntrue;

} catch(Exception e) {

Log.e(TAG, "文件發(fā)送異常 Exception: "+ e.getMessage());

returnfalse;

} finally{

if(outputStream != null) {

try{

outputStream.close();

} catch(IOException e) {

e.printStackTrace();

}

}

if(objectOutputStream != null) {

try{

objectOutputStream.close();

} catch(IOException e) {

e.printStackTrace();

}

}

if(inputStream != null) {

try{

inputStream.close();

} catch(IOException e) {

e.printStackTrace();

}

}

if(socket != null) {

try{

socket.close();

} catch(Exception e) {

e.printStackTrace();

}

}

}

}

@Override

protectedvoidonProgressUpdate(Double... values){

progressDialog.setProgress(values[ 0].intValue());

progressDialog.setTitle( "傳輸速率:"+ values[ 1].intValue() + "Kb/s"+ "n"

+ "預(yù)計(jì)剩余完成時(shí)間:"+ values[ 2].longValue() + "秒");

}

@Override

protectedvoidonPostExecute(Boolean aBoolean){

progressDialog.cancel();

Log.e(TAG, "onPostExecute: "+ aBoolean);

}

}

四、校驗(yàn)文件完整性

文件的完整性主要是通過(guò)對(duì)比文件前后的MD5碼值來(lái)校驗(yàn)了,文件發(fā)送端在發(fā)送文件前,先計(jì)算得到文件的MD5碼,將值賦給 FileTransfer 模型傳給文件接收端,文件接收端在傳輸結(jié)束后,再次計(jì)算本地的文件MD5碼值,通過(guò)對(duì)比前后值是否相等,就可以判斷文件是否傳輸完整

MD5碼值通過(guò)如下方法計(jì)算得到

/**

* 作者:chenZY

* 時(shí)間:2018/2/24 9:46

* 描述:

*/

publicclassMd5Util{

publicstaticString getMd5(File file) {

InputStream inputStream = null;

byte[] buffer = newbyte[ 2048];

intnumRead;

MessageDigest md5;

try{

inputStream = newFileInputStream(file);

md5 = MessageDigest.getInstance( "MD5");

while((numRead = inputStream.read(buffer)) > 0) {

md5.update(buffer, 0, numRead);

}

inputStream.close();

inputStream = null;

returnmd5ToString(md5.digest());

} catch(Exception e) {

returnnull;

} finally{

if(inputStream != null) {

try{

inputStream.close();

} catch(IOException e) {

e.printStackTrace();

}

}

}

}

privatestaticString md5ToString(byte[] md5Bytes) {

StringBuilder hexValue = newStringBuilder();

for( byteb : md5Bytes) {

intval = (( int) b) & 0xff;

if(val < 16) {

hexValue.append( "0");

}

hexValue.append(Integer.toHexString(val));

}

returnhexValue.toString();

}

}

好了,Wifi 傳輸文件的主要思路就講到這里了,這里也分享下源代碼:https://github.com/leavesC/WifiFileTransfer

大家都在看

android高級(jí)架構(gòu)工程師相關(guān)推薦
  • 相關(guān)百科
  • 相關(guān)知識(shí)
  • 相關(guān)專(zhuān)欄

最新詞條

安徽省政采項(xiàng)目管理咨詢有限公司 數(shù)字景楓科技發(fā)展(南京)有限公司 懷化市人民政府電子政務(wù)管理辦公室 河北省高速公路京德臨時(shí)籌建處 中石化華東石油工程有限公司工程技術(shù)分公司 手持無(wú)線POS機(jī) 廣東合正采購(gòu)招標(biāo)有限公司 上海城建信息科技有限公司 甘肅鑫禾國(guó)際招標(biāo)有限公司 燒結(jié)金屬材料 齒輪計(jì)量泵 廣州采陽(yáng)招標(biāo)代理有限公司河源分公司 高鋁碳化硅磚 博洛尼智能科技(青島)有限公司 燒結(jié)剛玉磚 深圳市東海國(guó)際招標(biāo)有限公司 搭建香蕉育苗大棚 SF計(jì)量單位 福建省中億通招標(biāo)咨詢有限公司 泛海三江 威海鼠尾草 Excel 數(shù)據(jù)處理與分析應(yīng)用大全 廣東國(guó)咨招標(biāo)有限公司 甘肅中泰博瑞工程項(xiàng)目管理咨詢有限公司 拆邊機(jī) 山東創(chuàng)盈項(xiàng)目管理有限公司 當(dāng)代建筑大師 廣西北纜電纜有限公司 大山檳榔 上海地鐵維護(hù)保障有限公司通號(hào)分公司 舌花雛菊 甘肅中維國(guó)際招標(biāo)有限公司 華潤(rùn)燃?xì)猓ㄉ虾#┯邢薰? 湖北鑫宇陽(yáng)光工程咨詢有限公司 GB8163標(biāo)準(zhǔn)無(wú)縫鋼管 中國(guó)石油煉化工程建設(shè)項(xiàng)目部 韶關(guān)市優(yōu)采招標(biāo)代理有限公司 莎草目 電梯平層準(zhǔn)確度 建設(shè)部關(guān)于開(kāi)展城市規(guī)劃動(dòng)態(tài)監(jiān)測(cè)工作的通知 廣州利好來(lái)電氣有限公司 四川中澤盛世招標(biāo)代理有限公司