顯示具有 android 標籤的文章。 顯示所有文章
顯示具有 android 標籤的文章。 顯示所有文章

2013年8月11日 星期日

說說,Android與iOS之中的偏好設定。


好緊張,我想我必須要對iOS和Android作個整理,當連"true"和"YES"會寫錯的時候,開始覺得,腦袋裡面的資料庫開始需要再進行更深度的重新整理了。

所以先來首蘇軾的〈定風波〉配上冷氣和下雨聲音開場:
莫聽穿林打葉聲,何妨吟嘯且徐行。竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生。 
料峭春風吹酒醒,微冷,山頭斜照卻相 迎。回首向來蕭瑟處,歸去,也無風雨也無晴。
好,然後開始。


我想從最基本的儲存動作開始,在Android之中,大概可以劃分為5種儲存動作,主要可以分為:
  1. Shared Preferences(分享式喜好設定):儲存私有的簡單資料在鍵-值配對
  2. Internal Storage:儲存私有的資料在裝置的記憶體(手機中)
  3. External Storage:儲存公開資料在外部的儲存空間(如SD卡)
  4. SQLite Databases:儲存結構化的資料在私有的資料庫
  5. Network Connection:儲存資料在網路上的網路伺服器中
詳見這裡,以及官方文檔。

而關於IOS,我發現最不一樣的地方就是好像都一樣,尤其是第3點的部份,在Android市場淘汰機制下,許多裝置目前也開始衍生出了不需要SD卡擴充的機型,而分別推出內建
16G或是32G的外部儲存容量款。

至於在code的概念上,我覺得是關於Shared Preference差異最大,
在Android裡面,我們可以設定不同的SharedPreference物件,他將會儲存在指定的資料夾之中,並且對他作存取。於是我們可以作一個class把他封裝起來:


//封裝偏好設定的class
public class SharePreferencesHelper {
    Context mContext;
    //建立一個屬於偏好設定的名字
    final String NAME = "SharePreferencesHelper";
    //一個私有的靜態的SharedPreferences檔案
    static SharedPreferences mSharedPreferences;
    
    //一個開關用途的key值,我們透過方法實作他的設定與獲取。所以外部不需要調用到他
    final String KEY="switchSomeThing";
    
    
    //建構子,確保SharedPreferences實例存在
    public SharePreferencesHelper(Context mContext) {
        this.mContext = mContext;
        if(mSharedPreferences==null)
            mSharedPreferences = mContext.getSharedPreferences(NAME,
                                                               android.content.Context.MODE_PRIVATE);
    }
    //實作的方法1
    public void setON() {
        mSharedPreferences.edit().putBoolean(KEY, true).commit();
    }
    //實作的方法2
    public void setOFF() {
        mSharedPreferences.edit().putBoolean(KEY, false).commit();
    }
    //實作的方法3
    public boolean getValue() {
        //這裡可以getBoolean(), getString() 依照需求用方法封裝起來
        return mSharedPreferences.getBoolean(KEY, false);
    }
    
}

這樣就很方便,甚至可以把全部都當作String來儲存,以後只要指定Key值,並且作適當的轉型就可以動作了。其他範例可以參考官方文件裡面也會有詳細說明。

而iOS之中,就更為簡便了,主要創立調用的方法是,他會被存成一個.plist檔案:

NSUserDefaults *userPrefs = [NSUserDefaults standardUserDefaults];

存取值得方法也有很完整的方法可以調用,例如:

//設定某個值
 [userPrefs setValue: Value值  forKey:Key值];
//獲取某個值
[userPrefs valueForKey:Key值];

當然也會有boolean或是依照特別user account所設立的偏好等其他方法。

詳細可以參考這個網頁,或是官方的文檔。


話說一直看JAVA 風格的API Reference之後,突然跑去看Apple的API Reference整個有點彆扭呀。


---------- 我是碎碎念的分隔線 ----------

話說,開始碰iOS至今也有大概也有一個多月左右的時間了,有遇到一些問題與挑戰,我想我應該把他紀錄起來,在學生時代,學校會產生許多比iOS還要多的Android開發者,這不是壞事,畢竟免費又自由唄。但是在業界,Android的開發成本似乎的確比iOS還要高很多,有許多人開始從Android跳到iOS的行列,於是雙棲的過程之中,也碰到了許多困難。

把他紀錄起來,以下不負責列點碎碎念:
  1. code:
    一開始碰到iOS的時候,心想:"這是什麼火星文!"後來其實也開始習慣了,這是有好處的,幫助我在iOS與Android的coding style作區隔記憶。iOS比較像是c++的內容,而也有獨特的方法調用規則,我想,保持著勇氣,就能夠習慣與成長。
  2. 命名的哲學:
    Android很喜歡用get and set,可是我發現iOS不會這樣作,大多獲取資料的方法就是直接"valueOf:"開頭,而設定的部份在UI相關的設定較多沒有"set"開頭的字樣,所幸在Xcode裡面,方法調用都有自動生成的機制,這幫助很大,比較不會遇到"怎麼可能想得到可以這樣作?"的問題。
  3. API Reference:
    這塊其實我糾結了很久,約有2到3年的時間已經很習慣Java style的API Reference,突然看到一排畫下來沒有表格樣式的介紹,突然有種不知道在寫什麼的感覺。俗話說得好:"英文很重要"。除了這個之外,我覺得,如果能夠先熟悉iOS呼叫與設計method的style,然後習慣辨識在iOS API Reference粗體字代表標題的意思,我想,其實iOS 的文檔也還滿好讀的。
  4. 硬體調用的架構:
    架構上真的,不太一樣。iOS方便的地方在規定較為嚴格,也因為設備也很單一,所以像是UI上考慮的解析度就差不多那幾種,調用其他硬體的方法也相對Android明確,就算想要繞路,也可能遇到iOS沒有提供source code或是審核可能不通過的情況下,我們實現方法的方向就是一種,這也算是方便開發者的一種作法。Android在實現上就較為不一定,同樣在UI的設計上,在xml的layout上雖然有官方建議的方式,卻也有些人用比例的方法調整layout的設計,或是switch不同解析度的畫面讀取不同的layout。
大概是這樣,還在學習,以後有心得或是更正的內容再補上。

















2013年7月2日 星期二

說說,Android debug的方法。


好久沒更新網誌了。

最近有朋友問我該怎麼進行Android的錯誤偵測?我想,不就使用斷點嘛?
但是實際上其實連自己也不怎麼愛用斷點,
於時我想我整理了常用的錯誤偵測方法,以供自己,以及那位朋友參考:
  1. 看DDMS
  2. 下Log
  3. 使用Toast
  4. 設break point,使用Debug模式
且待我一一娓娓道來...

首先是DDMS:


在eclipse的視窗右上角有個可以把DDMS叫出來的選項,
如圖我已經把他叫出來並且切換到那個視窗了,
這裡面有很多有趣的東西可以看,
有興趣可以參考Android develop tools, using DDMS,
或是其他中文比較看得懂的說明。
一般常用不外乎看heap,看thread,或是看LogCat。
不過LogCat其實也可以放到編輯的區域中顯示,像是有時候我就會把他放到下面:

但是除非螢幕夠大,要不也沒有特別的好處。

值得一提的是LogCat是顯示log的地方,
他可以由eclipse的windows->show viw裡面找到,找到後像是這樣:


LogCat來查看log訊息。而log不但android官方愛用,
我們也可以利用他來達到在app執行時候,
回傳給編譯環境一個資訊的工具,log有不同的等級標記,可分為:

  1. Verbose
  2. Debug
  3. Info
  4. Warn
  5. Error
