Android的事件處理

1 android事件處理概述  html

  不管是桌面應用仍是手機應用程序,面對最多的就是用戶,常常須要處理用戶的動做-------也就是須要爲用戶動做提供響應,這種爲用戶動做提供響應的機制就是事件處理。android提供了兩套事件處理機制:java

  • 基於監聽的事件處理

  主要作法就是爲Android界面組件綁定特定的事件監聽器;android還容許在界面佈局文件中爲UI組件的android:onClick屬性指定事件監聽方法,該方式須要開發者在activity中定義該事件監聽方法(該方法必須帶有一個View類型的形參,該形參表明被單擊的UI組件,當用戶單擊該組件時,系統將會激發android:onClick屬性所指定的方法)android

  • 基於回調的事件處理

主要作法是重寫android組件特定的回調方法,或者重寫Activity的回調方法。(基於回調的事件處理能夠用於處理一些具備通用性的事件(業務邏輯比較固定),某些特定事件處理,仍需事件監聽機制。)程序員

2 基於監聽的事件處理編程

2.1 監聽的處理模型安全

2.1.1 事件監聽的處理模型中,主要涉及以下三類對象網絡

  • Event Source(事件源):事件發生場所,一般是指各個組件,如按鈕、窗口、菜單等
  • Event(事件):事件封裝了界面組件上發生的特定事情,Event包含所發生事件的相關信息
  • Event Listener(事件監聽器):負責監聽事件源所相關事件的發生,並對並對發生事件做出反應

 2.1.2 事件處理流程圖多線程

  

2.1.3 基於監聽的時間處理模型的編程步驟app

  • 獲取普通界面組件的對象(事件源),即被監聽對象
  • 實現事件監聽器類,該監聽器類是一個特殊的java類,必須實現一個xxxListener接口
  • 調用事件源的setXxxListener方法將監聽對象註冊給事件源

2.2 內部類做爲事件監聽器類異步

  • 能夠在當前父類中複用該監聽器
  • 可自由訪問外部類的全部界面組件

2.3 外部類做爲事件監聽器類

2.3.1 該形式比較少見,主要有兩點緣由:

  • 事件監聽器對應於具體的組件,定義成外部類,不利於提升程序的內聚性
  • 外部類不可以自由訪問建立GUI界面類的組件,編程不夠簡潔

2.3.2 若某個監聽器須要被多個GUI界面組件所共享,且主要是完成某種業務邏輯的實現,則可考慮外部類的形式

2.4 Activity自己做爲監聽器

  直接用Activity自己做爲監聽器類,這種形式很是簡潔,但這種作法有兩個缺陷:

  • 這種程序結構可能形成混亂,Activity的主要職責應該是完成界面初始化工做
  • Activity類自己還要實現監聽接口,顯得結構臃腫

2.5 匿名內部類做爲事件監聽器

  • 因爲大部分時候事件處理器都沒有什麼複用價值,所以大部分都是一次性使用,故形成此方法爲最經常使用方法

2.6 直接綁定到標籤

  • 該方法結構簡潔
  • 可是結構也相對比較固定,適用範圍有限(只支持onClick)

3 基於回調的事件處理

3.1 回調機制與監聽機制

  • 事件監聽機制是一種委託式的事件處理,事件源和事件監聽器分離,事件源發生器特定的事件後,該事件交給事件監聽器負責處理
  • 基於回調的事件模型來講,事件源與事件監聽器是統一的,事件發生後,仍是又事件源自己負責處理
  • 爲了使組件自身處理事件,則必須爲組件對象添加方法,方法沒法動態添加,故預置處理接口,繼承重寫方法便可

3.2 android爲全部GUI組件都提供了一些事件處理的回調方法,以View爲例,該類包含如下方法:

  • onKeyDown,當用戶在該組件上按下某個按鍵時觸發該方法
  • onKeyLongPress,當用戶在組件上長按某個按鍵時觸發該方法
  • onKeyShortcut,當一個鍵盤快捷事件發生時觸發該方法
  • onKeyUp,當用戶在該組件上鬆開某個按鈕時觸發該方法
  • onTouchEvent ,當用戶在該組件上觸發觸摸屏事件時觸發該方法
  • onTrackballEvent,當用戶在該組件上觸發軌跡球屏事件時觸發該方法

3.3 基於回調的事件傳播

  幾乎基於回調的事件處理方法都有一個boolean類型的返回值,該返回值用於標識該處理方法是否能徹底處理該事件:

  • 若是處理事件的回調方法返回true,代表該處理方法已經徹底處理該事件,該事件不會傳播出去
  • 若是處理時間的回調方法返回false,則代表處理方法並未徹底處理事件,該事件會傳播出去,
  • 事件觸發順序爲:事件源監聽處理器---->事件源自己回調函數----->事件源父組件(遞歸而上)

