顯示具有 程式設計觀念 標籤的文章。 顯示所有文章
顯示具有 程式設計觀念 標籤的文章。 顯示所有文章

2016年10月25日 星期二

寫心經APP開發心得





寫心經是我第一款獨立開發並上架至Google Play的作品,歷經多次改版,也收到不少使用者的鼓勵與建議,今天來跟大家分享開發過程與心得!

 緣起

「寫心經」是在自我實現的期望下誕生的,當時想利用所學做出實績,經簡單的分析發現在生活美學相關的App仍不多,經過一些規劃與思考,逐步將其完成。

在決定開發這款App的當下,智慧型手機與App的生態環境已略為成熟,各界興起開發App的風氣。作為App發行重要平台的「Google Play」給了許多開發者發展機會,大幅降低了進入門檻。


從何開始

寫心經App是如何誕生的呢?或說從無到有,要創造一款App需要做什麼?
 

  1. 題材發想:
    要做什麼? 在行動裝置上書寫經文
    要有什麼功能(概略)? 書寫、比對、儲存…
  2. 確認市場方向:
    App的分類是什麼? 生活品味
    這個分類的現如何? (當時)不多見
    這個題材是否有人需要?
  3. 開發技術評估:
    要兼顧些平台的使用者?
    要採用跨平台的開發技術嗎?
  4. 系統原型設計:
    視覺化的東西有利進一步分析
    試著做出原型與視覺設計
  5. 系統開發:
    實際撰寫程式,使其滿足當初規劃
    釋出前的品質確認,可以透過一些測試方法來評估
  6. 上架作業:
    備妥文案與圖資,至各應用程式平台進行上架
  7. 更新維護:
    定期閱覽評價與評論
    隨時追蹤平台的規章變更 (通常會發信通知)
    隨時追蹤第三方資源的安全更新 (通常會發信通知)

上述的每個步驟若再展開,仍有說不完的細節。由於資訊技術與環境的快速變動,每個時期會經歷的技術都不盡相同,在這邊不多談太多細節。

獨立開發意謂著所有事情都要自己來──不嫌累,學更多!


聆聽使用者

任何一款App都需要聆聽使用者的需求,但不只是在開發前聆聽一次,你必須不斷蒐集回饋。

寫心經有一些功能即是來自使用者的建議,有時你得到的建議正好與你自己想做的相符,但仍需考量實現的困難度。近期比較重大的改革是加入字跡記錄的功能,它的概念很簡單,即是儲存使用者書寫的字跡,並彙整輸出成手稿檔案。說來簡單,但要實踐它仍需費一番功夫,例如:如何儲存字跡?用什麼格式來存?如何縮放筆跡?如何排版為手稿?有許多問題需要開發者一一克服。



手稿輸出



無處不在的挑戰

App猶如任何一款資訊產品,都需要思考營運問題,即便沒有「營利」行為,也要為了滿足使用者努力,定期維護與發布更新版本的動作是少不了的。這些過程是充滿挑戰的,挑戰可能來自Android版本的演進,老舊的程式需要翻新、碎片化的硬體規格,讓你不易兼顧所有使用者與特定功能的實踐困難等。

面對這些挑戰,開發者必須有隨時跟上技術演進的準備,實際上這也是資訊人的必備技能,你必須不斷學習,但學習的成本是高的,無論是金錢或時間,因此你還需要培養強大的自學能力作為後盾 (關於自學的經驗,再找機會與大家分享)。

若你的APP具營利性質,挑戰則不僅功能維護,更包含如何培養、發掘、維持客源的問題。


成果

至今寫心經已累計萬次安裝,我在開發過程中學習不少,這也為我的工作打下基礎。成果可以量化,也可以是模糊的,總之這些經驗的累積多少會令你成長,無論是實質上的技術,或是思考問題的角度。

也要感謝每位使用者的迴響,您的意見、評價與評論都非常寶貴,讓我能夠針對應用進行調整與優化,期望寫心經能帶給您愉快的體驗。


