android高級架構(gòu)工程師基本信息

中文名 android高級架構(gòu)工程師 所屬分類 職業(yè)

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

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

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

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

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

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

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

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

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

戴爾

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

達(dá)創(chuàng)

13% 河北達(dá)創(chuàng)體育器材有限公司
工程師優(yōu)佳墻面漆 品種:內(nèi)墻涂料;類型:面漆;容量:25kg/桶 查看價格 查看價格

亞士漆

kg 13% 亞士漆(上海)有限公司
工程師優(yōu)佳墻面漆 品種:內(nèi)墻涂料;容量:25kg/桶;類型:面漆 查看價格 查看價格

亞士

kg 13% 亞士漆(上海)有限公司太原辦事處
工程師優(yōu)佳墻面漆 品種:內(nèi)墻涂料;容量:25kg/桶;類型:面漆 查看價格 查看價格

亞士

kg 13% 亞士漆(上海)有限公司長春辦事處
工程師優(yōu)佳墻面漆 品種:內(nèi)墻涂料;容量:25kg/桶;類型:面漆 查看價格 查看價格

亞士

kg 13% 亞士漆(上海)有限公司長沙辦事處
工程師優(yōu)佳墻面漆 品種:內(nèi)墻涂料;容量:25kg/桶;類型:面漆 查看價格 查看價格

亞士

kg 13% 亞士漆(上海)有限公司西安辦事處
工程師優(yōu)佳墻面漆 品種:內(nèi)墻涂料;容量:25kg/桶;類型:面漆 查看價格 查看價格

亞士

kg 13% 亞士漆(上海)有限公司沈陽分公司
材料名稱 規(guī)格/型號 除稅
信息價
含稅
信息價
行情 品牌 單位 稅率 地區(qū)/時間
工程駁船 100T以內(nèi) 查看價格 查看價格

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

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

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

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

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

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

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

臺班 清遠(yuǎn)市英德市2013年1季度信息價
材料名稱 規(guī)格/需求量 報價數(shù) 最新報價
(元)
供應(yīng)商 報價地區(qū) 最新報價時間
工程師 工程師站|1套 3 查看價格 河南德燦電子科技有限公司 全國   2022-09-30
工程師 1.名稱:工程師2.規(guī)格、型號:工控型,配置29"液晶顯示器,包含打印機(jī) 含正版操作系統(tǒng)及應(yīng)用軟件3.其他:滿足設(shè)計、相關(guān)圖集、標(biāo)準(zhǔn)及招標(biāo)技術(shù)要求|1套 3 查看價格 北京勤瑞恒科技有限公司 全國   2021-07-29
工程師 1.名稱:工程師2.規(guī)格、型號:工控型,配置29"液晶顯示器,包含打印機(jī) 含正版操作系統(tǒng)及應(yīng)用軟件3.其他:滿足設(shè)計、相關(guān)圖集、標(biāo)準(zhǔn)及招標(biāo)技術(shù)要求|1套 3 查看價格 北京勤瑞恒科技有限公司 全國   2021-07-02
工程師 i5 3 2G 工控機(jī) 液晶22 i5 3 2G|1套 3 查看價格 北京勤瑞恒科技有限公司 四川   2022-09-27
工程師 i7,3.10GHz及以上;內(nèi)存:4GB ;硬盤1T|1臺 1 查看價格 廣州思源網(wǎng)絡(luò)科技有限公司 廣東   2018-06-15
工程師 CPU: core i7 , 內(nèi)存: 8GB,硬盤:4T,DVD-RW,100/1000M 網(wǎng)卡,22"液晶,1280x1024,顯卡 64M,光電鼠標(biāo),功能鍵盤|1套 3 查看價格 四川金川晟電氣成套設(shè)備有限公司 全國   2022-07-25
工程師 主流I7處理器 內(nèi)存8G 專業(yè)顯卡2G顯存 硬盤1TB|1套 2 查看價格 廣州市熹尚科技設(shè)備有限公司 湖南   2021-10-12
工程師 筆記本電腦,CPU 不低于 2GHz 2核64位,內(nèi)存不低于4G,硬盤不低于500GB(windows8 專業(yè)版),安裝RSlogix5000軟件、RSLinx Classic通信軟件、冗余模塊配置工具(RMCT)|1臺 1 查看價格 廣州賽瑞電子有限公司 廣東   2021-05-11

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

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

