2014年6月28日 星期六

Stream、Port、Ping、Socket

不知道有沒有人試過在瀏覽器的網址輸入欄中輸入「file:///」後面接上某檔案的路徑,然後按下「Enter」,看看會發生什麼事?........

很多檔案其實都可以用這種方式開啟。



因為這就是Stream與Port的概念。



在電腦中,中央對外溝通是藉由Port。

主機板要跟網路溝通?用Port。

主機板要跟列表機溝通?用Port。

主機板要跟硬碟要資料?用Port。

幾乎每個Port都有特定的工作,(或只能執行特定的工作,或是先佔先贏,程式大多會衝突,很多時候就是因為搶Port、或是對Port的設定沒有彈性。)

打入網址,瀏覽器會從Port編號:8080去跟外界網路溝通;如果打入檔案路徑,可能也是從Port編號:8080,但也可能不是,(我不想探究!Java耶!細問這麼多?)

重點是對電腦的中央來說,這都只是資料藉由Stream傳入或傳出而已,數據機跟網路怎麼處理從中央傳入的資料?中央才不管!(或許可以想像哪天真的把網址傳給硬碟,會發生怎樣有趣的事情呢?硬碟會變成變型金剛?)



但真正的問題是:Stream本身只管檢查自己是否可以傳出或傳入資料,能否建立Stream,這完全不是Stream自己可以控管的。

所以有File類別來檢查「檔案路徑是否存在」,所以有各種硬體管理類別來檢查「特定硬體是否存在」。

Socket就是用來檢查「指定的連線IP是否存在、是否可以進行連線」的類別。

網路連線的第一步就是「檢查IP是否正常、正確、可以連線?我在CMD模式下打入[Ping]+[ IP],會收到怎樣的結果?」,但Stream本身都不具備這些功能,這就是我們需要Socket的原因。

所以要清楚意識到Socket並不是用來進行資料傳輸溝通的主體!Stream才是!

雖然沒有Socket就沒有Stream,沒有Stream、Socket只是好看的工具,但這兩者並沒有絕對的依存關係。(.........講是這樣講,但我也不知道怎麼樣不靠Socket建立網路連線。)



學習使用Java Socket,大部分的新手都是死在這個認知上。

能夠清楚、易懂傳達這個認知的文章.......對不起!沒看過。(我也是別人明明白白講給我聽、甚至試範給我看後,才搞懂這一切。)

三張圖,讓人看懂Java Socket真正的用法

Socket的結構、特性......不難查到相關資料跟說明,例如怎樣設定它的Port、怎樣跟特定IP建立聯結、怎樣檢查連線是否成功........

但是實際上會用Socket建立TCP/IP或UDP連線的人很少,要做網路功能,URL和J2EE幾乎把這塊領域吃光光........

怎麼回事?



因為Oracle官方的教學文件本身就是塊爛帳!

要看清楚這塊爛帳,除非老師上課有講清楚、教學文寫者有意識到,不然讀的人最後都會撞死在「為什麼連線建好了卻不能用」、「為什麼連線的效率差到根本無法運作」、「為什麼最後會徹底失去建立Socket和Stream的能力」......上。

把「網際網路通訊原理」丟一邊,Java設計師/工程師其實需要理解到的本來就只有「這樣就可以跟遠端裝置進行通訊」這個概念就好,(而且這不正是「Socket的精神」嗎?)但是Oracle的官方範例僅止於「一條龍式的示範可以丟入怎樣的參數來建立Socekt,建立之後可以優先使用哪些參數或得哪些回傳物件........」但是對於真正重要的Stream使用就幾乎支字未提。

而且我可以保證同樣都是Stream,在網路跟單純的檔案管理上頭,這兩種Sream的使用與操作經驗完全不同。(我現在會這樣說,十年後還是這樣說。「這是一樣的東西啊!」那完全是老鳥的嘴砲與傲慢。)

不管是Oracle,或是網路上、課堂上無數的範例與教學,根本沒想過要讓人搞懂「Java怎麼建立連線環境」的全貌,都是不停丟出片段的知識而已。

(不知道多少初入門的Java設計師死在這上頭,最後只能一把鼻涕一把眼淚的強迫自己接受J2EE的強暴、放棄自己滿腦子本來可以用J2SE實現的應用與設計。真是殺死人的絕境、與欲哭無淚的哀愁啊!)



第一步,建立Socket。
不管是用直接指定IP的方式,或用ServerSocket。
總之,想去「取得」一個Socket。 


 從第一步中獲得的Socket產生兩個InputStream和OutputStream。
可能是Thread中產生兩個全域變數Stream;
或是直接產生兩個Thread裡面帶有Stream變數。

將兩個Stream丟給不同的Thread(不是負責生成Socket的同一個Thread),來進行資料的讀寫操作。

這裡頭牽涉到一些細節,例如負責生成Socket的Thread要怎麼管理已經生成的Socket?

提示:絕對不是馬上關掉它。Stream一樣也不用馬上關閉,除非在測試中發現機器沒辦法同時負荷那麼多Stream(原理...待查中...)否則Stream做好,除非要結束連線......建議:就放著吧。(不然連線的效能拉不起來。)