這不但是android系統給予開發者在確認當下狀態的資訊,
也是我們可以對app在運行時候給自己的狀態資訊。
其中Verbose和Debug在系統上應該是看不到的,
這是運用在開發階段時才會存在的等級,
意味著釋出成apk之後他就不會出現在LogCat裡面啦!

當我們app出錯時,系統便會跳出Error level的log訊息,
於是會在LogCat看到滿滿的紅字,
此時他會詳細說明出錯的log以及造成崩潰的位置,
如果崩潰的位置是在我們所撰寫的程式之中,那麼雙擊LogCat該訊息,
就會自動開啟該檔案,並且跳到崩潰的那行。


下Log:

剛剛我們說可以由 LogCat來看到手機裡面app或系統運行的狀態,
那該如果撰寫程式碼來讓系統顯示自訂的log動作呢?

參考:
String key="這是一個key";
        Log.d(key, "debug");
        Log.e(key, "error");
        Log.i(key, "information");
        Log.v(key, "Verbose");
        Log.w(key, "Warning");


這一系列的方法是使用Log裡面static的方法,其中有兩個參數,
首先我們需要一個key值,這個key值根據你的需要給予一個固定的值,
顯示的時候LogCat可以根據key值過濾需要的內容:



所以key值的設定最好在同一個需求就用一樣的值。

再來就是輸入我們需要表現出來的字串,
會用來檢驗變數或是程式執行到的位置。
Log下好之後,記得在LogCat中對Log設定過濾,
之後程式運行時就可以看到啦!




使用Toast:

這個方法的使用其實還滿簡單的,他應該算是一種使用回饋的表現,
不過我有時會拿來做為檢測的工具,為什麼呢?
因為有些時候,我們會需要到戶外實際測試,
此時不可能接著電腦,又不想要用到網路和伺服器連接,
也不想要作SQLite儲存Log訊息。
於是用Toast會是一個不錯的選擇,
他的操作方法可以參考官方說明,或是中文的相關教學。


設break point,使用Debug模式:

在編譯環境中,左手邊在顯示行數的地方雙擊,會跳出一個藍色的小點點,
那是break point:

然後執行時候對著專案按下右鍵,選擇Debug As-> Android Application:


此時系統就會像正常運作一樣,不過,當app跑到該行的時候,會跳出這個警告:

點擊"Yes",他會跳到Debug的視窗,長這樣:

並且他會停在之前設定好的break point ,可以使用快捷建F5、F6以及F7來操作繼續該點的
進入、跳過,或跳出。圖示的點擊在畫面左上角:
透過deBug模式,就可以藉由break point來檢測目前的參數以及各種行為的位置啦!

以上,四種方法提供參考。

















2013年5月15日 星期三

說說,關於OpenCV在今天上課的內容。



這是一份上課後的紀錄,只是純粹紀錄一下曾經帶過這堂課程。

這是project的影片,畫面可能會有點小:





然後這是今天上課的投影片,載點在這裡。

另外也有相關內容的載點,一併放上來。

2013年5月1日 星期三

筆記,android的廣播註冊和監聽方式;使用CursorLoader監聽其狀態


這篇文章可能會有點多,但是因為這兩個禮拜可能會很忙,
所以先留著接口(interface?哈哈)之後補。

首先這是廣播註冊、送出與監聽的方法,專案檔下載在這裡。
貼上主要的程式碼:

package com.example.broadcastTest;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

/** 測試broadcast */
public class MainActivity extends Activity {
    final String TAG = getClass().getSimpleName();
    Button button;
    TextView textView;
    // 這裡用static 是為了thread用途,建議如果夠大的話,用Service包起來
    static boolean threadIsStop;
    final String ACTION_BROAD_CAST = "susanTestYAHU";
    final String KEY_INTENT_SIMPLE = "intent用的key值";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.text_show);
        button = (Button) findViewById(R.id.button1);
        // 一個開關的設置

        if (threadIsStop) {
            button.setText(R.string.lb_switch_turnOn);
        } else {
            button.setText(R.string.lb_switch_turnOff);
        }
        button.setTag(threadIsStop);
        button.setOnClickListener(mOnClickListener);

    }

    OnClickListener mOnClickListener = new OnClickListener() {

        @Override
        public void onClick(View v) {

            boolean onThread = (Boolean) button.getTag();
            if (onThread) {
                // 關閉程序
                threadIsStop = true;
                button.setTag(false);
                button.setText(R.string.lb_switch_turnOn);
            } else {
                // 開啟程序
                button.setText(R.string.lb_switch_turnOff);
                threadIsStop = false;
                startThread();
                // FIXME 這個demo可能會有thread不同步的問題,不過不是主要的目的所以不管
                button.setTag(true);
            }// end if
        }// end onClick
    };

    @Override
    protected void onResume() {
        // 註冊監聽器
        IntentFilter mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(ACTION_BROAD_CAST);
        registerReceiver(mBroadcastReceiver, mIntentFilter);
        super.onResume();
    }

    @Override
    protected void onPause() {
        /*
         * 如果使用broadcast receiver來改變UI內容,則必須要註冊在onResume和onPuase中
         * 如果需要註冊全域,可以寫在Manifest中, 可參考:http://yangguangfu.iteye.com/blog/1063732
         * 裡面的"系統註冊"。其中也有更多的詳細介紹。
         */
        unregisterReceiver(mBroadcastReceiver);
        super.onPause();
    }

    void startThread() {

        new Thread(new Runnable() {

            @Override
            public void run() {
                Log.d(TAG, "thread啟動");
                long count = 0;
                while (!threadIsStop) {
                    // 開啟程序,定時送出一個broadcast
                    Intent intent = new Intent();
                    /* 注意送出的Action要和接收的一樣 */
                    intent.setAction(ACTION_BROAD_CAST);
                    // 填入值
                    intent.putExtra(KEY_INTENT_SIMPLE, count);
                    // 送出廣播
                    sendBroadcast(intent);
                    Log.d(TAG, "sendBroadcast count: " + count);
                    count++;

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }// end try
                }// end while
            }// end run
        }).start();

    };

    /** 廣播的監聽器 */
    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(ACTION_BROAD_CAST)) {
                /*
                 * 查Action,意味著這邊可以同時接收許多Action,
                 * 但是在register的時候也必須要填入filiter之內,要不根本進不來
                 */

                long value = intent.getLongExtra(KEY_INTENT_SIMPLE, 0);
                textView.setText("value notify: " + value);

            }
        }

    };

    @Override
    protected void onDestroy() {
        /*
         * 這裡設定當activity結束時thread也應該結束,如果是包在Service裡面寫就可以追蹤
         * 參考:http://androidsusan.blogspot.tw/2013/03/service.html
         */
        threadIsStop = true;
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        // getMenuInflater().inflate(R.menu.activity_main, menu);
        return false;
    }

}


其實這沒什麼好講的,只是要注意在註冊broadcast receiver的方法也可以在manifest中做掉,就是差別在全域的註冊和區域的註冊哩。
動態和靜態的註冊可以參考這裡,不過這篇有點難讀,請慢慢服用。

------------我是不想要換篇新文章的分隔線------------

再來就是CursorLoader監聽其狀態的作法,專案下載在這裡。
專案實作了獲取手機聯絡人相關訊息,如果要實作必須要注意它有權限必須登入。

主要是使用了LoaderCallbacks<Cursor>物件,這裡必須使用getLoaderManager()獲得LoaderManager物件,呼叫initLoader()初始化LoaderCallbacks物件,然後就可以在其中實作。

注意這是3.0以後才有的東西,話說我看了一下很久以前上架的程式,3.x的裝置幾乎沒有(沒有顯示?)超過2.3.x版本的裝置大概都超過了七成,可能樣本數不是很足夠,不過現在開發似乎都不是很想開發3.0以下的版本了呀...