android高級架構(gòu)工程師常見問題

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

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

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

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

  • 高級職稱高級工程師

    高級工程師職稱掃盲總結(jié)銳之旗2021-06-09工程類高級職稱是中國專業(yè)技術(shù)職稱中的高級職稱(職稱改革后稱為專業(yè)技術(shù)職稱任職資格)。作為工程領(lǐng)域的技術(shù)專家或技術(shù)能手,高級工程師在企業(yè)中發(fā)揮著不可替代的...

教育培訓(xùn):

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

工作經(jīng)驗:

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

android高級架構(gòu)工程師文獻(xiàn)

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

格式:pdf

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

頁數(shù): 6頁

評分: 4.4

高級工程師名單 高級工程師名單

格式:pdf

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

頁數(shù): 38頁

評分: 4.4

工程系列南寧市高級工程師評委會評審?fù)ㄟ^人員名單: 624人 晉升: 615人 安徽阜陽滬千人造板制造有限公司 : 何劍鋒 百色建筑安裝工程總公司 : 蔣 紅 賴國忠 百色市第一建筑公司 : 戴紹軍 賓陽縣黎塘鎮(zhèn)規(guī)劃建設(shè)管理站 : 王玉甫 崇左市公路管理處 : 林松華 陳 光 崇左市墻體材料改革辦公室 : 黃海香 達(dá)華工程管理(集團(tuán))有限公司北海分公司 : 劉 卓 蒂森電梯有限公司南寧分公司 : 陳桂明 扶綏縣農(nóng)田水利工作管理站 : 黃 靖 扶綏縣自來水廠 : 陳裕斌 廣東省冶金建筑設(shè)計研究院廣西分院 : 任永源 謝麥林 陸軍青 黃萬才 劉德武 廣西安廈建筑工程有限責(zé)任公司 : 黎 明 廣西百宏房地產(chǎn)開發(fā)有限公司 : 劉繼文 廣西百捷電氣有限公司 : 周基敏 陳 瑱 廣西百納工程咨詢有限公司 : 黃 勇 王嘉寧 陸建軍 陳 明 陳繼達(dá) 韋佳添 劉慧明 羅金琴 劉中想 韋昌儀 廣西百源供電設(shè)計

立即下載

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

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

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

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

1. 什么是Kotlin?

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

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

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

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

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

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

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

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

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

它是高度互操作性的:你可以繼續(xù)使用所有的你用Java寫的代碼和庫,因為兩個語言之間的互操作性是完美的。甚至可以在一個項目中使用Kotlin和Java兩種語言混合編程。

2. 我們通過Kotlin得到什么

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

易表現(xiàn)

通過Kotlin,可以更容易地避免模版代碼因為大部分的典型情況都在語言中默認(rèn)覆蓋實現(xiàn)了。舉個例子,在Java中,如果我們要典型的數(shù)據(jù)類,我們需要去編寫(至少生成)這些代碼:

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,我們只需要通過數(shù)據(jù)類:

data class Artist(

varid: Long,

varname: String,

varurl: String,

varmbid: String)

這個數(shù)據(jù)類,它會自動生成所有屬性和它們的訪問器,以及一些有用的方法,比如,toString()

空安全

當(dāng)我們使用Java開發(fā)的時候,我們的代碼大多是防御性的。如果我們不想遇到NullPointerException,我們就需要在使用它之前不停地去判斷它是否為null。Kotlin,如很多現(xiàn)代的語言,是空安全的,因為我們需要通過一個安全調(diào)用操作符(寫做?)來明確地指定一個對象是否能為空。

我們可以像這樣去寫:

// 這里不能通過編譯. Artist 不能是null

varnotNullArtist: Artist = null

// Artist 可以是 null

varartist: Artist? = null

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

artist. print()

// 只要在artist != null時才會打印

artist?. print()

// 智能轉(zhuǎn)換. 如果我們在之前進(jìn)行了空檢查,則不需要使用安全調(diào)用操作符調(diào)用

if(artist != null) {

artist. print()

}

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

artist!!. print()

// 使用Elvis操作符來給定一個在是null的情況下的替代值

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

擴(kuò)展方法

