Search

Android - Multi-thread 多執行緒與 UI 更新操作

2015-07-23 2:13 PM

在 Android 的開發規則裡有一個規定

就是所有與網路連線的功能都必須在另一個執行緒內執行

而另一個相關的重要規定是

所有與 UI 相關的功能只能在 UI Thread 內執行(也就是主執行緒)

這項規定是由於程式執行流暢度的考量而產生的

但這麼一來

若在其他執行緒的做完某件事情時 該如何使用 UI 告知使用者呢?

這時就需要用到 Thread 搭配 Handler 的 Design Pattern 了

當然還有其他的選項 例如 AsyncTask

但為了最大的開發自由度

我建議還是使用 Thread 搭配 Handler 會較一勞永逸

當然還是要看情況而定

廢話不多說 我們來看看該如何使用這項功能

這裡同時會用到 WeakReference 的 Design Pattern

會在另一個教學文章 WeakReference 與多執行緒的 Handler 提到

程式碼範例
// Handler in main activity
private SSHThreadHandler sshHandler = new SSHThreadHandler(this);

private static class SSHThreadHandler extends Handler{

    private WeakReference<MainActivity> refActivity;

    public SSHThreadHandler(MainActivity activity) {
        refActivity = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message message){
        MainActivity    activity = refActivity.get();
        SSHConnection  sshConnection = activity.getSshConnection();
        TextView        redirectPageStatus = activity.getRedirectPageStatus();

        switch(message.what){
            case SSHConnection.waitingUsername:
                sshConnection.inputUsername();
                redirectPageStatus.setText( "連線中... 快好囉~" );
                break;
            case SSHConnection.waitingPassword:
                sshConnection.inputPassword();
                break;
            case ISSHConnection.waitingDuplicateConfirm:
                activity.showDuplicateLoginAlert();
                break;
            case SSHConnection.waitingContinueConfirm:
                sshConnection.doContinue();
                redirectPageStatus.setText( "歡迎回來 " + sshConnection.getUsername() );
                break;
            case SSHConnection.menuPage:
                activity.hideLoginPageFrame();
                activity.hideLoadingFrame();
                activity.showMenuPageHolder();
                break;
        }
    }
}

// 其他執行緒與 Handler 互動的方式

public static final int connected = 0;
public static final int waitingUsername = 1;
public static final int waitingPassword = 2;
public static final int waitingDuplicateConfirm = 3;
public static final int waitingContinueConfirm = 4;
public static final int menuPage = 5;

@Override
 public void connect(String username, String password, Handler handler) throws IOException {
  if( somthingHappened ){
  sendMessage( doSomething );
 }
 else{
  sendMessage( doAnother );
 }
}

/**
 * Send message to the UI thread.
 */
private void sendMessage(int msg){
        Message message = new Message();
        message.what = msg;
        handler.sendMessage(message);
}

Android - WeakReference 與多執行緒的 Handler

1:59 PM

the handler class should be static or leaks might occur

Android 在使用多執行緒搭配 UI 元件操作時

我們必須使用 Handler 搭配 Thread 以便繞過 Android 不可在 UI Thread 以外執行 UI 操作的限制

這個警告便表示 Handler 應該宣告為 static 以免程式發生無法預期的問題

但宣告為 static 又無法呼叫 Activity 內的 function 該如何是好?

這時就必須要用到 weakreference 這個類別的設計方法

如此即可在 Handler 內存取 Activity 的成員

若想知道多執行緒如何與 Handler 搭配使用可以查看 Multi-thread 多執行緒與 UI 更新操作 這篇文章

程式碼範例
private SSHThreadHandler sshHandler = new SSHThreadHandler(this);

private static class SSHThreadHandler extends Handler{

 // 與 MainActivity 的 WeakReference 變數
 private WeakReference<MainActivity> refActivity;

 // 藉由 new 物件時傳入的 activity 參數建立 WeakReference
 public SSHThreadHandler(MainActivity activity) {
     refActivity = new WeakReference<MainActivity>(activity);
 }