關於LoaderManager的部分,這裡有一些介紹,
如果你看英文會比比看中文還要快樂,可以略為參考這裡。

主要程式碼:

package com.example.cursorloader;

import android.app.Activity;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.app.ProgressDialog;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts.Photo;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;

/** {@link CursorLoader}監聽的方法 */
public class MainActivity extends Activity {
    final String TAG = getClass().getSimpleName();
    TextView textView, tx_info;

    ProgressDialog mProgressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.textView);

        tx_info = (TextView) findViewById(R.id.TextView_info);
        tx_info.setText("還沒開始!");
        // 設定轉轉轉
        mProgressDialog = new ProgressDialog(MainActivity.this);
        mProgressDialog.setMessage("Please Wait..");
        mProgressDialog.setCancelable(false);
    }

    LoaderCallbacks<Cursor> mLoaderCallbacks = new LoaderCallbacks<Cursor>() {
        // 我們可以實作一個新的callback來實作更完整的呼叫方法
        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            Log.d(TAG, "獲取cursor " + id);
            CursorLoader mCursorLoader;
            // 設定Cursor搜尋條件,這邊可以在建構子時操作
            mCursorLoader = new CursorLoader(MainActivity.this);
            mCursorLoader.setUri(Phone.CONTENT_URI);
            String[] PHONES_PROJECTION = new String[] { Phone.DISPLAY_NAME,
                    Phone.NUMBER, Photo.PHOTO_ID, Phone.CONTACT_ID };
            mCursorLoader.setProjection(PHONES_PROJECTION);
            // 創立就馬上執行
            mCursorLoader.loadInBackground();
            return mCursorLoader;
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor mCursor) {
            // 這個介面的意思是如果在onCreateLoader()中創立的loader結束時在這裡就會有反應。
            if (mProgressDialog.isShowing()) {
                mProgressDialog.dismiss();
            }
            tx_info.setText("Complete!!");

            // 結束後的動作
            Log.d(TAG, "獲取cursor" + mCursor);
            if (mCursor != null) {
                Log.d(TAG, "cursor count: " + mCursor.getCount());
                if (mCursor.getCount() > 0) {
                    StringBuffer mBuffer = new StringBuffer();
                    while (mCursor.moveToNext()) {
                        // 獲得名字
                        mBuffer.append(mCursor.getString(mCursor
                                .getColumnIndex(Phone.DISPLAY_NAME)));
                        mBuffer.append("\n");
                        mBuffer.append(mCursor.getString(mCursor
                                .getColumnIndex(Phone.NUMBER)));
                        mBuffer.append("\n");
                        mBuffer.append("\n");
                    }// end while
                    textView.setText(mBuffer.toString());

                }// end if(mCursor.getCount()>0)
                mCursor.close();
            }// end if(mCursor!=null)
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            // TODO 這是reset的方法,沒有用到

        }

    };

    @Override
    protected void onResume() {
        mProgressDialog.show();
        LoaderManager mLoaderManager = getLoaderManager();
        // 利用觀察者模式來設計監控loader的方法,其中前兩個參數是判定方法可以由控制項操作LoaderCallbacks的內容
        mLoaderManager.initLoader(1, null, mLoaderCallbacks);
        super.onResume();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        // getMenuInflater().inflate(R.menu.activity_main, menu);
        return false;
    }

}



有空來補,最近在開始學ios,最麻煩的是兩個鍵盤不一樣,操作mac習慣以後換回來windows系統就整個卡卡的。可是mac的eclipse快速鍵又不太熟悉,突然覺得生命太長了可能要睡少一點才能在知識上面有所收穫才是...















2013年4月27日 星期六

說說,專案import jar檔或是其他專案的方法。


在android中,我們開發專案時常常會需要其他朋友所開發的輪子,
於是就會需要improt其他專案檔或是封裝好的jar檔。

這有許多好處:

  1. 避免重複開發:
    自己或是有找到已經開發過的系統,我們可以直接引用來使用,
    加快開發的時間。
  2. 專職分工:
    在合作開發上,每個人負責一個獨立的需求的系統,
    由team leader或是專職負責的人來進行整合的工作。
    此時將它做成專案檔或是jar檔就很有用了,
    如果配合上足夠的java doc,對於公司或是開發的同步速度上會有很大的幫助。
  3. 分享:
    就像是工具箱的功能,網路上也有許多神人與強者分享自己所開發的jar檔,
    這真的很偉大,尤其是有許多朋友提供了其中的source code給大家進行更多優化,
    未來我們也應該要多做這樣的事情來貢獻軟體產業才是。
所以,在合法使用的前提之下,我們可以盡量import專案或是jar檔來達到快速達到功能需求的目的。至於我為什麼會一直提到"improt專案和jar檔"呢?

因為目前我知道的有兩種方法可以避免重複創作輪子:
  1. jar檔:
    副檔名為".jar"的單一檔案,其中大多為許多class,為實現特殊需求所創作出來的工具箱,
    之後我們會有個簡單的範例來教學如何import jar檔。
  2. 專案檔:
    我想如果有開發過google map api for android v2的朋友應該會有印象,我們會add一個google所開發的專案作為lib。我們也可以自己製作一個完整的專案來讓其他專案import呢!
順道一提,.jar檔基本上如果沒有擾碼,其安全性是非常低的,
可以使用反組譯的方法來看到其中的source code。
不過如果正常使用的範圍,基本上是不需要這樣操作的,
而通常我們自己做的工具箱不太會需要把它封裝成.jar檔,
大多是設定為"Is Library"來作為讓其他專案引用的接口。

好,說到底,方法很簡單。首先我們來實作怎麼樣import .jar檔案:

  1. 首先,把.jar檔放到"libs"資料夾中,這不是最重要的。
    等等我們會設定參考位置,你也可以放在外部資料夾中,
    這樣的話要注意所放置的位置必須不能改變,要不牽一髮而動全身呀。
  2. 接著看圖片:
    1. 對著專案案右鍵,選擇Properties:
    2. 然後如果你是放在"libs"資料夾中的話,參考位置的設定依照下列步驟:
    3. 如果是放在外面,可以選擇右邊第二個"Add External JARs.."選項,
      選擇外部的檔案來使用。
  3. 如果完成的話,會發現我們可以開始使用.jar檔裡面的class啦!
    至於有件很重要的事情便是:"該怎麼知道裡面有什麼東西?"

    這時我們除了對他反組譯之外,在下載時所看到的doc或是範例就很重要了。除此之外,我們也可以直接用檔案名稱來google,通常也可以找到相關的說明文檔。例如之前分享過的專案(see: 說說,關於文件匯出至Excel檔案中。)可以直接搜尋"jxl.jar",會找到一些相關的資料。
嗯嗯..我還是建議放在"libs"的資料夾中,話說這個資料夾在以前稱之為"lib",在2012年大概六月底左右eclipse的android ADT改版,當時除了create new project的流程大改之外,也改了一些結構的部分,所以如果當時有更新這波流行個開發者們會突然發現之前的project都不能run了,主要就是原本import的jar檔連結斷掉,不過解法其實很簡單,只要稍微改資料夾名稱就好哩。

然後接下來,是開發project透過import來引用的作法。
這我覺得還滿重要的,因為我們常常同時一起開發一個專案,如果我是PM或是team leader,我應該怎麼管理RD的工作方式,以能夠有最好的效率呢?

