2012年11月28日 星期三

[Android] 如何正確地停止正在執行中的AsyncTask?

 在上篇文章中,我們提到了如何正確安全地停止Java執行緒的方法,接下來不得不提的,就是在 Android API 中用以簡化執行緒工作的AsyncTask類別的停止方法

 在 Android 的開發中經常會使用到執行緒來進行長時間的工作,這些工作絕對不能放在 UI thread 裡(也就是負責畫面繪製與處理使用者互動的那個執行緒),否則一個按鈕按下去,UI thread 開始去進行那些所謂長時間的工作,後果就是整個畫面看起來「當掉了」,所以切記,在進行 Android 程式的開發時,絕對要依靠 working thread 或AsyncTask來進行那些工作。總之AsyncTask存在的意義與一般用法很多地方都有談了,在此就不再贅述。

 要停止一個已開始執行的AsyncTask,主要要透過它的cancel()方法,一個被呼叫過cancel()方法的AsyncTask它之後的isCancelled()必然會回傳true值。因此,我們就可以以類似 [Java] 如何安全且優雅地停止Java執行緒?一文的方法,將迴圈中的旗標值判斷替換為isCancel()回傳值的判斷,來決定doInBackground()中的工作是否要繼續執行。同樣的,這一樣會遇到工作不會立即停止的狀況,此時我們可以在呼叫cancel()方法時將其參數設為true,這即代表不但之後isCancel()的回傳值要變成true,而且立即對此AsyncTask發出InterruptedException,好馬上改變doInBackground()的程式流程,來完成停止執行的目的。

 與前述該文中程式碼效果相同的程式碼,請見下述所示:

public class TestTask extends AsyncTask<Void, Void, Void> 
{
    @Override
    public void doInBackground(Void... values)

    {
        Log.i(TAG, "TestTask starts");
        while(!
isCancelled()

        {
            try

            {
                //do something

                Log.i(TAG, "Sleeping...");
                Thread.sleep(5000);
            }

            catch(InterruptedException e) 
            {
                
Log.i(TAG, "TestTask was inturrupted");

            }
            catch(Exception e) {
                //handle error
                Log.e(TAG, e.toString(), e);
            }   
        }
        
Log.i(TAG, "TestTask ends");
    }
}
public class SomeActivity extends Activity 
{
    ....    public void someMethod()
    {
        TestTask task = new TestTask();
        task.execute();
        // wait for a minute
        task.cancel(true);
    }
}

2012年11月27日 星期二

[Java] 如何安全且優雅地停止Java執行緒?

 Java執行緒的使用上最常被問到的一個問題就是:「我該如何停止執行緒的執行?Thread.stop()方法已被標為棄用了,我該怎麼做?

 最簡單的方法是:透過一個boolean旗標值與Thread.interrupt()方法的搭配來完成,請見下列程式碼:

public class TestThread implements Runnable 
{
    private boolean isRunning = true;


    @Override
    public void run() 

    {
        System.out.println("Thread starts");
        while(
isRunning

        {
            try

            {
                //do something

                System.out.println("Sleeping...");
                Thread.sleep(5000);
            }

            catch(InterruptedException e) 
            {
                System.out.println("Thread was inturrupted");
            }

            catch(Exception e) {
                //handle error
                e.printStackTrace();
            }   
        }
        System.out.println("Thread ends");
    }
 
    public void stopThread() 

    {
        this.isRunning = false;
    }

    public static void main(String args[]) throws  InterruptedException 

    {
        TestThread task = new TestThread();
        Thread t = new Thread(task);
        t.start();
        System.out.println("Main thread Sleeping...");
        Thread.sleep(2000);


        //stop in this manner
        task.stopThread();
        t.interrupt();
    }
}


 在上述程式碼中,執行緒中的迴圈工作是否要持續執行是依靠TestThread中的isRunning布林值而定的;也就是說,我們只要在TestThread中加入一個將isRunning的值變為false的方法便可控制TestThread是否繼續執行

 但問題出現了,即便我們透過改變isRunning值來阻止TestThread的下次執行,但執行續依舊會完成這次迴圈的工作執行,直到下次迴圈判斷時才真的停止。因此,除了呼叫stopThread()方法改變旗標職外,我們還要呼叫該執行緒的interrupt()方法,讓該執行緒丟出InterruptedException好脫離原本的執行流程,順利進入下次迴圈判斷;透過這兩個工具,我們即可達成不透過stop()方法亦能安全停止執行緒的目的了:)

2012年11月20日 星期二

[Android] 在Android中要進行HTTP網路連線時,該使用HttpClient還是HttpURLConnection?

