2015年8月6日 星期四

關於Thread多線程

過去幾個禮拜在做「在Google Map上記錄使用者行走路徑」的功能...

(基於業務需求,不能附上詳細的程式碼。)

製作的過程中碰到最重要的幾個問題:

1.GPS的精準度和效率。
2.如何在行走過程中,開啟和關閉地圖,(地圖只是記錄行走路徑的附屬功能,可以獨立開啟跟關閉,)但又不會影響紀錄的效率和整體操作的流暢性。
3.紀錄行走的路徑資料量吃光了記憶體。

關於1,結果有點無解。

關於3,用即時的SQL讀寫來解決。

關於2,解決的過程中刷新了我對Thread的應用和設計心得.......




Thread的意義在於「不要讓單一項工作/任務吃光所有CPU的運算資源」,所以它會建議設計師在迴圈或程式碼中埋下Thread.sleep,來讓目前的工作/任務先暫時停止工作、把結果保留下來,然後處理其他工作。



我個人接觸Thread的初體驗是遊戲設計中的「畫面更新」功能。

畫面更新速率要穩定!(不穩定可以視為Thread設計失敗!)

但這是「畫面更新」的要點,而不是「Thread設計」的基本技巧,導致我一直以來在Thread的設計上,都會將Thread設計得過於僵硬強勢!──Thread結構都類似「畫面更新」的「無止盡的執行下去、直到程式關閉為止。」(這樣的Thread執行或許很穩定,但卻很難關掉、很難即刻反映APP的生命週期或開關。)

我想這是因為遊戲中都會將要運算和更新的資料量控制在一個穩定的範圍內,例如敵人數量要穩定、圖層數量要穩定。

所以不管是遊戲數值的運算、或畫面的更新,這兩個Thread的運算效率通常都會很穩定。(偶爾會有「爆擊」、「連續技」、「超大範圍攻擊」等事件發生,.......能否處理好就看工程師的功力了。)



但這次在更新地圖路徑的繪圖需求上,卻是完全彈性的!

資料量從一開始可能十筆二十筆,在五分鐘後會爆增到一百筆兩百筆,再十分鐘後又會增加到八百筆九百筆。

資料的量非常不穩定,所以「遊戲軟體」中「畫面更新」的Thread設計原則必然不適用!

將「數據運算」和「畫面更新」做成兩個完全平行、且穩定持續運作的Thread並不適合在這個運算資料量一直增加的設計中。



所以兩個Thread要從平行改為主從式設計。

一個Thread為穩定持續運作,另一個則被前面的Thread動態的生成啟動或關閉,...但在我這次的工作中,只有啟動、不需要關閉。(「畫面更新」Thread不需要循環執行,單一的功能執行完、則整個Thread關閉丟入記憶體回收列中.....,下次要更新畫面時,重啟一個Thread。)

這還是需要一些參數上的設計,例如「畫面更新開始」和「畫面更新結束」,畫面更新如果開始,就主Thread就不會再發出畫面更新的Thread,直到畫面更新結束為止。

(其實這是可以同一個參數解決的。)



這樣設計後,畫面的更新效率或許不太理想,時不時「使用者當下位置」跟「路徑線」會有點差距.......

但APP的整體操作完全不受影響。

2015年7月19日 星期日

正規的使用Handler更新畫面並且避免Handler Leaking發生

【可以先參考官方教學與範例。

真正讓我想出不用Handler、而用BroadCast的方式更新畫面的原因...在於Handler很難用!



產生Handler的程式碼如果不是直接在Activity類別內,這個Handler最後不被執行的可能性...八成!

最發生的現象就是...在一個獨立在Activity外寫成的Thread/Runnable類別內,有一段生成/宣告Handler的程式碼,不管這段程式碼是宣告一個匿名物件、或確實寫一個class,也不管它寫在run()內或run()外,.......經驗來判斷是:「這個Handler一律都不會被執行。」。

Android開發者稱這個現象叫做「Handler Leaking」。

在Log訊息中可以看到系統發出關於這個現象的警告,意思大概是說「這個Handler不是在Activity內產生的......」

這個「直接在Activity類別內」...意思是說連Fragment和View本身也涵蓋在其中!

其他可能會發生的現象還有...在Activity中寫了個匿名物件,內有產生Handler的程式碼,但這段程式碼卻不是在Activity內被執行的!

(我個人模糊的形容方式是:要滿足以下兩個條件才「絕對」不會發生HandlerLeaking:

1.類別在Activity中設置為內類別/巢狀類別。2.在Activity中宣告產生實體物件,包含匿名物件。

門檻其實挺高。)