目前我的解如下:

  1. 將目前規劃的app依照需求功能切分和分類的動作。開始進行功能實現調查的動作。
    1. 功能調查完畢,我們應該會獲得所有功能完整的實作參考,甚至是demo。
  2. 此時便開始分工了,leader開始建構主程式的架構部分,實做出接口。
    1. 其他人同步開始將所負責的功能做成project,並且依照架構提供接口。
    2. 此時會架構出工具箱,提供未來開發需求時使用。
  3. 最後leader或是負責整合的夥伴,依照其他人所提供的project與doc來進行整合的動作,將系統完整建構出來。
  4. 依照該app的case進行最後修改的動作,例如刪除工具箱中不需要的功能,或是依照特殊的需求整理project的function。並且最最後壓力測試。
  5. 可靠度測試完成後,將app上線!
真的是完美的如意算盤。當然如果整個case只有一個人來做的話,這一切就只有一個人來弄了。
其中第2點和第3點的步驟中,我們將project設定成可以給其他夥伴使用的project,關鍵在這裡:
該project的Properties之中,設定為一個library用途的project,
而在main project中,要按下Add來選擇之前所設定的project,示意圖如下:
這和import .jar檔有什麼不一樣呢?
  1. 他是android獨特的import方法,可以用來import android相關的應用,包括了Activity和Service等相關類別。
  2. 因為是直接import編輯檔,所以可以一邊使用一邊檢查/修改所import的project。
    不過要注意,當library project修改過後,main project需要Refresh(按F5)過後才會重新更新鏈結,達到更新的效果。
  3. 系統功能分類會更加明確,可能不同的功能會需要相對應不同的res資料,透過這個方法我們可以將工具箱所需要的res資源分類的更好,但是另外一面來看我們也會發現可能會造成app檔案太大的問題,所以在最後才會需要有所整理的動作。

以上,個人認為import project非常好用。而以上提供兩種方法,希望對大家有所幫助。











2013年3月16日 星期六

說說,檢查Service是正在運行。


首先,我們先來看來自這裡所使用的方法:

public static  boolean isServiceStarted(Context context,String PackageName){
    boolean isStarted =false;

    try{
    int intGetTastCounter = 1000;

    ActivityManager mActivityManager =
        (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);

    List<ActivityManager.RunningServiceInfo> mRunningService = 
        mActivityManager.getRunningServices(intGetTastCounter);

    for (ActivityManager.RunningServiceInfo amService : mRunningService){
        if(0 == amService.service.getPackageName().compareTo(PackageName)){
        isStarted = true;
        break;
        }
    }
    }catch(SecurityException e){
    e.printStackTrace();
    }            

    return isStarted;                
}

這是一個檢測服務是否啟動的方法。

我們試著來分項看看:

  1. 這裡輸入了兩個參數:
    1. context:這是一個Context物件,用於呼叫getSystemService()方法;而getSystemService()方法的使用,會得到一個Object物件,透過轉型來獲得對應的物件,而該物件轉型的參考文件可參閱這裡,我們將要轉成ActivityManager物件。
    2. PackageName:這是用來比目前正在使用的Service物件列表中有沒有相同的package 名稱;當然,在RunningServiceInfo這個物件之中,我們也可以找到getClassName()方法,會返回一個完整路徑(String)。關於RunningServiceInfo的詳細方法,參考這裡。
  2. 然後我們來看看裡面的內容:
    1. 首先會看到我們需要獲取一個服務,將其轉型成為ActivityManager,顧名思義,它是用來查詢系統中目前有註冊(正在運行)的特定內容,包括了getRunningTasks()、getRunningServices()或是getRunningAppProcesses()...等等。
    2. 透過ActivityManager,我們獲得目前正在運行中的服務列表,以List<ActivityManager.RunningServiceInfo>承裝內容。
    3. 透過foreach的方法,一個一個查詢是不是有相同的package name,如果有則break,跳出foreach並且return true值。
    4. 這裡面嘗試處理了SecurityException,當查找失敗的時候會拋出這個錯誤。
最後我實作了一個簡單的App
會列出現在系統正在運行的Service的class 完整名稱,下載在這裡。

















2013年2月1日 星期五

筆記,訪問網路


做一個簡短筆記:

private void setStrictMode(){
        // 详见StrictMode文档
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectDiskReads()
                .detectDiskWrites()
                .detectNetwork()   // or .detectAll() for all detectable problems
                .penaltyLog()
                .build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .detectLeakedSqlLiteObjects()
                .detectLeakedClosableObjects()
                .penaltyLog()
                .penaltyDeath()
                .build());
    }

以上是一個讓主線程能夠訪問網路的方法,在4.0之後訪問網路在主線程之中會產生NetworkOnMainThreadException,必須另開新的線程執行,以防止ANR的異常,或是使用StrictMode物件能夠解決這個問題。

參考這裡、這裡以及這裡。

先記著,有空補充。

後來發現這應該是一個讓系統進入debug模式的方法,怎麼著也是一個不對勁的方法,
正確的方法建議還是使用新的thread來操作,可以參考非同步資料交換的方法,在處理上會比較安全。



















2013年1月12日 星期六

說說。非關程式,一些開發心得。

這篇文章稍微來一點感性。

轉眼間碩班就要準備畢業了,今天和指導教授討論論文的事情,在寒假開始的時候整個就必須要火力全開衝畢業呀。

而必須要想盡辦法把自己所學都記錄起來。一來是分享,分享才能變強;二來是讓這個世界看到自己的進步與能力,可以的話希望畢業之後能夠順利找到研發替代役,自己所學是在終端行動裝置上開發應用程式,變化速度之快,這幾年深深體會,是真的很害怕中途中斷呀!


好了,感性結束。

說到一些開發的心得,我想網路上有非常多,包括了反破解、獲利...等等,我想也許我應該也要把我的心得想辦法挖出來重新整理過一遍,做為未來對自己的提醒、以及分享給大家自己的拙見。

我想在開始之前,我們來將使用體驗模組化稱為一種「功能」,等會敘述的功能皆為使用體驗上我們要注意、或是已經常見、或是個人的見解。

