TShopping

 找回密碼
 註冊
搜索
查看: 892|回復: 2

[轉帖] 寫 android app 會遇到的事情

[複製鏈接]
發表於 2015-1-28 15:52:06 | 顯示全部樓層 |閱讀模式
 
Push to Facebook Push to Plurk Push to Twitter 

作者是 java 人買了 android 手機就想著可以自己寫 app,

        但其實還蠻懶的想不到要寫什麼,
        最後總算在買了兩年後在公司專案上碰到 android app。



        其實應該一篇一篇專文寫得,不過最近實在太忙,
        整理在一篇講一下心得就好。

        我這人懶得講廢話,底下講得都是我心中的重點。


        作者 android 純新手,有些地方如果我講得不對,
        請直接指正我沒關係,我會回來修改內文。


        然後底下所有東西都沒談到 web view,
        不是他不重要,是因為我沒空碰跟專案規劃上沒用到。先聲明。

        底下環境都是有裝 android-support-v4.jar 的前提下。


        所有程式碼基本上都是示範用,直接跑大概都還得改一些東西才會動,
        基本上就是我從專案拔來去掉 package 跟一些不重要 import 的結果,
        所以有 compile error 是正常的,想用就自己讀一下邏輯。

        ---------------------------------

        作者底子:

        Web 寫 n 年,前後端都碰,算精通。
        Java/JavaEE  三年++ ,算精通。

        Eclipse 非常嫻熟(有 Eclipse plugin 開發經驗...)


        Android 經驗 0,

        只有一年前試著建環境時開 emulator 跑個 hello world ,
        結果以 emulator 完全當掉跑不起來失敗告終。

        ---------------------------------

        目標專案:

        因為工作需要的關係跑來寫 android app ,
        目標是作我們 server 的資料給我們下游廠商看,

        然後可以做一些比方說是 server 跟 client 互傳訊息(含圖片),
        client 作業時可以拍照、錄音上傳、設定鬧鐘提醒 client 。


        btw 作者是個自幹控,裡面有許多 list ,
        但是我都是自己刻沒用 list view。


        網站我寫的、android app 也我寫的,web api 也我刻的,
        所以以下都是第一手經驗,絕對不怕踢館。XDDDDDDD

        ---------------------------------

        基礎建設篇:


     @ Android 環境建置:

        有很多 api 版本,但是基本上不用全裝,不要像我白痴的全勾然後等超久,
        裝你想要開發的開發版(我是裝最新的)跟你想要支援的最小版本,

        剩下的開發過程有需要再裝就好了。


        沒事不要升級 build tool 特別是在專案死線前,
        要升級請留個 4-5 個小時,處理升級後可能會有的開發環境問題。

        安裝還算容易,裝完把 eclispe 開起來就有很完整的環境。


     @ 模擬器

        內建的模擬器是垃圾,又慢又難用,真的是用到會想翻桌。(完)

        建議用 androVM ,只要裝 virtualbox (免費) 直接掛載他的 vm image 。
        http://androvm.org/blog/

        (2013/10/21 註,現在建議改用 genymoon 了~)


        然後開起來需要再手動下一行指令作 adb connect,
        adb connect 是用來讓他出現在 eclipse 能看見的裝置清單。

        我在 windows 底下是寫一個 bat 長這樣(兩行要接一起沒換行),
         adb 路徑可能會需要看各自位置改一下,點兩下就可以自動連接 還算方便。