雖然官方的教學範本裡面有說到要在Handler的建構子中傳入Looper.getMainLooper(),但這個方法也不管用!(沒有成功過!)



所以要用自己的變化與創意補足這塊了!

網路上看到過很多人使用Activity內增加Inner Class擴充Handler的方式來處理這個狀況,但我不採用。

因為這變成在Activity外使用Handler時,還要取得Activity當作參數傳入這個自設的Handler類別的建構子中。

現階段沒有辦法直接做到「隨意的產生Handler,然後又隨意決定Handler的功能,」只能做到最接近、最不花費力氣的方式...





這兩個介面/函數和Handler仍然需要在Activity中實作,但全部只有21行程式碼,而且只要做一次。


然後用上圖的方式,就可以在任何可以取道Context或Activity的地方產生帶有特定效果的Handler。




如果只是單純的要傳一個數值參數,用emptyMessage(int what)即可,為何還要有個Object參數?

仔細想想,Message類別中帶有一個Object參數讓使用者可以隨意傳入物件,真正的意義恐怕就在這裡。

2015年6月8日 星期一

ImageView或任何View無法將圖檔設為資源檔

最近在工作上遭遇到一個狀況,就是要在畫面上用「跟螢幕等寬」的方式顯示一張長度超過「七吋平板」可以顯示能力的圖片。

顯示圖片本身還是小事,問題在某些手機上這張圖片會消失......

ScrollView的長度有順利展開到相對的長度,所以不是資源檔匯入失敗的問題。



用ImageView設定Resource,或是用一般View、一般ViewGroup、LinearLayout、FrameLayout、RelativeLayout,都無法讓圖片順利顯示在螢幕上。

最後卻是用將這張圖片切割成三張的方式,才讓這張圖順利顯示。



資源檔匯入的時候,顯然會使用比較獨特的記憶體區塊,某些手機將這個區塊畫的比較小,所以當圖檔過大時,要不是破損、要不就是整張不匯入...

這次有寬高,但圖片無內容,應該就是種「破損」。

2015年2月6日 星期五

這些年在Android上,我看過的網路傳輸並接收資料的模組架構,還有Intent、SingleTon模組、和Adapter機制

【我不是要寫文教大家怎麼去POST/GET資料,也不是要自以為是的跟大家分享Socket經驗(雖然我很想......),我是想要分享我在設計Android APP時,設計「接收資料並呈獻到畫面上給使用者看」的過程中遭遇到的種種狀況。

這文對工程師意義可能不大,因為裡頭會談到一些Android架構的限制,工程師可能比較好奇怎麼凌駕這些限制,但我個人選擇和這些限制和平共處,──所以我的經驗和意見可能是不中看也不中用,但對非技術底的人來說可能比較有價值,例如設計或PM,因為在構思功能和流程時,不需要太多知識與經驗也知道要避開這些現象。】



Android的問題在於除了「Activity生命週期」的幾個階段被底層呼叫後,畫面一旦生成就不能隨便更動。要更動就必須要經由Handler、BroadCastReciever、或另起Activity。不然就必須要使用本來就可隨意更動的SurfaceView。

所以當畫面原本設計目的就是「不時要被網路資料更動」時,...注意!網路資料進出的時間點本來就不可能會等APP的「生命週期」,結果不是造成APP掛點,不然就是畫面跟實際接收到的資訊有落差。(前者是因為在生命周期外更動畫面,後者是因為生命周期外接收到的資訊不會反映在畫面上。)

要處理這個,我見到的兩大派作法:
1.大量使用Adapter,將資訊裝入Adapter中後進行簡單的「自動更新」。(給非技術底的:如果簡單描述Adapter,它就是個會針對資訊的內容快速產生畫面的工具,例如一百筆記錄了連絡資料的JSON。它有些限制,例如每筆資料間要最低限度的共通點、針對每筆資料產生的畫面通常也只能操做該筆資料...等。說它簡單,因為要啟動它的「自動更新」很簡單,而且「自動更新」的內容要多複雜就可以有多複雜,幾乎可以完全無視起動的時機點──因為系統會自動幫APP管理。)
2.在上一個Activity中使用一個Thread確保網路資料都已經接收完畢後,再啟動新的Activity、並直接使用剛接收到的網路資訊來決定畫面內容。(給非技術底的:這些字數完全無法描繪它實際上需要做的事情。)