而自己也有些App上架(一定要宣傳一下的啦),其中我將上面已經有實作的,覺得是不錯的體驗,而在網路上也滿常見的做列點:

  1. 離開程式時確定的dialog:
    在離開時產生一個提示是否離開的對話視窗。因為在android的裝置中,越來越少有實體的return按鈕,當手機設計不良,使用者很容易不小心按到return按鍵。注意return和home鍵實質上意義是不同的,當然我們也能夠設計成為兩個有一樣的效果,但是像這個App,曾經就發生過,小書在lab玩到一半老師走進來討論事情,結果心虛按下home鍵之後就會造成重新啟動的慘劇。重點是我沒儲存啊!!!!

  2. 做即時的儲存:
    有很多方法可以做即時的儲存,目的除了常見的偏好設定之外,也有像是偏好之外的「設定」,像是app中上次離開的頁面、或是地圖app中離開前的位置等等,這些隱藏在偏好設定中的設定,可以花點心思設計,不失為提供一個不錯的體驗。

  3. 對硬體調用時先做檢查:
    舉例像是GPS、網路、SD卡等等,我們在app功能調用時先做一個檢查,可以防止當使用者關閉了權限、或是沒有安裝SD卡等情況時造成app的異常崩潰;更重要的是防止app在這些錯誤的使用流程之下,造成app的ANR或是產生非預期的使用流程。防呆是很重要的,我們必須要減少更多使用者可能會發生的錯誤,所以必須要多花一步進行檢查。

  4. 對網路服務、android系統的服務進行防呆:
    這兩件事情分類成一項的原因是因為他們都是軟體的部分。對於網路服務,像是dropBox的服務,如果中途下載上傳失敗,我們應該採取什麼動作較好?是Toast?Notification?還是Dialog?我想這是一個可以根據case不同而有的動作,目的在於讓使用者能夠發現,並且採取正確的使用流程;對於android系統中提供的服務,像是螢幕的旋轉、按鈕的反應、UI的回饋等等,我們有幾個原則:

    1. 明確的。有明確的回饋,讓使用者知道目前所做的動作為何。
    2. 熟悉的。回饋是熟悉的,目前自己還沒有辦法大到能夠教育使用者的影響力之前,我們必須要先試著以熟悉的回饋讓使用者能夠快速而且直覺的上手。當然也能夠反其道而行,增加產品忠誠度,相信很多手機硬體開發商在UI的sense上都對這很有著墨,也會造成我們在App開發時美工的困難,這就變成了我們要想辦法克服的地方了。
    3. 不影響使用流程。我認為,除非我們的目的在於讓使用者創造/作,否則一切盡量是在控制範圍內的,避免設計太多條使用流程、也避免讓使用者跳出使用流程之外。像是螢幕的旋轉就是一個例子,default的狀態下螢幕旋轉會造成activity整個destroy再重啟,如果我們不想要鎖定螢幕轉向,那麼就必須要考慮到這點並且解決。

      順道一提,關於螢幕轉向,像是有些pad的設計上,插上鍵盤會自動變成橫向,或是可能會自動鎖定為橫向,這點在處理螢幕轉向時會是一個還滿有趣的挑戰。

  5. 不同裝置螢幕尺寸的處理:
    這真的是一個難解的問題啊,我們對於尺寸能夠分成橫向與直向的畫面:在直向的部分處理,除了不同的解析度設計不同的layout之外,也能夠利用在設計畫面的階段,讓畫面以垂直的視覺動線為原則來處理;在橫向的處理上,會必須要考慮到螢幕大像素高的手機,螢幕可能會造成元件顯示過小,螢幕小像素高的手機可能會造成尺寸比例過大,在icon的繪製上就必須要產生適當的大小,以及在layout的設計上要考慮到剛尺寸比例變化時上述可能會有的問題。所幸這些我們可以透過模擬器或是layout時預覽顯示的比例來處理,自己的美工能力還在學習中,如果有更多方法也還希望不吝提供。這裡有個很有趣的例子,在處理橫向與直向的畫面上我覺得表現得很棒,是一個可以參考的做法。
以上,接著我必須要列出一些點,是自己以前有碰過的,未來可能會遇到的,必須要提醒自己的,關於技術上或是使用體驗上可能會發生的問題,也分享給各位:
  1. 手在螢幕上的安排:
    一直覺得這是非常有趣的議題,假如我們開發以右撇子為主,就自己的觀察,在年輕的使用者會習慣以單手拿著手機(注意不是Pad),用大拇指進行各種手勢操作,所以如果我們在這個畫面希望導引使用者按下某個按鈕,它就應該被擺在右下角或是右上角的位置;而如果是較為年長的使用者,會習慣用一手拿著手機,另一手操作,如果是右手拿著手機,右下角可能會被擋住,所以在我們希望導引的按鈕安排上,右上角會是一個不錯的選項。這樣的設計在iphone的app上還滿常見的,我想..這應該沒有申請專利吧....
    先不管專利問題,我們還可以發現像是android的action bar,在menu按鈕便是作在右上角,我們可以試想menu按鈕與activity layout上的按鈕有什麼區別,便能夠適當的安排功能與使用者的親密關係該如何配置。

  2. Service被異常關閉的回饋:
    Service指的是在android的背景服務機制。會有使用者安裝一鍵清除程序的軟體,造成service會被刪除,網路上也有很多關於保持service不被刪除或是相關的處理方法,不過關於當service被刪除或是有某些異常時,我們也許會需要給予使用者適當的回饋,像是上面提到的網路服務便常常利用service來執行:上傳與下載失敗時,也可能不見得是因為程序清除的軟體造成,也有可能會是中途使用者將網路關閉、或是進入了電梯之中。這些都是不可預期的錯誤,我們除了UI的回饋之外,也必須要在背景的執行程序中考慮到回饋的部分。

  3. 使用者與市場:
    我們指的不是市場的獲利模式,而是對於我們的專案所鎖定的使用者,真正的需求以及可能會有反應必須要了解,也必須要考慮到在使用這個app的時候,該使用者可能會想到以及需要的服務。幸運的是在上研究所時便一直被提醒的,因為自己做的是科技輔具的開發,了解使用者,了解市場的重要性在很久以前便發現了,重點還是在實作。實作了解市場反而會花費更多的時間,除了自身的生活經驗之外,透過體驗與深刻觀察是很重要的。而且,關於對於市場的了解,個人認為這很難利用專業分工來完美的實現,如果設計者無法深刻的觀察與體會,往往設計的產品功能會少掉一些貼心的idea,或是缺乏好的使用流程造成讓同類型產品被進攻的機會。

  4. 擴充、整合、維護:
    我認為能夠快速寫出一個程式的人是很厲害的,但是如果能夠長久的維護一個程式並且能夠持續保持程式在市場上功能的更新更厲害,所幸在進入研究所以前便開始碰觸到OOP的領域,不斷增強這方面的能力能夠幫助自己在模組化功能的轉移以及程序的維護上有很高的機動性。除此之外,我們還必須要考慮在擴充app的功能上的策略,其中包括了時間點以及適切性:像是之前天橋科技的家銘學長在一場演講時提過,MoneyCare在ios的宣傳策略上便結合了功能的發展與appStroe的推廣策略。在開發主功能與主要的相關功能上市之後,能夠建立時間表讓其他附加功能的開發在時間的管理之下保持更新,這樣便能夠結合了宣傳策略與維護的步驟,達到最佳的獲利與使用體驗。


    我想這屬於專案管理的領域,偏偏如果是獨立開發者或是小公司的話,這也是我們必須要努力學習的。題外話,我們也預祝天橋科技的MoneyCare在android上能夠大賣。
  5. 美工:
    指導教授一直想要慫恿我們交設計學院的女朋友,偏偏阿宅如我,維持二次元的遠距離戀愛還是目前比較愜意的感情生活。美工部分在android開發上不如ios,雖然現在已經有許多不錯的icon和drawable藏在sdk的資料夾中,但是美工重點還是在整體畫面的協調與精美的感受,除了仰賴專業分工之外,也許我們能夠在除了工作之外提升生活之中的美學素養會是一個較為能夠解決問題的方法,目前的策略是:icon還是以android default的元件為主,而盡力以自己的認知透過排版與使用上習慣的體驗來進行設計;我認為專業的美工人員未必會比RD還要更懂得畫面的協調,因為我們也許碰的app比較多,也許也會比美工來得更懂得使用者,所以如果有幸合作開發中,美工部分是必須是要有平等而且密集的討論與修改的。當然,如果在專業分工下,能夠有專門的系統分析架構人員來主持,也許會有更好更快的工作效率。

以上,想到再更新。














2013年1月8日 星期二

筆記,關於android找不到裝置。


這張照片拍得還不錯,不過跟主題無關。

常常在用eclipse跑實機的測試時會出現安裝失敗的訊息:

[2013-01-08 01:44:35 - MoneyCare_1_0_3] Failed to install xxxxxxxx_xxx_xxx_x.apk on device 'HT24LW103531!
[2013-01-08 01:44:35 - MoneyCare_1_0_3] (null)
[2013-01-08 01:44:40 - MoneyCare_1_0_3] Failed to install xxxxxxxx_xxx_xxx_x.apk on device 'HT24LW103531': null
[2013-01-08 01:44:40 - MoneyCare_1_0_3] com.android.ddmlib.InstallException
[2013-01-08 01:44:40 - MoneyCare_1_0_3] Launch canceled!

或是timeout:

[2013-01-08 01:56:52 - MoneyCare_1_0_3] Failed to install xxxxxxxx_xxx_xxx_x.apk on device 'HT24LW103531': timeout
[2013-01-08 01:56:52 - MoneyCare_1_0_3] Launch canceled!

或是連結失敗:

[2013-01-08 02:19:31 - MoneyCare_1_0_3] Failed to install MoneyCare_1_0_3.apk on device 'HT24LW103531': Connection refused: connect
[2013-01-08 02:19:31 - MoneyCare_1_0_3] java.net.ConnectException: Connection refused: connect
[2013-01-08 02:19:31 - MoneyCare_1_0_3] Launch canceled!


導致安裝app不成功,怎麼辦呢?

根據小書的經驗,讓我把成功的經驗記錄下來。

我們可以循序採取下列步驟:
  1. 拔除手機線,再試一次。
  2. 如果在DDMS的Devices裡面沒有裝置,可以等一下,他可能在讀取;或是再試一次。
  3. 如果找到裝置了但是還是安裝失敗:
    1. 拔除手機線&關閉eclipse
    2. 開啟電腦中啟動工作管理員
    3. 再"處理程序"中會找到"adb.exe"或是"adb.exe *32"的程序,結束它。
    4. 再重啟eclipse&&裝上裝置。
我想第三個方法應該已經是大絕招了,如果上面都失敗...
請檢查手機中有沒有設定"允許未知來源"的安裝權限吧!

話說,先檢查"允許未知來源"才是第一個步驟。
















2012年12月28日 星期五

說說,FullScreenActivity是android給的新模板。不過重點是PackageManager的操作。


有點驚豔的,為了玩App to SD的實現所以創了新專案,發現原來android套件升級還多了幾個新模板,有設定用的,有登入用的,也有全螢幕的。

於是好奇的小書便拿了其中一個來玩,而因為這個project的性質,所以我拿全螢幕的來玩,也就是傳說中的FullscreenActivity。


而我有實做了一個app,但是其實我是想要做app to SD的,所以一切都怪在自己太愛玩了,本來信誓旦旦說一個晚上就可以把app to SD搞定,但是搞了這個全螢幕很炫麗的activity又弄了一段時間。


那就先來稍微介紹一下這個FullscreenActivity吧,有錯請鞭小力一點:

首先,他的畫面很炫,一開始他長這樣的,就是一個textView。
但是當你點一下螢幕的時候,你會發現它會跳出action bar 和一個"假的"Button,
跳出的動作是用動畫來操作的,不過這樣應該會有相容性的問題,於是我們會發現他在動畫產生的設計上有下些功夫,就是做了sdk版本的判斷。
版本的判斷是個大工程呀,像是畫面中下面Button的部分,如果你也創了一個同樣的project,會發現在main activity中,大約在Line 71的地方可以看見有版本來做動畫的產生與否判斷,而他還另外做了一個.util的package,其中包含了一個abstract class SystemUiHider和兩個分別給不同版本用的class extends SystemUiHider,而這個.util的package是幹嘛的呢?適用來處理action bar的,其中也有用到了interface 來實作action bar的開關動畫(mOnVisibilityChangeListener)。

然後,架構上我們可以觀察到,他有一個package,裡面有一個抽象類別,兩個繼承它(抽象類別)實作的class,用來處理action bar的hide和show;有兩個Interface,分別負責監聽當action bar與Dummy Button show與hide時的動畫;控制項是main activity(FullscreenActivity),activity裡面必須要實作處理Dummy Button 控制與動畫的interface(這個interface也對action bar做控制),至於另外一個interface在default的package裡面就做掉了。

在控制項中,android很貼心對使用流程做了一些設計,像是handler的postDelayed(),當產生action bar 和Dummy Button時候,會設計有自動消失的動作,以及當點擊了Dummy Button時候,他會延長(操作流程上便是刪除舊的runnable,再下新的postDelayed指令)顯示的畫面,我們也可以由AUTO_HIDE_DELAY_MILLIS來設定延長的時間,以及可以用AUTO_HIDE來設定是否要自動隱藏。

有機會可以來學學這怎麼寫的,整個很炫呀~~~
可是這不是重點,重點是我想試著實驗PackageManager的操作。

這是我做的App,參考來自這裡,和這裡。

在這個App中,主要實作了兩件事情:

  1. 能夠產生一個有簡單apk資訊的List,做些簡單過濾;點擊之後會跳轉至資訊的頁面。
  2. 對FullscreenActivity的模板做了小幅的修改,讓他可以在顯示資料之後,不會再有Dummy Button跳出來,action bar也只會跳出來一次。
送上精美套圖:



主要的方法可以參考這裡,和這裡,連結跟剛剛的一樣呵呵。

至於FullscreenActivity的修改,可以參考main Activity裡面Line 257到Line 310的程式碼,主要是複製了一份自動隱藏的方法稍作修改,以及在監聽事件中處理。