--
C:\eclipses\adt-bundle-windows-x86_64\sdk\platform-tools\adb connect
192.168.56.101
--

     @ 裝置

        手機除錯選項要開,剩下就沒啥問題了,線插到電腦上通常就會自己抓到了。

        因為我懶得每次 run/debug 都在裝置,
        有多個裝置時我會建多個 run configuration,
        像是 myapp_androvm , myapp_evo3d 之類的。

        然後執行不同 run configuration 時,
        選擇對應的 device 然後勾 always use this in the future。
        選錯了也沒關係,重開 eclipse 就會要你再選一次了。


     @ debug

        Debug 最重要的當然是 breakpoint ,watch variable/expression。

        不管模擬器或實機,基本上 breakpoint 都是能用的,
        我一開始以為只有模擬器能用 breakpoint ,事實上是我白痴。


        但是有些狀況 breakpoint 不會觸發,
        因為 debug mode 基本上是 by process 去 inject 的。


        有些 activity/service 在後來才起不同 process 去作的情況下,
        你需要手動(?)設定 process 為 debug 的設定。


        但像是 BOOT_COMPLETE 或 service 在 startService 才開另一個 process,
        這種事件你不太可能抓到,所以有時候 breakpoint 沒 trigger 可以檢查這。

        (通常要開在另一個 process 會需要在 menifest 裡面設定,
          應該不會錯過才對。)

        如果是 service 的話我通常是先設定成同一個 process debug 完再改回去,
        BOOT_COMPLETE 我就純粹靠 log 了。

        (當然,也有可能只是我不知道該怎麼作。)


     @ log

        Android Eclipse 上 log 有內建的 logcat view ,
        可以開這個 eclipse view 來看 log,

        log 很多很亂,可以只篩 by applcation 來看自己 app 的 log 就好。


        然後如果碰到當掉(俗稱閃退),通常都是死在 NPE 或其他 exception,
        開過來直接掃哪些是紅字的 stacktrace 通常都很有幫助。

        eclipse 的 logcat 在多個裝置切換時會有靈異現象,
        他沒有聰明到你 run 哪個 dev 就切到哪個 dev 的 log。

        像我在 androVM 跟 evo3d (實機) 兩個裝置間執行,
        老是我在跑 evo3d 時看到 logcat 是 androVM 的 log 然後就鬼打牆。


        我後來終於搞懂要怎麼駕馭他,很簡單,
        用 "run" mode 在你目標裝置去跑你的 app ,

        之後在 device view 選到你的裝置的你的 app ,右上角 debug 插下去,
        他就會自動顯示 debug 中的裝置的 log。

        然後只要你沒有再做一次這個動作,基本上 log 就不會換裝置。

        有點蠢我知道,但是這樣作還算能動。


        基本建置篇大概就寫到這。