3.4 兩種處理模型的區別 

  • 基於監聽的事件模型分工更明確,事件源、事件監聽分別有兩個類分開實現,所以具備更好的可維護性
  • Android的事件處理機制保證基於監聽的事件監聽器會被優先觸發

3.5 示例

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal">

    <EditText
        android:id="@+id/address"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="請填寫收信號碼"
        />
    <EditText
        android:id="@+id/content"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="請填寫短信內容"
        android:lines="3"
        />
    <com.example.penghuster.myfirstapp.MyButton
        android:id="@+id/send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="發送" />

</LinearLayout>
View Code
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;


public class MainActivity extends Activity {
    EditText address;
    EditText content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        address = (EditText) findViewById(R.id.address);
        content = (EditText) findViewById(R.id.content);
        MyButton btn = (MyButton) findViewById(R.id.send);
        btn.requestFocus();
        btn.setFocusableInTouchMode(true);

        btn.setOnKeyListener(new View.OnKeyListener() {
            public boolean onKey(View source, int value, KeyEvent event) {
                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    Log.v("--Listener--", "the keydown ");
                }
                return false;
            }
        });

    }

    public boolean onKeyDown(int keyCode, KeyEvent event){
        super.onKeyDown(keyCode, event);
        Log.v("--activity---", "ddd");
        return false;
    }
}

class MyButton extends Button{
    public MyButton(Context context, AttributeSet set){
        super(context, set);
    }

    public boolean onKeyDown(int keyCode, KeyEvent event){
        super.onKeyDown(keyCode, event);
        Log.v("MyButton", "the onKeyDown in MyButton");
        return  false;
    }
}
View Code

4 響應系統設置的事件

  在開發Android應用時,有時可能須要讓應用程序隨系統設置而進行調整,好比判斷屏幕方向、判斷系統方向的方向導航設備等。除此以外,有時還須要讓應用程序監聽系統設置的更改,並做出響應。

4.1 Configuration類

4.1.1 簡介

  • Configuration類專門用於描述手機設備上的配置信息,包括用戶特定的配置項、系統的動態配置
  • Configuration類的對象構建:Configuration cfg = Activity.getResources().getConfiguration()
  • Configuration對象提供經常使用的獲取系統配置信息的屬性:
    • public float fontScale:獲取當前用戶設置的字體縮放因子
    • public int keyboard:獲取當前設備所關聯的鍵盤類型。該屬性可能返回以下值:KEYBOARD_NOKEYS/KEYBOARD_QWERTY/KEYBOARD_12KEY
    • public int keyboardHidden:該屬性返回一個boolean值用於標識當前鍵盤是否可用。該屬性不只判斷系統的硬件鍵盤、也判斷系統的軟鍵盤。若是該系統的硬件鍵盤不可用,軟件鍵盤可用,也會返回KEYBOARDHIDDEN_NO;只有當二者都不可用用時,才返回KEYBOARDHIDDEN_YES
    • public Locale locale:獲取當前用戶的Local,國家地區
    • public int mcc:獲取移動信號的國家碼
    • public int mnc:獲取移動信號的網絡碼
    • public int navigation:判斷系統上方向導航設備的類型。該屬性可能返回值:NAVIGATION_NONAV(無導航)、NAVIGATION_DPAD(DPOR導航)、NAVIGATION_TRACKBALL(軌跡球導航)、NAVIGATION_WHEEL(滾輪導航)
    • public int orientation:獲取系統屏幕的方向,該屬性可能返回值ORIENTATION_LANDSCAPE(橫向屏幕)、ORIENTATION_PORTRAIT(豎向屏幕)、ORIENTATION_SQUARE(方形屏幕)
    • public int touchscreen:獲取系統觸摸屏的觸摸方式,該屬性可能返回TOUCHSCREEN_NOTOUCH(無觸摸屏)、TOUCHSCREEN_STYLUS(觸摸筆式觸摸屏)、TOUCHSCREEN_FINGER(接受手指的觸摸屏)

4.1.2 重寫onConfigurationChanged響應系統設置更改

  若是程序須要監聽系統設置的更改,則能夠考慮重寫Activity的onConfiguration(Configuration newConfig)方法,該方法是一個基於回調的事件處理方法,當系統設置發生更改時,該方法會被自動觸發。在程序中,能夠動態調用setRequestedOrientation(int)方法來修改屏幕方向

4.1.3 示例:監聽屏幕方向改變