第一種是Google在設計Android基本功能時常用的手法,不管是GMail、Google Play、或一些管理功能這類進行「系統控制」的「APP」。(對!一樣是給非技術底的:那些都是APP,Android底層是Linux,本來是只有文字的東西而已,凡是圖形介面、包含解鎖畫面都是種APP。)連FaceBook也是使用這樣的方式製作最新、最穩定的APP版本。(注意!這真的是最穩定,而且可能是最完美的APP型態。)

第二種在許多客製化打造的APP中經常使用。例如時不時接收網路傳來的JSON資料,然後將它分散到畫面上的數塊文字訊息欄中,或是變換背景顏色,甚至野心很大的更動它的佈局。



關於第一種,參考GMail、Google Play、還有FaceBook就可以知道這種設計方式的最大特徵。它們的畫面並不單調,程式不管是使用者體驗或功能都可以維持一定的穩定順暢。反觀第二種,就很考驗設計者和QC的耐心了!

以下是個範例客題:如果畫面被要求動態的、隨機的產生無數個欄位,每個欄位的內容功能都要隨網路資訊而有不同變化,而且網路資料的量、規則都無法預估.......

如果是第一種設計法,只要確保Adapter有呈現每種資料的能力,而網路接收資料正確、傳遞給Adapter的順序也如網路資料預估.......然後要求系統去自行「更新」即可。不管重新接收多少次資料、刪除單筆、插入多筆.........,都可以維持這個簡單的線性流程。(操作資料......操作一次、操作兩次、操作三次......操作N次,然後要求Adapter更新內容,一定可以精準的看到結果。)

如果是第二種設計法,就要先把畫面維持一個空白的大框架,然後接收完網路資料後開始數算數量,然後預估會需要在大框架中塞入幾個欄位,接著就開始逐一生成欄位。........

聽起來沒什麼、兩種差異不大,問題是用第一種方案中一行程式碼就可以做到的事情,為何要像第二種方案這樣寫數十、甚至數百行程式碼呢?

很多專案遲遲無法收尾,就是因為工程師被迫使用第二種。最後不管他們寫再多程式碼,都會發生「為何第一次新增條目時不會當機,刪除後新增就當機了,你的新增功能真的有做好嗎?」這類的Bug。因為第一種方法強制要求APP的畫面一律跟資料看齊,而第二種方式其實是寫很多修改畫面的程式碼,然後隨時判斷資料內容來決定要使用哪段程式碼修改畫面,只是這些方法即使相似處很多,但不是「永遠不夠用」,不然就是「會打架、會矛盾、會互扯後腿」。



另一個問題是我身為一個半路出家的技術狂最感興趣的現象。就是程式的「穩定壽命會縮短」。(以下所說其實已經是種理論性的天馬行空胡亂講...只是因為真的發生過,所以我寫下來。)

Activity是Android切割畫面與功能分界的一種機制,意義就有點像網路的「頁面」。

即使是同一個Activity之間,也無法直接交換資料,需要經過一些比較繁瑣的方式去設計它。

例如Intent,即使預設目的並不是要夾帶「畫面資料」,但很多人都會選擇這樣設計它。將下一個畫面的「標題」、「背景」...等資訊夾在Intent中,發送給下一個Activity。聰明的人、經驗夠的人都一定會經歷到Intent沒有忠實的把所有資訊傳給下個Activity的現象,例如前十筆資料都有帶到,後十筆資料就不見了。(這可能是因為Intent是用C/C++實做的,它並不是完美的物件導向定義出來的物件,即使它有嚴謹的建構子,但它其實會被轉換成非物件的型態在Activity和系統間傳遞,轉換回物件時,資料就消失在記憶體中了。──歡迎技術底更強的人吐嘲!)

所以老手應該都會使用Intent以外的方法傳遞資料。最常見的就是SingleTon。

所謂的SingleTon,就是在記憶體中畫置一塊區域,存放一個物件,然後這個物件中有存放資料的功能。因為這個物件嚴格說起來是「人人(任何Activity)都可以摸到裡面,所以只要知道這個物件存在的都可以往裡面塞資料、取資料」。

如果看過我其它文章的人就知道:使用SingleTon的人不見得很懂物件導向,他們只是覺得短短幾行程式碼可以有這個功用,就把它拿來用吧!

不管是SingleTon,或任何一種第二類資料傳輸法,理論上都會有個風險,就是記憶體會不夠用、記憶體管理會出錯。