後記

資訊界總是一波又一波的新趨勢,本文撰寫時App已不是時下焦點,取代而之的是VR/AR, IoT, FinTech。在競爭激烈的背景下,我想目前「App」一詞所代表的意義,更貼近資料展示平台、特殊的/靈活的/便攜的服務提供管道,開發者要思考的是如何透過它來提供服務,以及可以帶給使用者何種價值。

資訊人應該隨著時下潮流,並動手嘗試,說不定能發現各種新的可能。

2016年1月31日 星期日

Android存取後端Server (part 1)

前言

若您開發的App有保存資料的需求,多半希望將資料存集至遠端伺服器,比較建議的作法是透過PHP, JSP, AST.NET等技術,以Web方式實作資料傳輸的媒介,存取資料即使用POST或GET的方式來進行。

在實作前有件事情需要注意,Android 4.0規定了存取網路的操作必須執行在另一Thread(執行緒)中,為了因應此規定,目前有兩大方向可供選擇:
  1. 使用Runnable與Handler:這種方法比較複雜,但彈性高,可依自己的需求規劃,同時在Handler的使用上需注意Memory leak之情況。
  2. 使用非同步任務(AsyncTask):此為相對簡單的方法,依規定好的格式即可以輕易完成網路存取。
接下來,就開始以第一種方式實作(Runnable + Handler),簡單一點的AsyncTask預計寫在下一篇(part2)。以下以重點說明為主,完整的範例已放在GitHub上,有需要的參考一下即可。


Step 0. 理解執行架構與流程

這邊用張簡圖表示,描述自步驟1發出請求起,至將遠端資料顯示至TextView的第驟6。Handler, Thread與Activity的互動關係大略是這樣。




Step 1. 開啟網際網路存取權限

為專案加入網際網路存取權限,找到AndroidManifest.xml,加入 android.permission.INTERNET 。


Step 2. HTTP請求與回應

以下程式摘錄自SendDataRunnable.java,可以直接套用,範圍程式中已把它包成method,若您想要自己另外處理,參數對照一下即可。這段程式碼有幾個重點:
  1. 第n列,有些教學範例會使用HttpClient這個類別來操作,但其已被Android棄用,因此這邊改以HttpURLConnection實作。
  2. 第n列,URL物件需要傳入Server上某個php檔案 (或其他Web程式)的路徑。
  3. 第n列,HTTP_OK這個常數值為200,表示正常狀態,確認正常了才開始讀取Response,你也可以再該處加上else,處理一些異常狀態。
  4. 第n列,composePostString( )負責組合參數,需傳入HashMap作為數個參數的集合(HashMap有Tag與Value對應的概念),詳見範例程式。
private String sendData(String target, HashMap params){
    String responseData = "";
    try {
        URL url = new URL(target);
        //建立與目標之間的連線
        HttpURLConnection connection = (HttpURLConnection)url.openConnection(); 
        connection.setRequestMethod("POST");
        connection.setDoInput(true);
        connection.setDoOutput(true);

        OutputStream stream = connection.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream, "UTF-8"));
        String c = composePostString(params);

        writer.write(c);

        writer.flush();
        writer.close();
        stream.close();
  
        int httpResponseCode = connection.getResponseCode();
        if(httpResponseCode == HttpsURLConnection.HTTP_OK) {
            String oneLine = "";
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(connection.getInputStream()));
            while ((oneLine = reader.readLine()) != null) {
                responseData += oneLine;
            }
        }

    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return responseData;
}



Step 3. 實作Run()

run( )是Runnable介面中唯一的方法,當其被執行時會自動呼叫,在範例程式中拿來呼叫Step 2.所寫的sendData( )。

此段程式相當簡單,sendData( )會傳回Server回應的資料,故需檢查資料長度是否正常,並針對各情況進行處理。傳回來的資料會放在response變數中,至此所有動作仍不在Android的主執行緒上執行,若你要在這邊把資料更新到UI上是無法存取的,所以這邊有個handler.obtainMessage( ),用它把資料傳至主執行緒上的Handler。