我們可以給任何類添加函數(shù)。它比那些我們項目中典型的工具類更加具有可讀性。舉個例子,我們可以給fragment增加一個顯示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)ヂ暶饕粋€點擊所觸發(fā)的事件,可以只需要定義我們需要做些什么,而不是不得不去實現(xiàn)一個內(nèi)部類?我們確實可以這么做,這個(或者其它更多我們感興趣的事件)我們需要感謝lambda:

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

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

距離活動開始還有一天,重慶的開發(fā)者們趕快報名行動起來吧!

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

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

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

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

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

Wifi P2P 技術(shù)并不會訪問網(wǎng)絡(luò),但由于會使用到 Java socket,所以需要申請網(wǎng)絡(luò)權(quán)限。此外,由于是要實現(xiàn)文件互傳,所以也需要申請SD卡讀寫權(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"/>二、注冊廣播

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

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

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

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

當(dāng)收到 WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION廣播時,可以判斷當(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è)備列表發(fā)生了變化,可以通過 requestPeers方法得到可用的設(shè)備列表,之后就可以選擇當(dāng)中的某一個設(shè)備進(jìn)行連接操作

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

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

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è)備已斷開連接");}

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

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

(WifiP2pDevice) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)

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

publicinterfaceDirectActionListenerextendsWifiP2pManager.ChannelListener{

voidwifiP2pEnabled(booleanenabled);

voidonConnectionInfoAvailable(WifiP2pInfo wifiP2pInfo);

voidonDisconnection();

voidonSelfDeviceAvailable(WifiP2pDevice wifiP2pDevice);

voidonPeersAvailable(Collection<WifiP2pDevice> wifiP2pDeviceList);}

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