 相信開發Android的人都知道,在進行HTTP網路連線時有兩個選擇可以使用:一是內建於基本Java函式庫中的java.net.HttpURLConnection類別;另一個則是Apache Commons下的HttpClient 函式庫。那麼,我們應該選用哪一個來進行HTTP連線呢?

 在做這個選擇之前,不要被所謂的「基本Java函式庫」(base Java library) 這個名字給騙了;所有被放進 Android application framework 中的類別(不管是不是基本的)都會被Google的Android開發團隊進行檢視並針對Android平台環境進行最佳化。所以,問題來了,當每個Android在進行平台升級與效能改善時,由於Apache HttpClient 函式庫所包含的類別數量太過龐大,因此難以針對Android平台對其進行修改 (甚至修bug);官方也建議,如果是在Android 3.0之後的裝置進行開發的話,請盡量使用HttpURLConnection

相關的資訊與討論可見:
Android官方開發者blog文章:http://android-developers.blogspot.tw/2011/09/androids-http-clients.html
官方blog文章簡中翻譯:http://ericbaner.iteye.com/blog/1183998
stackoverflow相關討論:http://stackoverflow.com/questions/9633236/apache-httpclient-4-x-vs-httpurlconnection-which-one-is-faster-on-android

2012年11月16日 星期五

[Python] Pythonic到底是什麼意思?


作者:Martijn Faassen
譯者:賴勇浩(http://blog.csdn.net/lanphaday
繁中修訂:林世鵬
註:Martijn Zope 領域的專家,他為 Zope 系列產品做了許多開發工作,也開發了 lxml 等多個開源產品。你可以在這裡瞭解一下他的資訊http://www.zope.org/Members/faassen。這篇文章寫於 2005 年,雖然有少部分內容(主要是例子)看起來已經有些過時,但即便是在今天,它的中心思想依然有極高的指導意義。

這是幾個月前在 EuroPython mailing list(主要用來組織和計畫 EuroPython 會議的郵寄清單)出現的問題。這是一個非常有意思的問題,我看到Pythonic這個詞被無數次地使用,但很少有人嘗試解釋它的含義。在串討論中,許多人包括我自己,都給出了自己的答案。現在我把我的答案放到 blog 上,並且潤色了一下,希望它能對您有所幫助。

「Pythonic」其實是一個含糊的概念,儘管沒有「智慧」或「生命」那麼模糊,但當你嘗試定義它們的時候,就會發現那其實難以下手。雖然它們難以定義,但並不意味著它們沒用;人們總是能在混亂的定義上依舊做得很好。Pythonic 有點像「Python慣用法」的意味,但現在我們必須來描述一下它真正的含義。

隨著時間的推移,Python語言不斷演進,社群不斷成長,出現了許多關於如何正確使用 Python 的方法。一方面 Python 語言推薦使用大量的慣例 (idioms) 來完成任務 (「完成任務的唯一方法」亦即 “the one way to do it”);另一方面,社區不斷演變的新的慣用法又反過來影響了語言的進化,以便更好地支持所謂的慣例。比如 dictionary 中的 .get() 方法,它把 has_key() 和元素存取兩個操作組合為一個操作,便可看出這種進化。

慣例往往不能直接從其它程式設計語言移植過來使用。例如,下文是實現對一個序列裡的每個元素執行一個操作的 C 語言實作方法:
for (i=0; i < mylist_length; i++) {
   do_something(mylist[i]);
}
直接的等效 Python 程式碼是這樣的:
i = 0
while i < mylist_length:
   do_something(mylist[i])
   i += 1
這段程式碼能夠完成工作,但並不 Pythonic,它並不是 Python 語言推薦的慣例。讓我們來改進一下。典型的 Python 慣例是用內建的 range() 函數來生成索引值:
for i in range(mylist_length):
   do_something(mylist[i])
其實這種實作方法也並不 Pythonic,接下來大家看看語言本身推薦的方法,真正 Pythonic 的實作:
for element in mylist:
   do_something(element)

 「如何直接地傳遞或改變參照」(how to pass or modify references directly) 是 comp.lang.python 的月經文,但在只有賦值(importclassdef 等語句也可視為賦值)的 Python 中這是不可能的。這種需求通常是因為想讓函數回傳多個值出來,用 C 或者許多其它程式設計語言的方法是給這個函數傳入引用或指標:
void foo(int* a, float* b) {
    *a = 3;
    *b = 5.5;
}
...
int alpha;
int beta;
foo(&alpha, &beta);
Python 中可以用很奇怪的方法來實現:通過給函數傳遞序列參數來返回結果。寫出來的代碼可能像這樣:
def foo(a, b):
    a[0] = 3
    b[0] = 5.5

alpha = [0]
beta = [0]
foo(alpha, beta)
alpha = alpha[0]
beta = beta[0]
顯然這是毫無 Pythonic 可言的方法。在Python 中要讓函數返回多個值的慣例與此迥異,得益於 tuple 和 tuple unpacking,它看起來也舒服得多了:
def foo():
    return 3, 5.5

alpha, beta = foo()

在經驗老到的 Python 程式師看來,不夠 Pythonic 的代碼往往看起來古怪、累贅,而且也難以理解。因為它使用冗長的程式碼代替常見的、公認的、簡短的慣例來實現預期效果。更糟糕的是,因為語言本身傾向支援正確的慣例,因此非推薦的程式碼通常執行起來較慢。

Pythonic 就是以清晰、可讀的慣例使用Python語言內容與資料結構的意思。例如,Python在處理物件實體 (instances) 這個問題時選擇了使用動態資料型別 (dynamically typing),若當初採用的是靜態資料型別的話,那之後造成的語言繁瑣與冗長事實上就不 Pythonic 了。另外,也請只使用經驗豐富的 Python 程式師熟悉的方式來完成事情;老鳥不熟悉的方法,通常都是不 Pythonic 的(請遵循最小驚奇原則)。

Pythonic 一詞也能夠適用於底層的慣例。對於一個函式庫或框架來說,Pythonic意謂的是要讓programmer盡可能容易且自然的利用它來完成工作。如果一個用Python寫成的函式庫或框架必須迫使程式師編寫累贅或非慣例的程式碼時,那麼它就不是Pythonic的。也可能是為了讓函式庫更加方便使用或易懂,而沒有使用 Python 的一些理念,例如類別時,那也是不 Pythonic 的。函式庫中的類別定義應該盡可能地將資訊隱藏,雖然 Python 的許多操作都只作「寬鬆限制」(通常由程式師在屬性的前面加上一個底線來暗示這是私有成員),但也要做得像 Java 那樣嚴格。

當然,當函式庫或框架的規模很大的時候,它是否 Pythonic 就極具爭議性了。這裡給出一些 guidelines:首先,必須減少任何繁瑣冗餘的部分:Python函式庫的API都傾向於小型化和輕量化(相對於 java 程式庫而言)。重量級的、API過於細化的Python函式庫則並不非常 Pythonic。例如 W3C XML DOM API,儘管它的 Python 實現已經頗有時日,但大家並不認為它 Pythonic。有些人認為它是 Java 式的,雖然也有許多 Java 程式師認為並不如此。

一個 Pythonic 的框架不會對已經用慣例完成的東西重複發明輪子,而且它也遵循常用的 Python 慣例。

當然,問題是建構框架時肯定會不可能避免地使用一些看來不常見的的方法來完成事情。Zope2 是我極為熟悉的一個框架,它也是一個使用了許多的特殊的方法(如 Acquisition)來完成工作的例子,這些方法往往什麼地方都用不到,因此許多經驗豐富的 Python 程式師認為它並不 Pythonic

建立 Pythonic 的框架極其困難,什麼理念更酷、更符合語言慣例對此毫無説明,事實上這些年來優秀的 Python 代碼的特性也在不斷演化。比如現在認為像 generatorssetsunicode strings datetime 之類的特性尤為 PythonicZope2 的歷史悠久,它從1997年開始開發,你不能把不夠 Pythonic 歸咎於它,甚至考慮到這麼多年來它控制得如此之好,更應該感謝它。

關於 Pythonicness 新趨勢的一個例子是Python 對套件和模組結構的日益規範化。新的codebases TwistedZope3 PyPy 等或多或少都跟隨了這樣的模式:
  • 套件和模組的命名使用小寫,單數形式,並且簡潔。
  • 套件通常僅僅作為命名空間,也就是 __init__.py 中並沒有任何東西。
在我寫函式庫時(如 lxml),我也試著遵循了這樣的慣例。

因為以上的觀念,許多 Python programmer 會覺得一個功能不那麼強大,但易學的框架會比另一個功能強大,但需要較多時間來學習框架更為 pythonic。所以有時我覺得說一個軟體不Pythonic其實並不公平,甚至可能會因為這種想法而忽略了該軟體好的一面。

最後,作為什麼是 Pythonic設計的補充教材,你可以嘗試一下在 Python 直譯器中執行以下敘述:
import this