不懂電腦原理的人一定會質疑「記憶體需求不都開好、規畫好,怎麼會不夠用?不夠用不能改良嗎?」

事實是:資料在記憶體中不會自己判斷自己是不是「已經不被需要了」,必須要程式要求它「請從記憶體中消失」,或是設計者關機斷電。一般來說,C/C++程式設計師都要自己管理這個動作,自己產生的、自己主動讀取的取用的資料,都要自己下指令、寫程式把它們清除;但Java/Android工程師不是如此,只要操作一種類似「索引值」的東西,系統內的某個特定程式就會定時按照索引值來清除記憶體內不要的資料。

不是開玩笑,真的有一支專門的系統程式負責這個工作。

但某些因素讓這支程式工作起來不太精準,經常會發生已經被排入要被清除的資料,清除程式竟然認為自己不可以清除它,但也不知道該拿它怎辦.........(或許是「索引值」管理程式出問題?或其他?)當這些「不知道該怎辦的資料」在記憶體中越堆越多,最終的後果就是程式效能越跑越慢、甚至當機。(所以重開機通常可以讓Android效能恢復,可見這個現象不是神話或笑話。)

可是「因為Android和Java的共通性」這種話講起來是很讓人心虛的。重點是:程式的動作越多、程式碼越多、管理調配的動作越多,就越容易產生這些「不知道該怎辦的資料」。譬如不用Adapter來產生動態畫面的APP在刷新畫面無數次後,最終的命運就是記憶體被那些「淘汰」的畫面元件吃光光.....(被丟掉的畫面變成「不知道該怎辦的資料」。)

使用第一種方法並不會保障「不知道該怎辦的資料」絕不會產生。

但是.......舉例來說,如果使用Intent傳遞資料,結果可能會造成資料傳遞完、Intent的一部份變成「不知道該怎辦的資料」,如果大量、頻繁使用Intent,結果就是造成APP從開始到「因為記憶體被這些資料吃光而無法正常運作」的周期縮短。

SingleTon也如此。

重點在於:第二類方法總是會造成「特定Activity需要的資料必須要在其他Activity中產生,並且使用額外物件管理或傳遞,處理跟接收本身也要產生額外的物件」,──附帶一提,所謂的物件也是種「資料」。這些物件越多,也就表示資料越多......

所以在單一Activity當下接收並管理資料,怎麼看都是王道。



寫這些,是因為公司某前輩離職前寫的程式開始被客戶抱怨「使用久了、操作頻繁了,就會當機」。

大老闆正在四處找更資深的工程師來解這個問題,我雖然知道解法、而且在別家公司也成功處理過類似的狀況,但.........先安靜.........

2014年12月9日 星期二

【Android】CheckBox的「帽子把戲」(如何自製「單選選項」)

這篇算是白癡教學。只要把基礎讀完的人應該都可以順利看完。

如果(讀者)要準備製作的是一張從頭到尾都手工打造、每個選項都有自己獨特的ID和對應資料的選單,這篇文章講的技巧沒什麼意義。

但這裡講的是「ListView這類用緩存記憶體大量複製出來的UI」,而且操作的是JSONArray這類的資料時,可能會需要的技巧(或說是心得會比較恰當)。

另外...如果不知道什麼是CheckBox的人也沒有必要繼續浪費時間看下去。



說明一下題目。

雖然Android就有預設「單選群組」給大家使用,但「萬一不夠用」,或「某些特性跟自己的需求相違背」時,該怎麼辦?

我沒辦法解釋或說明「那會是什麼樣的狀況」,那是碰到了就知道的狀況.......

一般來說CheckBox是用來製作「多選選項」、或「勾選式True/False」的元件。(有一種「滑動式切換True/False」的元件。)

如果每個CheckBox都是獨一無二的,則只要「輸出結果」時將全部的CheckBox都集合起來,用迴圈判讀它的Check狀態就好,但如果是用迴圈/緩存記憶體大量複製的,則無法這樣做,資料的操作必須要在按下「CheckBox」當下的瞬間進行修改。

再說明清楚一點:

假設功能要求這裡有個長整數(Integer)的矩陣A,每「格」矩陣對應內有二或多個以上CheckBox的CheckBox群組,CheckBox群組內的CheckBox依序對應「1」、「2」....以此類推的值,(CheckBox1被勾選時,某數值為1,CheckBox2被勾選時,某數值為2,.......某數值不可能同時又是1、又是2,所以CheckBox1和CheckBox2必須為單選項,)被選取的CheckBox則會把自己對應的數值寫入群組對應的矩陣格內。