但有一點請注意,obtainMessage傳入兩個參數,第一個是int型別的值,這是讓你識別目前的情況用的,姑且稱它為「識別碼」,你可以自己決定該如何實作,例如:1代表新增成功、2代表刪除成功、3代表取得清單資料等。為了方便管理,也可以宣告常數來處理,讓程式更易維護。


@Override
public void run() {
    String response = sendData(this.path, this.params);
    if(response.length() > 0) {
        //TODO
        handler.obtainMessage(0, response).sendToTarget();
    }else{
        //TODO
    }
}



Step 4. Handler

在上一步驟中,我們已經取得Server回應的字串資料,為了在主執行者中接收這個字串資料,需要準備Handler來承接,礙於Memory Leak的問題(有機會再談),建議以內部靜態類別來實作,完整程式請參考範例中的main.java。

這邊定義了HttpHandler類別,其繼承Handler (來自android.os.Handler,別import錯了)。

請特別注意幾個地方:
  1. 第n列,將WeakReference強迫轉型為Main,這個Main需繼承當初建立WeakReference傳入的泛型 (本例是Activity),白話的說Main是Activity,但其具有Activity沒有的成員與方法(因為繼承),因此我們要把它強迫轉型為Main,以方便進行後續處理。
  2. 第n列,這邊用if判斷了msg.what值,該值就是在Step 3所傳入的int參數(本例為0),所以這邊檢查等於0時把Server傳回的response字串顯示在TextView上。
  3. 承上,response字串也是是在Step 3傳入,但其型別為Object,所以這邊取出時要再強迫轉型為字串。


static class HttpHandler extends Handler {
    WeakReference weakReference;
 
 public HttpHandler(Activity activity) {
        weakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        Main main = (Main)weakReference.get();
        if(msg.what == 0) {
            //TODO 依what判斷後續處理
            main.txvResponse.setText((String)msg.obj);
        }
    }
}



Step 5. 串接一切

各細節寫好了,現在要把他們串起來!實際上該封裝的東西也都裝好了,所以真正在呼叫時動作不會太多。

首先,在呼叫前確保handler與params兩個物件有新建了,兩者的作用在前面都有提過。
handler = new HttpHandler(this);
HashMap<String, String> params = new HashMap<>();
接著填入要送至Server的參數,每個參數都會有Tag (或稱為Name)與Value的對應。
params.put("name", "tom");

最後要將整個HTTP的請求與回應動作放在另一個執行緒中執行,因此我們這麼處理。記得網址的部分自己換掉。
new Thread(
    new SendDataRunnable(handler, "http://www.example.com", params)
).start();


小結

上述的程式還可以再封裝的更細緻,但有時封過頭會綁手綁腳的,大家若要直接使用範例程式,請注意裡面標示//TODO的地方。
這邊有小建議,請盡可能的將識別碼、網址或ip之類的資源統一管理,看是要宣告成常數,還是把它放在string.xml中。
還有!安全議題也需注意,本篇沒有特針對安全部分進行討論,若你要傳輸的資料是有安全顧慮的,請以https的方式進行,亦需留意Server端的安全措施。


2015年9月7日 星期一

程式開發觀念:減少重複

本篇並不談論深入的內容,主要為多少有接觸程式開發,但尚無法利用物件觀念來簡化程式的人寫所寫。

基本功:提取重複敘述

在開始之前,必須先確定你有這種能力,你可以把重複的程式包裝為方法(method),在不同的概念下也有人稱為function、副程式、函數等。

這樣好處有很多,大概列出幾項:
1.    以後要使用直接呼叫,不用再寫一次。
2.    多處的處理都會參照到一個方法,日後若需修正,僅需改一個地方(即修改方法的內容)。
3.    防止你忘了處理某些細節,因為相關的動作被包起來了,一次完成。