---------------------------------

        架構篇(簡論)


        架構不外乎幾個:
        Activity 、Service 、 Receiver , 前兩個比較重要,剩下的後面講:

     @ R

        這是我雖然寫了有點久 java 但是我寫 android 還是非常卡的一個重點,
        android 會自己 parse xml 去做一些很神奇的事情,
        像是你在 layout 寫個 android:id="@+id/hello" ,

        java 世界就會多個  R.id.hello 可以用,
        寫個 .aidl 檔, java 世界就會多個 interface 可以用。

        res/drawable 資料夾裡面多放個圖檔或 xml,
        java 世界 R.drawable 就會跟著出對應屬性可以用


        重點在於,要去理解 android 在哪些狀況哪些事情會幫你作 code gen,
        code gen 對應的內容是什麼,習慣他的雞婆之後就會好一點。


        比較常見的是 R.drawable , R.id, R.layout , R.string

        這件事情不太穩定,如果他運作得怪怪的,停掉整個 project ,
        然後作 clean build 通常會改善。

     @ Intent

        Android 裡面傳遞資料的基本單位,
        可用 extra 來傳遞string/int/long ..etc。

        也可用來指定要開啟哪一個 activity / service。

        舉例在 activity call 這段 code 可以傳遞三個資料給 ImageActivity。

             Intent intent = new Intent(MainActivity.this,
             ImageActivity.class);
             //MainActivity.this 是當前的 activity instance

             intent.putExtra("Title","桃園縣 asdasdasd先生 1 ");
             intent.putExtra("Url", "http://my.org/66.jpg");
             intent.putExtra("Name", "media_66");
             startActivity(intent);

        而在 ImageActivity 則是這樣接收

        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);

                Bundle extras = getIntent().getExtras();
                String title = extras.getString("Title");
                String url = extras.getString("Url");
                String name = extras.getString("Name");

                //作其他的事情
        }


     @ Activity

        如果你什麼都不會 Activity 一定是最先開始學的,

        拿網頁來類比的話, activity 大概就是 xxx.html ,

        裡面可以設 contentview 指定讀取特定 xml 來呈現畫面,
        可以設定事件之類的。

        你看到 app 裡面一個一個的畫面大多都是由  Activity 堆疊出來的,
        然後你按 back 鍵通常就是回到上一個 Activity。

        (可以設 flag 讓它不要,但 default 就是回到上一個 Activity )


        Activity 有幾個 life-cycle method 一定要知道,

        onCreate 、onStart、onResume 、onPause、onDestory
        一般畫面的建立跟呈現會盡量建議做在 onCreate 。


        onStart、onResume 跟 onCreate 的差別,

        onStart、onResume 觸發次數比較多次,
        是會在你手機切換到桌面再切換回來當前 activity 時、
        onResume 會重新被 call 。


        onPause 則是你切到其他 app 或回桌面時會 call ,
        onDesotry 則是你開了太多其他 app 或是 finish時會 call 。


        android user 一定都知道,使用者開一個畫面之後,
        看到 nofitication 或回桌面開其他 app 去幹甚麼事情是很正常的事情,

        所以不要期待一個 activity 會活太久。



     @ Service

        既然一個 activity 不會活太久,所以如果我想作一些事情,

        像是定期檢查有沒有新訊息之類的,就不太適合在 activity 作,
        因為我們可能希望每十分鐘檢查一次或之類的,
        萬一 activity 被 kill 掉就很麻煩。

        service 就會是個好選擇,他可以持續很長的時間。

        只要在適當的時候 call startService 或是在 menifest 裡面指定就行了。


        另外在使用 service 要注意長時間存在就表示會有耗電問題,
        要比較注意不要作太累的事情。



     @ Service/Activity 溝通

        講了 Service 跟 Activity ,實作上你接下來一定會問,
        那怎麼在兩者之間傳輸資料,基本上是透過 Activity bindService 達成。


        這邊蠻複雜的,然後我實作下來的經驗我會告訴你,
        直接寫 AIDL 最快。

        http://developer.android.com/guide/components/aidl.html


        這可以讓你達到 Activity 在跟 Service 溝通時,
        像是在直接操作 Service 的方法。

        這裡有我一個在 service 作背景錄音,但由 Activity 控制的例子。
        https://gist.github.com/tony1223/5731812


        這裡有一篇別人寫的文章可以參考
        http://android.yaohuiji.com/archives/728

     @ Fragment

        這個其實很重要,但我跟他不太熟,就先跳過。

        我自己寫 tab 時有用到 Fragment (我用官方建議的作法),

        但是用得很火大,因為切換 tab 時 fragment getActivity 全死,
        有些透過網路去抓資料的會比較晚更新,
        但那時候如果 fragment 已經被 detach ,操作 UI 就非常容易中 NPE 。

        搞得我每次作 UI 操作都要先判斷 isAdd() ,常常中獎。

        (當然,應該是我比較笨不會用。 -_-)


      @ Receiver

        用來註冊事件跟執行的角色,他是可以跨 service 跟 activity 的,
        甚至跨 app 都行的。

        像是我們可以用 Receiver 來接 BOOT_COMPLETED 事件,
        就可以在 android 一開機就做某些事情。(當然,需要額外要求權限)

        他其實就像是 APP 等級的 event listener 。


        在 activity /service 可以透過 sendBroadcast ,
        來 trigger 事件叫 receiver 做事。


        ---------------------------------

        廢話扯太多了寫不完了 -_-

        底下加快速度繼續講一些重點,有些如果寫得太簡單的歡迎推文問,
        我沒有時間把每個部份寫得很細,但現在寫得都是我有實作的。Orz

        ---------------------------------

        UI 功能相關篇


     @ 網路

        請讓我粗魯的下個副標:這真的是很靠北的麻煩問題。


        android 4.0 以後不准 UI thread 裡面碰網路,
        這是完全可以理解的。

        因為網路的傳輸可以快可以慢,但通常都很慢,
        如果在 UI thread 裡面作網路傳輸很有可能會讓畫面白掉、停止回應,

        (就跟 web 的 sync request 一樣意思。)


        這意味著什麼呢?

        先不要說你需要透過 web 去抓資料、爬資料、 call web api 之類的。

        光是你要從用一個 imageview 顯示一張放在網路上的圖,
        都得開個子 thread 先去把圖片抓回來。

        (不過抓圖這件事情還有麻煩的問題,後面專章討論。)


        然後你 google 一下網路會有幾個解法,眾說紛紜,

        大抵不外乎一個是 handler ,一個是 AsyncTask ,一個自己開 thread,
        我直接可以在這裡講,大部分狀況下直接用 AsyncTask 會比較快樂。Orz


        理由:
        life cycle 明確、資料傳遞簡單(postMessage真的很煩)。

        我一開始用 Handler 寫得 5-6 個實作後來全被我拆成 AsyncTask 了。


        他有兩個 method ,一個叫 doInBackground(){} ,
        不在 ui thread 裡面所以你可以幹 network 有關的事情。

        另一個叫 onPostExecuted() 會把你在 doInBackground 準備好的東西傳回來,
        這時候就在 ui thread 裡面所以你可以安心改 ui。


        他還有其他 method ,但是我沒用到我就不介紹了,有興趣的自己啃文件;

        但一定要小心的一件事情是 onPostExecuted 被執行的時間點不一定,
        所以操作 ui 時不要假設他的執行順序。


        剩下的自己參考這篇文章
  http://stackoverflow.com/questio ... asynctask-vs-thread


     @ 判斷網路連線狀態

        有時候你會需要判斷目前網路的連線狀態,請參考這隻 class:
        https://gist.github.com/tony1223/5732145

        context 可以丟 activity/service,
        在 receiver 時直接把 onReceive 的 context 扔進去就行了。

        當然,如果你想進一步判斷現在連線的是 3g 或 wifi ,
        (有些應用程式比較聰明的會在連接到 wifi 時才做需要大流量的事)

        可以用 networkInfo.getType 去判斷是 mobile(3g/4g) 或 wifi,

        這裡有 sample
        http://goo.gl/C6eY0


        然後如果你想作連上網路時自動恢復作業之類的,
        你可以寫個 receiever 聽這個 action
        android.net.conn.CONNECTIVITY_CHANGE