話說這份文章還蠻詳細的,就是剛剛參考的第一個連結(出處:http://blog.csdn.net/qinjuning),有機會可以再來試試看怎麼抓取更詳細的訊息。












2012年11月18日 星期日

說說,App上傳筆記。調用DialogFragment的方法。

宣傳一下,因為最近辦了visa卡,所以終於可以開辦google play帳戶了,
上傳以前一個用來尋找北方的app,修改為eclipse更新過後格式,並且加上也可以尋找天頂的功能,我想對於觀星族或是有定位需求的人們應該會很方便。

下載網址: 點擊我。
QR Code:

後來發現上傳app到google play上面其實滿容易的,以下重點摘要:

首先,我們必須要有個開發者帳號,如何申請?請參考這裡或是這裡也有詳細的教學。

  1. 最重要的是我們必須要先把apk弄成可以上傳的格式,我們先看這張圖:

    首先對著專案點選右鍵,
    會在androidTools中會找到Export Signed Application Package的選項,
    點選之後會需要一個keyStore輸入,其中會提示需要創立新的還是使用舊的,
    如果沒有創立過的朋友,建議就弄個新的唄。記得創立的密碼要記住。
    都輸入完畢之後,最後會選擇匯出的位置,把這個位置記起來。
  2. 進入開發人員的後台,選定剛剛那個位置的apk上傳。
    這裡要注意,不是專案裡面bin資料夾的那個apk喔!(要不會不給上傳)
  3. 你也有可能會遇到他說會有未知的錯誤,要我們稍等,這好像很多人遇過,
    直接重新登入,或是等一下即可。
  4. 到這裡結束了嗎?錯!!
    上架之前還有一堆設定要做,包括要給高解析度應用程式圖示,以及如果有宣傳文字就必須要有宣傳圖片等等,最重要的還有各國的翻譯(當然是可以用google翻譯啦..)
  5. 高解析度應用程式圖示格式如下:
    512 x 512
    32 位元 PNG 或 JPEG
    大小上限:1024 KB

    我被他搞很久...
  6. 當設定完成之後,按下"發布",系統會自動檢查有那裡沒有填好的。
    確定都可以準備發布之前,要注意的,
    在apk上傳的頁面那邊,要記得啟用我們上傳的apk,
    因為之後版本更新的動作可以用來配合發布的版本管理動作。
    所以這個動作千萬不要忘記哩!
  7. 發布之後,不會馬上搜尋到這個app,
    大概是在做系統檢查吧?大概要等幾個小時之後才會有內容出現。

以上。



接著我們來放個最近在玩的Fragment,我快被他搞死了!哈哈!

這是DialogFragment,參考Develop的作法實作的一個,
按下返回按鍵即會跳處確定離開的詢問dialog。

首先我們必須架構一個DialogFragment.java檔,其中有些string.xml的內容可自行發揮,我便不放上來,然後在點擊事件中,會與Activity互動,這邊先不用急著除錯,等等在Acrivity中實作哩:

package susan.idea.compass_north_v001;

import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;

public class EndMessageFragment extends DialogFragment implements
        android.content.DialogInterface.OnClickListener {

    /**
     * 初始化這個DialogFragment
     * 
     * @param tagMap
     *            必須是一個String[2][n]的形式包裝的Array 作為傳入參數(HashMap形式)用途.
     * @return 返回一個 DialogFragment.
     */
    public static EndMessageFragment newInstance(String[]... tagMap) {
        EndMessageFragment frag = new EndMessageFragment();
        Bundle args = new Bundle();
        if (tagMap != null && tagMap.length == 2 && tagMap[0].length > 0) {// 檢查有無符合格式
            for (int n = 0; n < tagMap[0].length; n++) {
                args.putString(tagMap[0][n], tagMap[1][n]);
                frag.setArguments(args);
            }
        }
        return frag;
    }

    @Override
    public void dismiss() {
        // TODO Auto-generated method stub
        super.dismiss();
    }

    @Override
    public void show(FragmentManager manager, String tag) {
        // TODO Auto-generated method stub
        super.show(manager, tag);
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        // TODO Auto-generated method stub
        super.onCancel(dialog);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Builder mBuilder = new Builder(getActivity());
        mBuilder.setTitle(R.string.dialog_title);
        mBuilder.setMessage(R.string.msg_exit);
        mBuilder.setPositiveButton(android.R.string.ok, this);
        mBuilder.setNegativeButton(android.R.string.cancel, this);

        return mBuilder.create();
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        switch (which) {
        case DialogInterface.BUTTON_POSITIVE:
            ((MainActivity)getActivity()).doPositiveClick();
            break;
        case DialogInterface.BUTTON_NEGATIVE:
            ((MainActivity)getActivity()).doNegativeClick();
            break;
        default:
            break;
        }

    }

}


接著,我們要在Activity中調用,必須要注意的是,因為我們使用Fragment,
所以相對也要使用FragmentActivity(如果有錯或是有其他方法請指正,感恩)。

把default的Activity改成FragmentActivity其實很簡單,就直接改名字就好,因為方法都一樣(繼承來的著唄!),改完之後把import錯誤的內容改一改就不會有error message了。

因為我是設定在按下返回按鍵之後,所以程式碼寫在onBackPressed()之中:

@Override
    public void onBackPressed() {
        showDialog("dialog",EndMessageFragment.newInstance());
        // super.onBackPressed();
    }

    /** 顯示DialogFragment 注意需要運行在FragmentActivity環境下 */
    void showDialog(final String DIALOG_TAG,DialogFragment mFragment) {

        // DialogFragment.show() will take care of adding the fragment
        // in a transaction. We also want to remove any currently showing
        // dialog, so make our own transaction and take care of that here.
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        Fragment prev = getSupportFragmentManager().findFragmentByTag(
                DIALOG_TAG);
        if (prev != null) {
            ft.remove(prev);
        }
        ft.addToBackStack(null);

        // Create and show the dialog.
        
        mFragment.show(ft, DIALOG_TAG);
    }

    public void doPositiveClick() {
        finish();
    }

    public void doNegativeClick() {

    }

這裡實做了三個方法,一個是避免dialog一直重複跳出(開關的方法),另外兩個是dialog的點擊事件。自己的經驗是,把點擊事件寫在fragment裡面其實也沒差,不過官方的文檔既然都這樣寫了,我們就如實照辦啦~

以上。下台一鞠躬,


2012年6月25日 星期一

方法,關於android中,字型的設定。


關於android設定字型的問題,我們必須透過Typeface類別來實做,簡單的操作方法參考這裡。 Typeface類別 吃.ttf檔案,也就windows在控制台中的字型檔,怎麼做呢?

照例,我們做了一個Demo:


view:
打開spinner選取字型

選擇字型之後,下方會顯示字型名稱以及該樣式。


這個方法結合Spinner和從assets獲取資料名稱的動作,完成將選擇的.ttf檔(名稱、屬性)置換到textView之中,我丟了一堆阿哩阿雜的.ttf檔在assets/fonts資料夾中,如果想要測試.ttf的字型,就把檔案丟到assets的fonts資料夾中吧!

而我把使用方法都寫在註釋當中了,以下,這是主要的activity:

package fontTest.susan.idea;

import java.io.IOException;

import android.app.Activity;
import android.content.res.AssetManager;
import android.graphics.Typeface;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

public class FontTestActivity extends Activity {


    TextView textView;//顯示字型名稱,字型。
    Spinner spinner;//字型的列表

    String[] mStringArray;//列表的容器

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        textView = (TextView) findViewById(R.id.textView);
        spinner = (Spinner) findViewById(R.id.spinner);
        
        try {
            mStringArray = getAssets().list("fonts");//獲取所有字型的名稱。
            //從assets的fonts資料夾中獲取檔案列表。
        } catch (IOException e) {
            e.printStackTrace();
        }
        spinner.setAdapter(new ArrayAdapter<String>(this,
                android.R.layout.simple_spinner_dropdown_item, mStringArray));//設定adapter
        
        spinner.setOnItemSelectedListener(new OnItemSelectedListener(){

            @Override
            public void onItemSelected(AdapterView<?> parent, View view,
                    int position, long id) {
                setTypeface(getAssets(),mStringArray[position]);//當選擇了之後進行設定字型
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                setTypeface(getAssets(),mStringArray[0]);
            }});
//        setTypeface(getAssets(),mStringArray[0]);//不會用到
        spinner.setSelection(0);//設定選擇的初始值,該內容會通過OnItemSelectedListener
    }

    /**設定字型與顯示字型名稱*/
    private void setTypeface(AssetManager mAssetManager, String name) {
        Typeface face = Typeface
                .createFromAsset(mAssetManager, "fonts/" + name);//從assets獲取字型
        textView.setTypeface(face);//設定字型
        textView.setText(name);//顯示字型名稱
    }

}

而layout非常的簡單:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <Spinner
        android:id="@+id/spinner"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/spinner"
        android:layout_centerHorizontal="true"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

嗯嗯,就是這樣。

2012年6月16日 星期六

方法,XMPP的Demo

這是最近念念不忘了的XMPP,想要試著利用這來多點應用。

關於XMPP的介紹,不多說,參考這裡。

這裡有比較詳細的介紹,還滿詳細的。

然後實做了一個App,是綁定Google Talk進行聊天用的:
這是載點。

發現了幾點:

  1. 不能夠和自己聊天。也就是兩台機器登入同樣的帳號沒辦法互相傳遞訊息。
    這真的很殘念呀!
    通常用戶都會安裝google相關App,如果要透過XMPP頻繁的傳送data的話,
    登入自己的帳號,這樣傳送會一直有默認的通知產生,很麻煩的。
    而且,如果登入自己的帳號,互傳時必須要與傳送方加入google talk的通訊錄。
  2. 如果我想要頻繁傳送data又必須解決通訊錄名單的問題的話,也許我可以自己創造一個server,似乎可以用Openfire?相關設定參考這裡。載點在這裡。


參考的API,在這裡。
而其中lib和相關server的載點,參考這裡。

這次我比較用心,裡面有較為詳細的註解,請笑納:

package demoXMPP.demo.idea;

import java.util.ArrayList;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;

import android.app.Activity;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

public class XMPPClient extends Activity implements MessageListener {

    private ArrayList<String> messages;// 文字暫存的列表
    private Handler mHandler = new Handler();
    private EditText mRecipient;// 傳送文字對象的帳號
    private EditText mSendText;// 傳送文字的內容
    private ListView mList;// 顯示文字的列表
    private XMPPConnection connection;// XMPP的連接物件
    private ArrayAdapter<String> adapter;// ListView的adapter