為何要獨立一個Thread來管理Socket生成?

當機器要能夠具備跟多個對象溝通的能力時,Socket絕對不會只有一組,可能最後要建立多組,特別是使用ServerSocket時,但問題是ServerSocket的avaliable()指令會霸佔住整個Thread,所以對於已經建立好的Socket,如果要保有繼續操作它們的能力,就要用另外獨立的Thread來操作,另外Socket要有建立流程、自然也要有關閉流程,一個Thread中怎麼同時管理多組Socket、操作多組InputStream/OutputStream?(神人會說「有何難?不就幾百行程式碼而已!」........想當那種神人,我就不會開這個部落格、寫這些文章分享這些經驗了。)

領悟力好的人會理解這點,根本不需要我這三張圖,.......但問題是領悟力好的人有多少呢?況且說這些人領悟力好...這些人又有多少有能力自己從底層去寫自己的Socket物件來管理網路連線?........還不提我看到一家規模中上等級的公司開出「Java工程師,懂Socket使用,會管理TCP/IP連線......」的徵人啟事,薪水45K(起跳),結果過了一個月還沒人應徵.......

2014年6月26日 星期四

Android的背景Service連續啟動、或前景Activity連續啟動,對程式中的Thread(或其他東西)造成的影響

[2017/8/24]

現在發現其實Android有些更簡單的機制可以處理這種狀況...

ActivityLifecycleCallBacks...這東西的使用機制比較複雜。

或是LifecycleObserver...但這東西我沒實用過,我習慣使用LifecycleCallbacks



 [2017/8/24 !!]
==========================================================

寫一個Activity或Service,然後在onCreate()中加入Log訊息,會發現當「程式/APP」大到某個程度時,會經常(其實是變成例行公事)一個Activity或Service連續開啟(執行「onCreate()」)兩次。

雖然第一次開啟的Activity/Service最後都會關掉(都會立刻轉到「onDestroy()」),但是程式整體結構會變得很難掌控。

開啟不是開啟,關閉不是關閉,如果有別的程序──例如等一下會講到的執行緒──要去判斷/讀取這些Activity/Service內的參數時,可能就糗了......

再講明白一點:Thread啟動、將Activity/Service當作傳入引數要求Thread判斷裡頭的參數......結果不到一秒鐘的時間,Activity/Service已經被關閉,但Thread才剛完成「生成」和「啟動」而已,結果進入執行緒的任務中要開始工作時要去判讀Activity/Service的參數......當機!



有些人會認為應該要在執行緒中加入極為精密、精準的判斷邏輯,避免掉這樣的錯誤發生,「這才是程式設計。」

我不否認「程式設計本來就是門苦工」,產品使用者不曉得在智慧型手機上按一下螢幕、或在電腦上點一下滑鼠,這個「按」與「點」,本身就包含了無數行程式碼。(像寫這個的工程師們致敬。)

但這不代表我們應該只認可「苦工」,而放棄、或否定尋找其它秘訣的機會。

(而且:最苦工的方法想也知道會讓Thread效率變差。)


其實解決方法很簡單,(這部落格講的是Java/Android,所以這些經驗主要應用在Java上,)基本上已經摸過一定程度的Java工程師都知道在Thread中的迴圈要用一個外面可以控制的參數A來決定Thread是否要繼續執行。

這個參數A可能是個Boolean值,程式進入onCreate,則Boolean轉為True,程式執行onClose,則Boolean轉為False。

但僅僅是這樣還不夠解決問題。(像如果用Activity或Service本身的物件是否還存在、是否為Null來做判斷.......抱歉!沒成功過。)

可是真的要解決問題也非常簡單......

Thread有run()函數,這個函數內的程式碼可以被無限執行、並同時排程。

在run()的最開頭,只要用排程的方式先讓它「休息0.5秒」,等到「休息0.5」秒結束後,真正要值行任務內容時,參數A已經變成了False,這個Thread自然也會跳入關閉狀態、然後乖乖等著被回收,不會干染下一個Activity/Service啟動並產生真正穩定的Thread。



應該不會有人會問「0.5秒太短了,不管用,該怎麼辦?」........



================================================

這篇文講的東西很碎,但我以為Java本來就是這樣的東西。它的學習門檻很低,應用門檻也很低,要用它的人、可以用它的地方其實都很多。

但是它的門檻低,後面卻有一階比一階高的樓梯,沒有多種其他層面知識的人跳進來學Java,其實很快就會遭遇障礙。

把程式設計的基礎,例如迴圈和判斷式的使用、物件的設計等很基本的東西講的淺白易懂,然後接下來馬上轉為「基礎教完了,要開始把你們當成專業級的對待,」然後下一個教程、範例、或項目(隨便怎麼稱呼),馬上開始用「我只講這麼多,剩下的要自己理解自己推敲」的態度........所以Java程式設計師能夠活過「初階」的很少,特別是這種語言標榜著所有人都能學。

孔子講「舉一隅不以三隅反者,吾未往也。」這是舉一反三的典故。