//manifest
<receiver android:name=".MyNetworkConnectedReceiver">
   <intent-filter>
      <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
   </intent-filter>
</receiver>

        這裡有一個我寫好的 receiver 範例
        https://gist.github.com/tony1223/5732191


        然後注意,開啟 app 時並不會因為手機有連線就自動觸發這個事件。
        (上面這行有需要、看得懂的就知道我在講什麼,看不懂就算了。XD)



        ---------------------------------
        沒時間了,底下以流水帳形式非常快速的帶過
        ---------------------------------

     @ 拍照問題

        可以直接調用原生的相機 Activity 進行拍照,
        但你一開始應該會碰到圖很小的問題。

        要設定 EXTRA_OUTPUT 讓他把檔案存在 SD 卡裡面,
        這樣才能拿到完整解析度的圖檔。

        範例 code 示例
        https://gist.github.com/tony1223/5732291


        然後這裡手動拍的照片是不會存進使用者相簿的,
        所以使用者可能會覺得有一點不方便。(至少我會)

        如果你想讓照片也進使用者相簿的話,可以看這個範例,不算難很好寫。

        https://gist.github.com/tony1223/5732304


     @ 錄音  自己找範例吧(汗)

     @ 播放音源檔

        這裡我直接用內建的播放軟體開,蠻簡單的。

        Intent audioActivity = new Intent(Intent.ACTION_VIEW);
        audioActivity.setDataAndType(Uri.fromFile(file), "audio/*");
        startActivity(audioActivity);


     @ 圖片問題

        android 的記憶體非常小,(16MB 起,看裝置)

        所有你自己 create 的 bitmap 都要自己手動 recycle ,
        然後大多數的時候你只能呈現小圖,不然沒幾張圖你就爆了。

        但是基本上 bitmap 操作還算蠻容易的。


        這裡非常容易事後整死人,請務必在很早期時就開始正視、面對這個問題。



        這一塊比較大的問題是在從檔案或網路讀取的圖檔,
        我有獨立寫了一隻包 cache 的 AsyncImageDownloader ,

        不然每次一讀網路圖片就要寫一次真的是沒完沒了,
        然後在裡面也處理掉 recycle 的問題,當然這只是解法之一。

        我用了一個 interface 跟一個自己做的 action 來處理這個問題。

        (主要還是我一直找不到 activity onDestroy 的 listener ,

          我猜這裡還有更簡單的作法,寫到這裡想到如果現在是我作,
          直接實作 image view 可能更靠譜,anyway 先我把目前的解法放出來。
        )

        (因為我所有的 activity 都有過自己的 BaseActivity ,
          我是直接源頭處理掉。)

        範例碼
        https://gist.github.com/tony1223/5732415



     @ 檔案上傳相關

        千萬別把檔案轉 base64 再傳,
        base64 會增加 33% 的體積真的只是小問題,

        真正的問題是字串組裝非常的浪費記憶體,(即使你用 stringbuffer)
        超容易因為字串太大而 OOM 的。

        (當然,這是跟 in-place 只讀幾 byte 寫入幾 byte 的 IO stream 比。)


        然後也別像我這麼假掰不只用 base64 傳,還用 JSON 傳,
        導致我傳一個 5M 的檔案,系統實際上會組出兩個 超大字串。

        我本來只是想讓 web api 一致的,在這個議題上浪費 40 hrs+ 後我放棄了,
        反正我是覺得在 android 的記憶體沒辦法這樣搞。


        我用 JSON + base64 傳 3M 檔案就有好幾隻手機會當掉了,
        (對,偏偏就是我的開發測試機不會,Evo 3d 你好樣的...)

        傳 10M 檔案是全死,改用 multipart 上傳後傳 30m 都沒問題。-_-#


        android  multipart 的檔案上傳很簡單,

        因為內建就有 httpclient ,只需要額外 include httpmime ,
        放到 libs 資料夾跟加到 classpath。

        我是在這裡抓 httpcomponents 整組,只拿 httpmine-4.2.5.jar 出來用。
        http://hc.apache.org/downloads.cgi


        這裡我直接放我自己用來作 get/post/multipart 的 util 函式當範例,
        其實 httpclient 現在作 multipart 用法真的很簡單,不難。
        https://gist.github.com/tony1223/5732470

        server side 的實作則跟一般網頁表單上傳一樣。

     @ 鬧鐘

        android 有內建的 AlarmManager,可以自己在指定時間,
        叫醒你一隻 receiver 起來做事,

        所以你不用自己煩惱要怎麼寫一隻 service 定期檢查時間。

        sample 大概如下