 @Override
 public void handleMessage(Message message){
     // 取得 MainActivity
     MainActivity    activity = refActivity.get();
     // 取得 MainActivity 內的成員
     ISSHConnection  sshConnection = activity.getSshConnection();
     // 取得 UI 物件
     TextView        redirectPageStatus = activity.getRedirectPageStatus();

     switch(message.what){
         case ISSHConnection.waitingUsername:
             sshConnection.inputUsername();
             redirectPageStatus.setText( "連線中... 快好囉~" );
             break;
         case ISSHConnection.waitingPassword:
             sshConnection.inputPassword();
             break;
         case ISSHConnection.waitingDuplicateConfirm:
             activity.showDuplicateLoginAlert();
             break;
         case ISSHConnection.waitingContinueConfirm:
             sshConnection.doContinue();
             redirectPageStatus.setText( "歡迎回來 " + sshConnection.getUsername() );
             break;
         case ISSHConnection.menuPage:
             activity.hideLoginPageFrame();
             activity.hideLoadingFrame();
             activity.showMenuPageHolder();
             break;
     }
 }
}
各項資料連結
Android - Multi-thread 多執行緒與 UI 更新操作
WeakReference

Java - 編碼過的網址 punycode decoding

2015-07-16 10:54 AM

有在做爬網頁資料的人應該會遇到有的網頁內具有一種編碼過的連結網址存在

這種轉碼的方法我們稱之為 Punycode

這種編碼方法可以呈現 Unicode 字元組成的網址

如果需要分析經過 punycode 轉碼過的網址時就可以使用以下方法 decoding

這裡使用 Apache 的 Httpclient 提供的 Punycode 工具直接轉碼

程式碼範例
import org.apache.http.client.utils.Punycode

String punyUrl = Punycode.toUnicode(url);
各項資料連結
Punycode Wiki
Maven Repo - Apache Httpclient 4.3.2
Class Punycode

Java - FreeMarker + Spring MVC 如何設定

2015-07-10 11:31 AM

使用 Java 撰寫網頁最基本的是使用 JSP 產生的動態格式文件

但有使用過的都知道其實 JSP 與 HTML 語法混雜的可讀性非常之差

就我本身的立場而言 template 能有多單純就多單純

最好只用到輸出變數, 迴圈以及為數不多的判斷式就好

當然最重要的就是可以不用重啟伺服器就能讀取修改後的 template 這個功能

FreeMarker 剛好符合我的需求

是一個設定方便, 反應迅速並且保持 HTML 一致性的輕量級模板引擎

之前雖也有考慮過 Thymeleaf 這個模板引擎

他搭配 HTML 的一致性可以說是在 FreeMarker 之上

但似乎不能用來產生非 XML 格式的動態文件

因此就放棄使用 Thymeleaf 了

那麼以下我們就來看看 FreeMarker 搭配 Spring 的設定檔該如何使用

程式碼範例
<!-- 使用FreeMarker Templates -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/views/freemarker"/>
    <property name="defaultEncoding" value="UTF-8" /> 
    <property name="freemarkerSettings">  
        <props>
            <prop key="template_update_delay">10</prop>  
            <prop key="locale">zh_TW</prop>  
            <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>  
            <prop key="date_format">yyyy-MM-dd</prop>
            <prop key="time_format">HH:mm:ss</prop>
            <prop key="number_format">#.##</prop>
            <prop key="boolean_format">true,false</prop>
            <prop key="object_wrapper">beans</prop>
            <prop key="template_exception_handler">mvc.controller.exception.resolver.FreeMarkerExceptionHandler</prop>
        </props>
    </property>
</bean>

<!-- 設定預設view路徑與副檔名 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
 <property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView"/>
   <property name="cache" value="true" />
   <property name="suffix" value=".ftl" />
   <property name="contentType" value="text/html;charset=UTF-8" />
   <property name="requestContextAttribute" value="request" />
   <property name="exposeRequestAttributes" value="true" />
    <property name="exposeSessionAttributes" value="false" /> 
    <property name="exposeSpringMacroHelpers" value="true" /> 
</bean>