如果矩陣A的長度永遠固定,那就表示CheckBox群組的數目也永遠固定,所以只要用固定格式的介面即可。最終要輸出答案時,只要把所有CheckBox群組用固定的順序判讀即可。

但如果矩陣A的長度會隨機變化,那狀況就很複雜了.........(先不提選項有多少種,光是題目數目不一定,這就是個「菜鳥的挑戰」了。)

以ListView為例,如果用全緩存的方式管理生成畫面,會發生「當這個選項滑出畫面外時,系統會把這個選項的介面圖形回收掉,」(不懂技術的人肯定完全不懂這是怎麼回事。意思就是說「這個欄位如果滑出畫面外,可能會被系統消除掉、以節省記憶體......,當它划回營幕時,系統再把它重新生出來。」)所以如果用「判讀介面上CheckBox選項狀態」的方式來決定答案值,會發生取不到CheckBox的狀況。

碰到這種案例時,就不能「讀取CheckBox的選項狀態」來決定數值,必須要在選項被圈選的當下就決定數值.......這就要使用介面「OnCheckedChangedListener」。

這只是狀況之一,隨機決定長度的資料難度完全不同。




首先,下面這算是個用匿名函數式去宣告的一個OnCheckedChangeListener。(怕有人沒用過,所以說明一下。CompoundButton buttonView是指設定了這個Listener的CheckBox,所以即使多個CheckBox設定同一個Listener,Listener也不會弄錯,──這是種設計函數的基本技巧。boolean isChecked則是前面這個buttonView的選取狀態,true表示被勾選,false則是無勾選、或勾選取消。)

OnCheckedChangeListener l1 = new OnCheckedChangeListener() {
  
  @Override
  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
   // TODO Auto-generated method stub
   
  }
 };
請不要用這個方式製作接下來的OnCheckedChangeListener!

請正規的實作一個類別後再宣告。


結果大概如下!這個類別預設的是操作JSONObject。
class CheckListenr implements OnCheckedChangeListener{
  ...//省略建構子
  
  JSONObject js;
  CompoundButton lastView = null;

  @Override
  public void onCheckedChanged(CompoundButton buttonView,
    boolean isChecked) {
   // TODO Auto-generated method stub
                        //!!!!0#  將這個CheckBox對應的值取出
   int tag = (Integer) buttonView.getTag();
   if(!isChecked){//!!!!A#
    ....
    ....
                                //!!!!3#  
                                lastView = null;
   }
   else{
    if(lastView != null){
                                        //!!!!2#  將上一次選取的CheckBox取消勾選
     lastView.setOnCheckedChangeListener(null);
     lastView.setChecked(false);
     lastView.setOnCheckedChangeListener(this);
                                        //!!!!2#End
    }
                                //!!!!1#  將這次勾選的選項設為「上一次勾選的項目」
    lastView = buttonView;
    ...
                                ...
   }
  }
  
 }

假設眼前有三個CheckBox,分別是CheckA、CheckB、CheckC,則將它們對應的數值設為Tag,譬如CheckA為A、CheckB為B、CheckC為C,──可以看到「!!!!0#」下面對應的那行。(我這裡的做法是用isChecked為true時填入這個值,如果為false則填入統一的「X」。各位也可以選擇用設定兩個Tag,isChecked為true時取Tag1,false則取Tag2。這是很基本的變化技巧.......不是講給熟手聽得。)

「!!!!1#」、「!!!!2#」和「!!!!3#」就是將這些CheckBox做成單選項的關鍵。

「!!!!A#」的地方將整個邏輯切割開來,分為「勾選」和「取消勾選」......不難懂(吧)。

如果是第一次勾選這個群組(提醒:群組是指都設定了這個Listener、並且被指定要修改同一個數值的CheckBox)、或群組內的勾選被取消的狀況時,「!!!!2#」區段的程式碼並不會被執行,有CheckBox被勾選後、使用者又決定改勾選別的CheckBox時,「!!!!2#」就會被呼叫。

這地方是我稱這個技巧為「帽子把戲」的原因。注意看我將「上一次勾選的CheckBox取消勾選(設定為false)」前後,個做了一次「將Listener設為Null」和「將這個Listener設定給CheckBox」。

這有可能造成無窮迴圈,或至少會造成「無法將選項正確的反勾選」。因為如果CheckBox有設定CheckChangedListener時改變它的勾選狀態,則這個Listener會被呼叫執行,然後「取消勾選後」,也就是「!!!!A#」這個地方如果為false的區段會被執行,包含「!!!!3#」。