Mainfest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.penghuster.myfirstapp" >

    <uses-sdk
        android:maxSdkVersion="12"
        android:minSdkVersion="10" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:configChanges="orientation|screenSize"
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
View Code

activity.java

import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;


public class MainActivity extends Activity {
    EditText address;
    EditText content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        address = (EditText) findViewById(R.id.address);
        content = (EditText) findViewById(R.id.content);
        MyButton btn = (MyButton) findViewById(R.id.send);

        btn.setOnClickListener(new View.OnClickListener() {
            public void onClick(View source) {
                Configuration cfg = getResources().getConfiguration();

                if (cfg.orientation == Configuration.ORIENTATION_LANDSCAPE)
                    MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

                if (cfg.orientation == Configuration.ORIENTATION_PORTRAIT)
                    MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

            }
        });

    }


    public void onConfigurationChanged(Configuration configuration) {
        super.onConfigurationChanged(configuration);
        String screen = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE ?
                "橫向屏幕" : "豎向屏幕";
        Toast.makeText(this, "系統屏幕方向改變\n" + "修改後屏幕方向爲:" + screen, Toast.LENGTH_LONG).show();
    }
}
View Code

5 Handler消息傳遞機制

5.1 UI線程

  • Android的UI操做並非線程安全的,多線程操做可能致使線程安全
  • android制定一條簡單的規則:只容許UI線程修改Activity裏的UI組件
  • UI線程:當一個程序第一次啓動時,Android會同時啓動一條主線程(main thread),負責處理與UI相關的事件,並把相關的事件分發到對應的組件進行處理
  • 問題:因爲只容許UI線程修改Activity裏的組件,致使新啓動的線程沒法動態改變界面組件的屬性值,但實際開發中,每每須要讓新啓動的線程週期性地改變界面組件的屬性值,這就須要藉助Handler的消息傳遞機制

5.2 Handler類簡介

5.2.1 Handler類的主要做用

  • 在新啓動的線程中發送消息
  • 在主線程中獲取、處理消息

5.2.2 handler類消息處理原理

  • 經過回調方式來實現,重寫Handler類中處理消息的方法
  • 新線程的發送消息時,消息會發送到與之關聯的MessageQueue
  • Handler會不斷從MessageQueue中獲取並處理消息

5.2.3 Handler類包含以下方法用於發送、處理消息

  • void handlerMessage(Message msg):處理消息的方法,該方法一般用於被重寫
  • final boolean hasMessages(int what, Object object):檢查消息隊列中是否包含what屬性爲指定值且object屬性爲指定對象的消息
  • 多個重載的Message obtainMessage():獲取消息
  • final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒後發送空消息   
  • final boolean sendMessage(Message msg):當即發送消息
  • final boolean sendMessageDelayed(Message msg, long delayMillis):指多少毫秒以後發送消息

5.3 Handler、Loop、MessageQueue的工做原理

5.3.1 相關組件介紹

  • Message:Handle接收和處理的消息對象
  • Looper:每一個線程只能擁有一個Looper,其loop方法負責讀取MessageQueue中的消息,讀到消息以後會把消息交給發送該消息的Handler進行處理。Looper的構造器使用private修飾,代表程序員沒法經過構造器建立Looper對象
  • MessageQueue:消息隊列,採用先進先出的方式管理Message。程序建立Looper對象時,會在他的構造器中建立Looper對象。

5.3.2 Looper對象的建立

  因爲Looper對象沒有提供public構造器,可是Handler正常工做,必需要求當前線程中有一個Looper對象,爲了保證當前線程中有Looper對象,可分如下兩種狀況:

  • 主UI線程中,系統已經初始化了一個Looper對象,所以程序直接建立Handler便可,讓後經過Handler來發送、處理消息
  • 程序員本身啓動的子線程,程序員必須本身調用Looper類的靜態方法repare()建立一個Looper對象,並啓動它

5.3.3 在新線程中使用Handler的步驟:

  • 調用Looper的prepare()方法爲當前線程建立Looper對象,建立Looper對象時,它的構造器會建立與之配套的MessageQueue
  • 有了Looper以後,建立Handler子類的實例,重寫HandleMessage()方法,該方法負責處理來自其餘線程的消息
  • 調用Looper的loop()方法啓動Looper

5.3.4 示例:使用新線程計算質數

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;


public class MainActivity extends Activity {
    EditText address;
    EditText content;
    static final String UPPER_NUM = "upper";
    CalThread calThread;