聽起來很了不起、很霸氣,但如果今天的「教學」都用這種態度,那小學生學會九九乘法表的一到三之後,豈不是要自己歸納出剩下的四五六七八九的訣竅?

整體的知識水平要提升、科技要進步......這種態度(期望丟出去的教學能夠有最大話的回收)就有嚴重的問題。

所以我開這個部落格、分享這些很碎很碎的技巧。

除了因為還是會有人需要用到,也是為了讓那些沒辦法第一步就達到舉一反三的人可以達到有能力舉一反三。

Thread要更新畫面,除了使用Handler以外,還可以考慮BroadCastReceiver.......(內無程式碼,因為不需要)

有人注意到BroadCastReceiver也可以像Handler一般接受從主線程以外發出的「更新畫面」指令嗎?

註冊一個特定畫面專用的BroadCastReceiver,(或是你有別的辦法可以產生動態、適用所有你想要的畫面的Receiver,)然後改成使用sendBroadcast(Intent intent),來接收........

public abstract void onReceive (Context context, Intent intent)

在Receiver內的這個函式中的Context就可以獲得發出這個Intent的Activity。

也就是說,用Context(強制形變一下)來執行....findViewById(int id)

就可以輕鬆的更新任何想要更新的畫面了........

效率是不太好啦!(但也不會很糟。)



為何要想出這招,而不是滿足於使用Handler就好?因為用Handler更新畫面,在測試程式中看起來很完美,但實務上經常碰到的問題是:

變化比較頻繁的功能可能會發生「Handler」送出後,但是Activity卻已經關閉。

這時候APP絕對死當!




一般來說,大家都會把Handler寫成個別Activity下的子類別、巢狀類別,所以裡面的Activity都很「專一」,但也因為專一,所以大家可以寫個很獨特的偵測方式來決定Handler真的被主執行緒執行後,要不要真的執行畫面更新的動作......

多麻煩啊!(把Activity變成Handler的參數,然後每呼叫到一次就先執行、檢查一下「act != null」.......這樣的設計合理嗎?見人見智。)

改用BroadCastReceiver,只要解除註冊,就不用再煩惱任何問題,如果有問題就是「寫錯內容」,而不是「發送Handler的程序需要調整」。

而且在Android下,只要確認View元件的ID碼正確、內容也正確,其實BroadCastReceiver可以作的事情、可以更新的畫面幾乎是無限多。



(稱這為「小秘訣」...好像有點沒價值。到網路上找,好像沒什麼人想到這樣做。)

2014年6月18日 星期三

【Android】EditText && Editable && Selection

會研究到這兩個東西,是因為在作網路連線功能時,要讓使用者自行輸入IP來決定連線對象。

IPv4機制需要四組0~255的數字。(現在知道了,那其實是四個Byte。)所以需要四個EditText輸入框。

但問題是.......

當我在第一組數字輸入完後,我希望跳到下一組數字的輸入框......

容易!

EditText下的getText()可以獲得一個Editable物件。這個物件長的很像StringBuffer,雖然不是,但用法概念很像。



問題是如果我要用「刪除鍵(BackSpace←)」刪除文字,同時具有「跳回上一個EditText輸入框」的功能...結果也不難,但問題是跳過去後,游標會停留在框內的文字開頭。

例如框內如果有「100」,游標會出現在「1」之前,但我希望它直接出現在最後一個「0」的後面。

這個時候就可以用Selection來決定游標要停留在哪裡。



其餘的函式具體功能還沒機會嘗試研究,但主要使用的是setSelection(...)。

先將EditText.getText()獲得的Editable物件當成引數傳入。(Editable其實是Spannable的子類別。)接著決定「位置」,──如果傳入「頭」「尾」,則會把「頭」「尾」之間的文字「選」起來,功能就類似滑鼠的拖曳。



其實是很簡單的一件事,但很實用。

2014年6月15日 星期日

ArrayList.add()的兩種方式的效能差異

下面有兩個迴圈.......(limit2是1000,但其實可以隨便寫。)

for(int k = 0 ; k < limit2 ; k++){
  for(int l = 0 ; l < limit2 ; l++){
    Integer irt = new Integer(l);
    list1.add(irt);
  }
  list1.clear();
}

for(int k = 0 ; k < limit2 ; k++){
  for(int l = 0 ; l < limit2 ; l++){
    list1.add(new Integer(l));
  }
  list1.clear();
}
看出差異了嗎?

下面的效能大概比上面的效能快了兩成左右。

「宣告一塊記憶體位址」然後再「產生一塊實作」,佔去的效能要比「不宣告」多兩成左右。

(所以造成效能差異的關鍵並不在ArrayList本身。)



沒什麼意義。

因為我把迴圈擴大(外頭在加兩層迴圈,)總共執行了1000 * 1000 * 1000次的『「宣告一塊記憶體位址」然後再「產生一塊實作」』,和1000 * 1000 * 1000次的『「不宣告」直接「產生一塊實作」』,這樣才看的出有兩成左右的效能差異。

應該很少有人沒事會去在Java上操作這麼大的ArrayList吧!(即使「有事」也不會作,因為記憶體會掛掉。大家會想別的辦法達到同樣的效果。)