一般情況下這其實不會有問題!可是......我無法精確說明何時會是問題,因為「取消勾選(群組內的任何一個選項)」跟「被觸發設為false」兩者是不一樣的!(簡單來說,如果「取消勾選」時對應的動作如果在「被觸發設為false」時不可以被執行會出錯時,這就是我這裡再講的狀況。)

所以就假設「萬一出問題好了」,單純的將「上一次勾選的CheckBox」設為「false」會導致邏輯判斷出錯,解法非常簡單,再執行「被觸發設為false」前,將Listener解除就好。

2014年9月30日 星期二

【Java】for(;;)和for(:)的效能差異

【我測效能的方式一向是土法煉鋼。】



宣告一個長度在三千以上的矩陣,然後用兩種不同的for迴圈來讀取、操作內容,效能會差距多少?

注意!矩陣長度一定至少要在三千以上,一萬是比較保險的。(對矩陣個別元素的操作或運算內容也別太複雜。)

注意!還可以在for(:)的迴圈內容加個長整數,然後讓長整數一起跟著遞增,讓兩個迴圈的差異變得比較小。

答案是........




一千倍!

for(:)迴圈的速度是for(;;)迴圈的一千倍以上!




但了解技術內容的人都知道for(:)迴圈其實是將矩陣轉換成Iterator,然後用這個物件操作內容。

而且這個內容只是矩陣元素取值出來操作而已!它只是宣告一個變數,然後這個變數的值會是當下正在操作的矩陣元素的值。不論對這個變數做怎樣的操作,矩陣元素的值都不會有變化。

理論上來說,for(:)反而應該要比較慢才對,因為多了個轉換手續。

所以矩陣的長度要夠長,才會看得出差異。如果太短,這個轉換手續造成的效能差異會讓for(:)迴圈看起來比較慢。




如果有在for(:)迴圈內加長整數遞增,效能依然是一千倍!

顯然。遞增運算絕對不是效能差異的關鍵。(這很理所當然。)兩個迴圈的差異,完全在於Iterator的效能。

但差在哪裡呢?操作記憶體的效率?........傳統的for(;;)迴圈操作矩陣可能沒有位址紀錄器,要重覆的讀取迴圈內設置的長整數,還要判斷長整數的執行範圍........不過這是我隨便猜想啦!

如果在換個方法來測試這兩個迴圈的差異,例如也在傳統的for(;;)迴圈中加上一個區域變數,然後用這個區域變數取值後再對這個區域變數做運算操作,效能會差多少呢?




根本沒差!

或許關鍵根本不再Iterator,而是「變化矩陣元素內容」就是會這麼慢。






一般來說長度會到這個等級的矩陣......都是Stream吐出來的基本型別資料串。(自己寫的物件搞到三千多個?...超級資料庫?)

2014年9月17日 星期三

【Android/Java】ListView中使用ArrayAdapter搭配自己設計的物件

一般官方範例只會說到如何使用ArrayAdapter呈現String資料串。

感覺上大家會以為「ArrayAdapter只是把程式中寫在泛型資料格式指定的資料物件塞到XML Layout中的TextView上」。

但其實ArrayAdapter呼叫的是資料物件的「toString()」這個函數。


也就是說:設計師只要把「自己設定的物件」的「toString()」重新複寫就可以丟進ArrayAdapter了!

但剩下的資料呢?自己設定的物件中一定還有其他資料要顯示在ListView中,否則就不需要自定義物件了......

問這個問題的人去跪算盤!

複寫getView(int position, View convertView, ViewParent parent)這個函式就好啦!

講精準一點來說,ArrayAdapter在產生實體(new)的時候,會需要指令XML Layout跟這個Layout中的一個TextView的id。(如果不指定會預設為android.R.id.text1。)複寫的getView中如果寫了super.getView,就不用另外執行這個toString動作。(記得把super.getView傳出的View設為回傳值...記得getView這個函數要設回傳值。)




這個方法可以用在幾乎所有會用到Adapter的地方。

而且資料動態變更(執行notify函數)時,絕對會成功,因為用的是ArrayAdapter的方法。(我實作BaseAdapter產生的物件幾乎沒有成功過。)



話說......Java物件預設的函式到底還有那些?又有哪些功能?......

複寫他們會有些什麼副作用嗎?一般來說「toString()」是回傳物件的class資訊,就這樣複寫掉會有什麼影響呢?.......

有機會再補充啦。