    /**
     * Called with the activity is first created.
     * 簡單操作介紹:http://www.cnblogs.com/zhangdongzi/archive/2012/01/11/2320021.html
     */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);
        // --初始化---
        messages = new ArrayList<String>();
        mRecipient = (EditText) this.findViewById(R.id.recipient);
        mSendText = (EditText) this.findViewById(R.id.sendText);
        mList = (ListView) this.findViewById(R.id.listMessages);
        setOrRefreshListAdapter();
        Button send = (Button) this.findViewById(R.id.send);
        // --初始化---
        send.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                // 按下傳送,讀取文字,讀取傳送對象,送出文字
                StringBuffer temp = new StringBuffer();
                temp.append(mRecipient.getText().toString());
                if (temp.indexOf("@") == -1) {
                    temp.append("@gmail.com");
                }// 因為讀取對象需要完整mail帳號,如果只輸入帳號則添加後面的@...
                String to = temp.toString();
                String text = mSendText.getText().toString();
                Message msg = new Message(to, Message.Type.chat);// 產生送出文件
                msg.setBody(text);// 設定文字內容
                connection.sendPacket(msg);// 送出文件
                
                messages.add(StringUtils.parseBareAddress(connection.getUser()) + " say: ");
                messages.add(text);// 準備本機在ListView的顯示內容。
                setOrRefreshListAdapter();// 更新列表
            }
        });
        setting();// 初始化設定登入帳號密碼
    }

    private void setOrRefreshListAdapter() {
        //如果是第一次則進行設定,之後則使用更新列表的指令。
        if (adapter == null) {
            adapter = new ArrayAdapter<String>(this,
                    R.layout.multi_line_list_item, messages);
            mList.setAdapter(adapter);
        } else
            adapter.notifyDataSetChanged();//更新列表

    }

    @Override
    public void processMessage(Chat chat, Message message) {
        // 處理接收到的值,將收到訊息更新在ListView上面
        Log.d("Test", "processMessage");
        if (message.getBody() != null) {
            String fromName = StringUtils.parseBareAddress(message.getFrom());//獲得傳送者的完整mail帳號
            messages.add(fromName + " said: ");
            messages.add(message.getBody());//添加訊息
            mHandler.post(new Runnable() {
                public void run() {
                    setOrRefreshListAdapter();//更新列表
                }
            });
        }
    }

    private void setting() {
        //產生對話視窗,需要輸入登入的各項資訊。
        Builder builder = new Builder(this);
        final View view = ((LayoutInflater) this
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
                R.layout.settings, null);
        builder.setView(view);
        builder.setPositiveButton(R.string.ok, new OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                String host = ((EditText) view.findViewById(R.id.host))
                        .getText().toString();// 獲取google talk host
                String port = ((EditText) view.findViewById(R.id.port))
                        .getText().toString();// 獲取google talk port
                String service = ((EditText) view.findViewById(R.id.service))
                        .getText().toString();// 獲取google talk service
                String username = ((EditText) view.findViewById(R.id.userid))
                        .getText().toString();// 獲取使用者的帳號
                String password = ((EditText) view.findViewById(R.id.password))
                        .getText().toString();// 獲取使用者的密碼
                if (username.indexOf("@") == -1) {
                    username = username + "@gmail.com";
                }// 在這邊只需要輸入帳號,會自動添加後面的@...

                ConnectionConfiguration connConfig = new ConnectionConfiguration(
                        host, Integer.parseInt(port), service);// 創立連結設定檔
                connection = new XMPPConnection(connConfig);// 創立連結

                try {
                    connection.connect();// 進行連結
                } catch (XMPPException ex) {
                    Log.e("Test", "XMPPException 錯誤:" + ex);
                }
                if (connection.isConnected()) {
                    //雖然說服務相關內容已經寫上去了,但是做個確保。如果出錯了會要求重新輸入
                    try {
                        connection.login(username, password);// 登入
                        // 將狀態設定為登入狀態
                        Presence presence = new Presence(
                                Presence.Type.available);
                        connection.sendPacket(presence);
                    } catch (XMPPException ex) {
                        Log.e("Test", "XMPPException 錯誤:" + ex);
                    }
                    if (connection.isAuthenticated()) {
                        connection.getChatManager().addChatListener(//設定監聽訊息
                                new ChatManagerListener() {
                                    @Override
                                    public void chatCreated(Chat chat,
                                            boolean createdLocally) {
                                        if (!createdLocally) {
                                            chat.addMessageListener(XMPPClient.this);//設置監聽實做
                                        }
                                    }
                                });
                    } else
                        failMsg();
                } else
                    failMsg();
            }
        });
        builder.show();
    }

    //錯誤訊息實做
    private void failMsg() {
        Toast.makeText(getApplicationContext(), R.string.not_connection,
                Toast.LENGTH_LONG).show();
        setting();
    }
}







2012年6月13日 星期三

方法。判斷封閉區域內外。InputType切換。


為了考賞自己所以買了新背包,超開心。

然後該工作的還是要工作,最近實做了一個試著判斷封閉區域內外的程式,詳細說明如下:

本來還有更高畫質的圖檔的,可惡Google竟然不給我傳。
然後傳完赫然發現第三個點點位置跑掉了!(大驚)


主要的演算法參考這裡(備份在這裡):

/**
     * 這是主要的演算法。
     * 
     * @param p
     *            p點,待測點。
     * @param polygon
     *            依序點集合所產生的封閉區間。
     * @param mStringBuffer
     *            將演算法的資訊顯示出來所需要的文字容器
     * @return true is inside;false is outside
     */
    private boolean InsidePolygon(Vector<GeoPoint> polygon, GeoPoint p,
            StringBuffer mStringBuffer) {
        // ref: http://paulbourke.net/geometry/insidepoly/
        int counter = 0;
        int i;
        double xinters;
        GeoPoint p1, p2;

        p1 = polygon.get(0);
        for (i = 1; i <= polygon.size(); i++) {
            p2 = polygon.get(i % polygon.size());
            if (p.getLatitudeE6() > Math.min(p1.getLatitudeE6(),
                    p2.getLatitudeE6())) {
                if (p.getLatitudeE6() <= Math.max(p1.getLatitudeE6(),
                        p2.getLatitudeE6())) {
                    if (p.getLongitudeE6() <= Math.max(p1.getLongitudeE6(),
                            p2.getLongitudeE6())) {
                        if (p1.getLatitudeE6() != p2.getLatitudeE6()) {
                            xinters = (p.getLatitudeE6() - p1.getLatitudeE6())
                                    * (p2.getLongitudeE6() - p1
                                            .getLongitudeE6())
                                    / (p2.getLatitudeE6() - p1.getLatitudeE6())
                                    + p1.getLongitudeE6();
                            if (p1.getLongitudeE6() == p2.getLongitudeE6()
                                    || p.getLongitudeE6() <= xinters)
                                counter++;
                        }
                    }
                }
            }
            p1 = p2;
        }

        mStringBuffer.append("\n count is: " + counter);
        if (counter % 2 == 0) {
            return (false);
        } else {
            return (true);
        }
    }
功能很簡單,就是只要在狀態2底下我們點擊地圖,落點在紅色範圍內就是圈內,落點在紅色範圍外就是圈外,判斷為圈內就會有特別的標示和說明。
然後,還有一個也是功能很簡單的程式,就是把InputType類別給全部列出來讓我們調用,
畫面如下:


然後程式碼很蠢,所以就不貼上來分享了。
要注意的,有些type有版本的限制,所以可能會無效,請參考官網說明,
可以試著把Filter by API Level上下調看看就會發現嘍!

不嫌棄的話載點在這裡。


















追蹤者