包裝成方法多半需要傳入參數與制定傳回值,這部分務必弄清楚,不同語言可能有不同的規範,請自行參閱各語言的書籍,這邊不再贅述。

若你對傳入/傳回沒有概念,這邊提供簡短的說明。舉例來說,你需要在Android內顯示Dialog,你可以編寫如下的method:

private void showDialog(String msg){
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle(getString(R.string.strTitle));
    builder.setMessage(msg);
    //……
    builder.create().show();
}

在這個例子當中,會變動的只有Message(訊息)內容,其他的動作都是不變的,若你想讓Title也可以變動,那麼可以在showDialog後方的括號內,再加上第2個輸入參數,例如showDialog(String msg, String title)並在method內將其應用即可。

那麼傳回值呢?這個例子的傳回值是void,意即沒有傳回值,假設你需要在Dialog顯示後傳回True或False,那請把void修正為boolean,並於method內適當的地方加入return true; 或 return false; (視你的目的而定)。

但會這種包裝是不夠用的,在一些情況下你必須跨類別執行相同(或相似)的行為,這是可以利用「物件」的概念來處理。

善用物件

我們也可以透過物件導向的觀念來提升程式的重用。在物件導向領域中應該多少聽過封裝、繼承、多型,三者都非常重要(且深奧),本篇不探討細節內容,網路上已有不少大師提出看法與建議,值得大家參考。

就先談談最基本的封裝吧,簡單說就是要把一些處理過程包裝起來(最終包成class),而為什麼要這麼做呢?舉幾點好處:
1.    為了自己方便,在日後的開發過程呼叫已經包裝好的東西即可。
2.    為了同伴方便,要實現相同功能只要呼叫你包裝好的即可。
3.    安全性考量,畢免日後忘了進行某些處理。

而繼承可以讓你更方便的擴充,多型可以讓你的擴充更有彈性 (這是非常簡短的說法,詳細內請自行參考相關資料,不然真的談不完),善用這些觀念,可使程式更易維護,更白話點,好處即是程式碼可以方便的重複使用與管理。

來看看封裝的例子,你可以把網路連線的系列動作包裝成一個類別「網路存取.class」,其下有一些相關的屬性(Property),像是解析HTML使用的固定Tag、定義回傳狀態Tag、連線目標(URL)等,以及一些方法(method),如「檔案下載」與「檔案上載」。這邊的method如同本文前半段所提,那在這邊為何要再說一次呢?

因為這邊的method是寫於「網路存取.class」裡面,這個class聚合一些相關的方法與成員,專門設計來處理網路的相關事項,今後你若有相近的需求,基本上僅需這麼處理:

網路存取 i = new 網路存取();
i.檔案下載("http://hefetech.blogspot.tw/");

那繼承與多型呢?以本例而言不見得會使用到,而且相關的例子也不適合在這邊舉出,先跳過這個部分。

思考如何建立類別

有不少人在接觸物件導向時,難以將現實體驗轉化為虛擬的類別,若你也有這種問題,多練習還是最適當的方法,你可以試試以下例題,想想如何把它轉化為類別(class),思考這個類別裡面可能有什麼屬性與方法。
1.    鍵盤
2.    計算機
3.    滑鼠
4.    西瓜
5.    畢業證書
6.    智慧型手機
7.    魔術方塊
8.    行動電源
9.    鏡子
10.    山豬

你說怎麼沒有汽車嗎?倒是有個山豬可以讓你試著使用萬年動物「梗」,像是跑()、叫()…。這些都是現實世界的實體,概念鮮明,容易學習,但其通常與你寫的程式無關,你真正需要的是培養將較抽象概念轉為類別的能力,在這種情況下你很可能要「無中生有」,自己規劃類別中的內容,跟據實際需求進行定義。

在實際規劃上,以下這些例題不一定只產生單一類別,很可能會再拆分,但那屬於更進階的部分,不在本文討論的範圍。:

1.    會員
2.    HTTP連線管理器
3.    身分證字號驗證器