/** * 作者:chenZY * 時間: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; }

// 對等節(jié)點列表發(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è)備已斷開連接"); }

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連接到了一起,此時系統(tǒng)會自動創(chuàng)建一個群組(Group)并隨機(jī)指定一臺設(shè)備為群主(GroupOwner)。此時,對于兩臺設(shè)備來說,群主的IP地址是可知的(系統(tǒng)回調(diào)函數(shù)中有提供),但客戶端的IP地址需要再來通過其他方法來主動獲取。例如,可以在設(shè)備連接成功后,客戶端主動發(fā)起對服務(wù)器端的Socket連接請求,服務(wù)器端在指定端口監(jiān)聽客戶端的連接請求,當(dāng)連接成功后,服務(wù)器端就可以獲取到客戶端的IP地址了

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

因此,服務(wù)器端要主動創(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 在后臺監(jiān)聽客戶端的Socket連接請求,并通過輸入輸出流來傳輸文件。此處的代碼比較簡單,就只是在指定端口一直堵塞監(jiān)聽客戶端的連接請求,獲取待傳輸?shù)奈募畔⒛P?FileTransfer ,之后就進(jìn)行實際的數(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();

//將文件存儲至指定位置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); }

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

因為客戶端可能會多次發(fā)起連接請求,所以當(dāng)此處文件傳輸完成后(不管成功或失?。?,都需要重新 startService ,讓服務(wù)再次堵塞等待客戶端的連接請求

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

publicclassFileTransferimplementsSerializable{

//文件路徑privateString filePath;

//文件大小privatelongfileLength;

//MD5碼privateString md5; ···}

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

publicinterfaceOnProgressChangListener{

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

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

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

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ā)起文件傳輸請求

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

首先,需要先注冊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()); }

通過 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)就會觸發(fā) WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION廣播,此時就可以調(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(); }

之后,通過點擊事件選中群主(服務(wù)器端)設(shè)備,通過 connect方法請求與之進(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(); } }); }}

此處依然無法通過函數(shù)函數(shù)來判斷連接結(jié)果,需要依靠系統(tǒng)發(fā)出的 WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION方法來獲取到連接結(jié)果,在此處可以通過 requestConnectionInfo獲取到組連接信息,信息最后通過 onConnectionInfoAvailable方法傳遞出來,在此可以判斷當(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)通過 Wifi P2P 連接在了一起,客戶端也獲取到了服務(wù)器端的IP地址,在選取好待發(fā)送的文件后就可以主動發(fā)起對服務(wù)器端的連接請求了

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

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

獲取選取的文件的實際路徑

@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)來,在正式發(fā)送文件前,先發(fā)送包含文件信息(文件名,文件大小,文件MD5碼)的信息模型 FileTransfer ,并在發(fā)送文件的過程中同時更新進(jìn)度

/** * 作者:葉應(yīng)是葉 * 時間: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); }} 五、校驗文件完整性

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

/** * 作者:葉應(yīng)是葉 * 時間: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(); }}

因為客戶端會將 FileTransfer 傳給服務(wù)器端,所以服務(wù)器端在文件傳輸結(jié)束后,可以重新計算文件的MD5碼值,進(jìn)行對比以判斷文件是否完整。

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

大家都在看

距離活動開始還有兩天,深圳的開發(fā)者們趕快報名行動起來吧!

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

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

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

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

開啟Ap熱點接收文件

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

文件傳輸完成后校驗文件完整性

開發(fā)步驟分為以下幾點:

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

本應(yīng)用并不會消耗移動數(shù)據(jù),但由于要使用到 Wifi 以及 Java Socket,所以需要申請網(wǎng)絡(luò)相關(guān)的權(quán)限。此外,由于是要實現(xiàn)文件互傳,所以也需要申請SD卡讀寫權(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ù)器存在,需要主動開啟Ap熱點供文件發(fā)送端連接,此處開啟Ap熱點的方法是通過反射來實現(xiàn),這種方法雖然方便,但并不保證在所有系統(tǒng)上都能成功,比如我在 7.1.2 版本系統(tǒng)上就開啟不了,最好還是引導(dǎo)用戶去主動開啟

/**

* 開啟便攜熱點

*

* @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)閉便攜熱點

*

* @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();

}

}

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

publicclassFileTransferimplementsSerializable{

//文件路徑

privateString filePath;

//文件大小

privatelongfileLength;

//MD5碼

privateString md5;

···

}

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

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

@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();

//將文件存儲至指定位置

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)度時的時間

longtempTime = System.currentTimeMillis();

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

longtempTotal = 0;

//傳輸速率(Kb/s)

doublespeed = 0;

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

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, "時間變化:"+ time / 1000.0);

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

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

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

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

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

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

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

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

tempTotal = total;

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

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);

}

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

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

}

}

因為客戶端可能會多次發(fā)起連接請求,所以當(dāng)此處文件傳輸完成后(不管成功或失敗),都需要重新 startService ,讓服務(wù)再次堵塞等待客戶端的連接請求

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

publicinterfaceOnProgressChangListener{

/**

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

*

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

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

* @paramspeed 文件傳輸速率

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

*/

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

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

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ù)估的剩余完成時間:"+ 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ā)送端作為客戶端存在,需要主動連接文件接收端開啟的Wifi熱點

/**

* 連接指定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);

}

/**

* 開啟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ā)送的文件后,就啟動一個后臺線程去主動請求連接服務(wù)器端,然后就是進(jìn)行實際的文件傳輸操作了

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

Intent intent = newIntent(Intent.ACTION_GET_CONTENT);

intent.setType( "*/*");

intent.addCategory(Intent.CATEGORY_OPENABLE);

startActivityForResult(intent, CODE_CHOOSE_FILE);

獲取選取的文件的實際路徑,并啟動 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ā)送文件的過程中實時更新文件傳輸狀態(tài)

/**

* 作者:chenZY

* 時間: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, "開始計算文件的MD5碼");

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

Log.e(TAG, "計算結(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)度時的時間

longtempTime = System.currentTimeMillis();

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

longtempTotal = 0;

//傳輸速率(Kb/s)

doublespeed;

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

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, "時間變化:"+ time / 1000.0);

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

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

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

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

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

publishProgress(progress, speed, remainingTime);

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

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

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

tempTotal = total;

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

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ù)計剩余完成時間:"+ values[ 2].longValue() + "秒");

}

@Override

protectedvoidonPostExecute(Boolean aBoolean){

progressDialog.cancel();

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

}

}

四、校驗文件完整性

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

MD5碼值通過如下方法計算得到

/**

* 作者:chenZY

* 時間: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高級架構(gòu)工程師相關(guān)推薦
  • 相關(guān)百科
  • 相關(guān)知識
  • 相關(guān)專欄

最新詞條

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