    class CalThread extends Thread {
        public Handler mHandler;
        public void run(){
            Looper.prepare();
            mHandler = new Handler(){
                public void handleMessage(Message msg){
                    if (msg.what == 0x123){
                        int upper = msg.getData().getInt(UPPER_NUM);
                        List<Integer> nums = new ArrayList<Integer>();
                        outer:
                        for (int i=2; i<=upper; i++){
                            for (int j=2; j<=Math.sqrt(i); j++)
                                if (i != 2 && i % j ==0)
                                    continue outer;
                            nums.add(i);
                        }

                        Toast.makeText(MainActivity.this, nums.toString(), Toast.LENGTH_LONG).show();
                    }
                }
            };
            Looper.loop();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        address = (EditText) findViewById(R.id.address);
        content = (EditText) findViewById(R.id.content);
        MyButton btn = (MyButton) findViewById(R.id.send);

        calThread = new CalThread();
        calThread.start();

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message msg = new Message();
                msg.what = 0x123;
                Bundle bundle = new Bundle();
                bundle.putInt(UPPER_NUM, Integer.parseInt(address.getText().toString()));
                msg.setData(bundle);
                calThread.mHandler.sendMessage(msg);
            }
        });

    }

}
View Code

6 異步任務

 6.1 爲了解決新線程不能更新UI組件的問題,android提供了以下幾種解決方案。

  • 使用Handler實現線程之間的通訊
  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)
  • AsyncTask異步任務

6.2 AsyncTask(異步任務)

  • AsyncTask<>是一個抽象類,一般用於被繼承,繼承AsyncTask須要指定以下三個泛型參數:Params,啓動任務執行的輸入參數類型;Progress,後臺任務完成的進度值的類型;Result,後臺任務執行完成後返回的結果類型

6.3 使用AsyncTask的步驟:

  • 建立AsyncTask的子類,併爲三個泛型參數指定類型,若是某個參數不須要,則指定爲Void
  • 根據須要選擇性實現以下方法
    • doInBackground(Params):重寫該方法就是後臺線程將要完成的任務,該方法能夠調用publishProgress(Progress values)方法更新任務執行進度
    • onProgressUpdate(Progress values)在doInBackground中調用publishProgress更新進度後,將會觸發該方法
    • onPreExecute():該方法將在執行後臺耗時操做前被調用,一般該方法用於完成一些初始化的準備工做,好比在界面上顯示進度條等
    • onPostExecute(Result result):該方法在doInBackground()完成後,系統自動調用,並將doInBackground的返回值傳給Result參數
  • 調用AsyncTask子類實例的execute()開始執行耗時任務

6.4 使用AsyncTask時必須遵照以下規則

  • 必須在UI線程中建立AsyncTask的實例
  • 必須在UI線程中調用AsyncTask的execute方法
  • doInBackground(Params),onProgressUpdate(Progress values),onPreExecute(),onPostExecute(Result result)等方法都是由系統負責調用
  • 每一個AsyncTask只能被執行一次,屢次調用將會引起異常

6.5 示例:使用異步任務下載

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;


public class MainActivity extends Activity {
    TextView address;
    TextView content;

    class DownTask extends AsyncTask<URL, Integer, String> {
        ProgressDialog pDialog;
        int hasRead = 0;
        Context mContext;

        public DownTask(Context ctx) {
            mContext = ctx;
        }

        protected String doInBackground(URL... params) {
            StringBuilder sb = new StringBuilder();
            try {
                URLConnection conn = params[0].openConnection();
                BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(),
                        "utf-8"));
                String line = null;
                while ((line = br.readLine()) != null) {
                    sb.append(line + "\n");
                    hasRead++;
                    publishProgress(hasRead);
                }

                return sb.toString();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return null;
        }

        protected void onPostExecute(String result) {
            address.setText(result);
            pDialog.dismiss();
        }

        protected void onPreExecute() {
            pDialog = new ProgressDialog(mContext);
            pDialog.setTitle("任務執行中");
            pDialog.setMessage("任務執行中,敬請期待");
            pDialog.setCancelable(false);
            pDialog.setMax(202);
            pDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            pDialog.setIndeterminate(true);
            pDialog.show();
        }

        protected void onProgressUpdate(Integer... values) {
            content.setText("已經讀取了" + values[0] + "行!");
            pDialog.setProgress(values[0]);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        address = (TextView) findViewById(R.id.address);
        content = (TextView) findViewById(R.id.content);
        MyButton btn = (MyButton) findViewById(R.id.send);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    DownTask task = new DownTask(MainActivity.this);
                    task.execute(new URL("http://www.cnblogs.com/renqingping/archive/2012/10/25/Parcelable.html"));
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });

    }

}
View Code
相關文章
相關標籤/搜索