------------
Calendar cal = Calendar.getInstance();//這是要響起的時間
//設定時間
//ex. cal.setTime(new Date(2013,6,9));

AlarmManager alarmManager = (AlarmManager)
getSystemService(Context.ALARM_SERVICE);

Intent intent = new Intent(MyActivity.this, AlarmReceiver.class);

//要傳給 receiver 收的資料,這裡我傳的是案件編號
//intent.putExtra("RequestWorkID", detail.getRequestWorkID());

int uuid = 10;//just for example ,請視狀況取不重複 id
PendingIntent pi = PendingIntent.getBroadcast(SheetActivity.this,
                uuid, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pi);
-------------

        receiver 收到後要彈 dialog、要發訊息幹麼都可以。(那又是另一個故事了)


        當然,還是有要注意的事情:

        1.如果指定的時間是過去的時間,會當下馬上響而不是忽略

        2.重開機後,重開機之前設定的鬧鐘全部會消失。

        這裡我會建議先用 sharedPreference 存一份,
        搭配 BOOT_COMPLETED 時重新註冊事件。


     @ 存資料

        有幾個選項: SQLite 、 SharedPreference、存檔案


        我自己是懶得用 SQLite ,我的東西不算多感覺一直去兜 SQL 有點整自己,


        設定、一些字串什麼的小 cache 資料我建議用 SharePreference 存,
        大塊的資料用 db 也不適合,我建議你直接存檔案。

        除非是 list 型的資料或是你需要 select 協助你下一些條件,
        不然 sql 我覺得是用不太到。


        我的狀況是我的案件列表會從伺服器 api 來,所以我不需要自己 host db ,
        自己 host db 還要不斷考慮跟 server sync 的問題,所以我不這麼做。


        然後 SharePreference 因為是用 xml 作,資料一多時 commit 很花成本,
        像我前面實做上傳 task 時,一開始把 base64 字串直接塞進去,
        commit 動輒就 4-5秒 (很蠢我知道 ,後來我改存檔案路徑。lol)。


     @ 其他的


        其實還有很多東西想寫啦,但是越寫越簡略了我想寫出來應該也沒人看得懂,
        能看到這行的我都算佩服你們了,啊哈哈哈。

        寫一點跟 ui 有關的:
        ---------------------------------

        ProgrssDialog 可以幫你把整個畫面轉圈圈,讓使用者不能動。
        但是預設行為有一個行為很白痴是點圈圈外面就會取消 dialog 。

        記得要 call setCanceledOnTouchOutside(false) 如果那不是你要得。


        如果你只想要畫面上有一個小圈圈轉,而不是擋住整個畫面,
        你可以用 progressbar 這個元件
           <ProgressBar
                android:id="@+id/head_progress"
                style="?android:attr/progressBarStyle"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_gravity="center_vertical|center_horizontal"
                android:indeterminate="false"
                android:visibility="gone" />
                />

        我自己的用法是要轉就設 visibility 為 visible ,不轉就設 gone。

        ---------------------------------

        要寫一個用兩根手指就能 zoom-in zoom-out ,一根手指可以移動 ,

        這麼看似簡單功能的 imageview 其實是很難做的,
        要自己 overwrite onTouch 跟一堆有的沒的。

        有一個作法好像是扔到 webview 去作,但是我沒採用這個作法不確定成效。


        我自己有手刻一個用 relative layout 跟 margin 刻的
        zoom-in zoom-out / drag 的 image view。

        (對,我就是把他當 web 跟 js 來寫了......哈哈哈哈
          但後來才發現他有 matrix 這個更聰明的機制。(淚奔))

        https://gist.github.com/tony1223/5626327

        自己刻得 ui 元件在 xml 定義起來用法是這樣,
        把 full classname 放到 tag 去就對了。

        <com.mcall.view.ZoomImageView
            android:id="@+id/image_imageview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="matrix" />

        但是 zoom-in 會歪歪地,預設大小沒處理好,
        需要 parent 是 relative layout 都幾點不太靠譜,

        所以我一開始只是頂著應付需求先。=3=


        後來我有參考某別人做的因盜版因素所以不在架上的 somecomic app 的 ui ,
        另外刻一版新的(不過還是半成品,沒空調整好。Orz)

        你只要指定 imageview 的範圍,他就會根據 imageview 的範圍,
        自己調整裡面的 bitmap 在這範圍內自由縮放、瀏覽。

        https://gist.github.com/tony1223/5732622

        效果可以參考我錄得
        http://www.youtube.com/watch?v=QpCxTSjSE4M



        其實說半成品,是因為還有圖片往左拖或往右超過一定範圍就會觸發事件,
        可以用來做上一張或下一張,這一段我還沒處理好現在還不會動,

        不然他會是個蠻完整的 image view 。

        等搞定會再發一個 github repo 出來放這個元件。Orz


        寫到這剛好七百行又花掉我三個小時,希望對有興趣跳進來的朋友有幫助,
        有興趣再多聊聊的我們再另外討論吧,就這樣,掰。 XDDDDD
https://www.ptt.cc/bbs/AndroidDev/M.1370641609.A.1BF.html

 

臉書網友討論
發表於 2015-5-7 08:01:13 | 顯示全部樓層


  先支持了再说

版主招募中

發表於 2015-5-21 16:43:33 | 顯示全部樓層


  先支持了再说


您需要登錄後才可以回帖 登錄 | 註冊 |

本版積分規則



Archiver|手機版|小黑屋|免責聲明|TShopping

GMT+8, 2016-12-9 01:44 , Processed in 0.059770 second(s), 22 queries .

本論壇言論純屬發表者個人意見,與 TShopping綜合論壇 立場無關 如有意見侵犯了您的權益 請寫信聯絡我們。

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表