Android事件處理的兩種方式詳解
安卓提供了兩種方式的事件處理:基于回調(diào)的事件處理和基于監(jiān)聽的事件處理。
基于監(jiān)聽的事件處理
基于監(jiān)聽的事件處理一般包含三個要素,分別是:
Event Source(事件源):事件發(fā)生的場所,通常是各個組件
Event(事件):事件封裝了界面組件上發(fā)生的特定事件(通常就是用戶的一次操作)
Event Listener(事件監(jiān)聽器):負責監(jiān)聽事件源發(fā)生的事件,并對各種事件作出相應的響應
下面使用一個簡單的案例介紹按鈕事件監(jiān)聽器
布局文件就是簡單的線性布局器,上面是一個EditText,下面是一個Button按鈕
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal">
<EditText
android:id="@+id/txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:editable="false"
android:cursorVisible="false"
android:textSize="12pt"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/bn"
android:text="單擊我"/>
</LinearLayout>使用Java代碼給Button注冊一個事件監(jiān)聽器
public class EventActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.event);
Button bn = (Button) findViewById(R.id.bn);
bn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EditText txt = (EditText) findViewById(R.id.txt);
txt.setText("bn按鈕被單擊了!");
}
});
}
}單擊按鈕后,文本框就會顯示"bn按鈕被單擊了!"

外部類作為事件監(jiān)聽器類
如果某個事件監(jiān)聽器確實需要被多個GUI界面所共享,而且主要是完成某種業(yè)務邏輯的實現(xiàn),那么就可以考慮使用外部類形式來定義事件監(jiān)聽器類。
我們定義一個類實現(xiàn)OnClickListener接口,并且實現(xiàn)onClick()方法
public class SendSmsListener implements View.OnClickListener {
private Activity activity;
private EditText address;
private EditText content;
public SendSmsListener(Activity activity, EditText adress, EditText content){
Toast.makeText(activity, "初始化完成", Toast.LENGTH_SHORT).show();
this.activity = activity;
this.address = adress;
this.content = content;
}
@Override
public void onClick(View v) {
String addressStr = address.getText().toString();
String contentStr = content.getText().toString();
// 獲取短信管理器
SmsManager smsManager = SmsManager.getDefault();
// 創(chuàng)建發(fā)送短信想PendingIntent
PendingIntent pendingIntent = PendingIntent.getBroadcast(activity, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE);
// 發(fā)送短信文本
smsManager.sendTextMessage(addressStr, null, contentStr, pendingIntent, null);
Toast.makeText(activity, "短信發(fā)送完成", Toast.LENGTH_LONG).show();
}
}然后編輯一個簡單的線性布局,有兩個輸入框和一個按鈕
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<EditText
android:id="@+id/edit1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="50pt"/>
<EditText
android:id="@+id/edit2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="50pt"/>
<Button
android:id="@+id/send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="點擊我發(fā)送信息"
android:longClickable="true"/>
</LinearLayout>最后編寫一個Activity
public class SendMessageActivity extends Activity {
private EditText editText1;
private EditText editText2;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.message);
editText1 = (EditText) findViewById(R.id.edit1);
editText2 = (EditText) findViewById(R.id.edit2);
Button button = (Button) findViewById(R.id.send);
button.setTextSize(25);
button.setOnClickListener(new SendSmsListener(this, editText1, editText2));
// 請求權限
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.SEND_SMS} , 1);
}
}為了能夠順利發(fā)送短信,需要重新開啟一臺模擬器,填寫模擬器的ID,否則程序會報錯

我的電腦配置沒有辦法同時運行兩個虛擬機,所以這里就不展示了。
基于回調(diào)的事件處理
回調(diào)這個詞在編程領域經(jīng)常被提及,我的理解是,回調(diào)實際上是某個類中早已經(jīng)定義好的方法或者接口,當我們繼承或者實現(xiàn)接口的時候,可以相應地重寫對應方法,或者實現(xiàn)相應接口。在程序運行的特定位置會調(diào)用特定的方法,當我們重寫了某個方法之后,就可以在特定情況下實現(xiàn)對應的邏輯。
最最簡單的一個例子就是Activity的onCreate()方法,當我們初始化一個Activity類的時候,就會調(diào)用這個方法,如果我們不重寫這個方法,那么程序就會調(diào)用默認的onCreate()方法,如果我們重寫了這個方法,那么程序就會調(diào)用我們重寫的onCreate()方法。
我們可以用回調(diào)的方式實現(xiàn)一個跟隨手指的小球。
首先自定義一個自定義的View
public class DrawViewPlus extends View {
public float currentX = 50;
public float currentY = 50;
// 定義創(chuàng)建畫筆
Paint p = new Paint();
public DrawViewPlus(Context context, AttributeSet set) {
super(context, set);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 設置畫筆顏色
p.setColor(Color.RED);
// 繪制小球
canvas.drawCircle(currentX, currentY, 15, p);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 獲取觸碰的坐標點
currentX = event.getX();
currentY = event.getY();
// 重新繪制小球
this.invalidate();
// 返回true表明處理方法已經(jīng)處理完該事件
return true;
}
}然后再xml文件中加入自定義組件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 使用自定義組件 -->
<com.example.acitvitytest.ui.DrawViewPlus
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>在Activity中只需要簡單地加載界面就行,所有的邏輯都在自定義組件中編寫,這樣可以讓程序結構更加清晰。
public class DrawActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.draw);
}
}
響應系統(tǒng)設置的事件
Configuration類專門用于描述手機設備上的配置信息,這些配置信息既包括用戶特定的配置項,也包括系統(tǒng)的動態(tài)設備配置。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="35pt"
android:id="@+id/ori" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="35pt"
android:id="@+id/navigation" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="35pt"
android:id="@+id/touch" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="35pt"
android:id="@+id/mnc" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bn"
android:textSize="35pt"
android:text="獲取手機信息"/>
</LinearLayout>public class CfgActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.configuration);
// 獲取應用界面中的界面組件
EditText ori = findViewById(R.id.ori);
EditText navigation = findViewById(R.id.navigation);
EditText touch = findViewById(R.id.touch);
EditText mnc = findViewById(R.id.mnc);
Button bn = findViewById(R.id.bn);
// 為按鈕綁定事件監(jiān)聽器
bn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 獲取系統(tǒng)的Configuration對象
Configuration cfg = getResources().getConfiguration();
String screen = cfg.orientation ==
Configuration.ORIENTATION_LANDSCAPE ? "橫向屏幕" : "縱向屏幕";
String mncCode = cfg.mnc + "";
String naviname;
if(cfg.orientation == Configuration.NAVIGATION_NONAV){
naviname = "沒有方向控制";
}
else if (cfg.orientation == Configuration.NAVIGATION_WHEEL){
naviname = "滾輪控制方向";
}
else if (cfg.orientation == Configuration.NAVIGATION_DPAD){
naviname = "方向鍵控制方向";
}
else {
naviname = "軌跡球控制方向";
}
String touchname = cfg.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH ? "無觸摸屏" : "支持觸摸屏";
ori.setText(screen);
navigation.setText(naviname);
touch.setText(touchname);
mnc.setText(mncCode);
}
});
}
}點擊按鈕就可以獲取相應的配置信息

Handler消息傳遞機制
Handler類的主要作用有兩個:在新啟動的線程中發(fā)送消息和在主線程中獲取處理消息。
我們可以通過一個新線程來周期性地修改ImageView所顯示的圖片,通過這種方式來開發(fā)一個動畫效果。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/show"/>
</LinearLayout>public class HanlderActivity extends Activity {
// 定義周期性顯示圖片的id
int[] images = new int[] {
R.drawable.ic_launcher_foreground,
R.drawable.ic_launcher_background
};
int currentImageId = 0;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.handler);
ImageView image = findViewById(R.id.show);
Handler myhandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
// 如果消息是本程序發(fā)送的,那么就修改ImageView顯示的圖片
if(msg.what == 0x1233){
image.setImageResource(images[currentImageId++ % images.length]);
}
}
};
// 定義一個計時器,讓該計時器周期性地執(zhí)行指定任務
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// 發(fā)送空消息
myhandler.sendEmptyMessage(0x1233);
}
},0, 1200);
}
}上述代碼中使用TimerTask對象啟動了一個新的線程,由于啟動的線程沒有辦法直接訪問Activity中的界面組件,因此使用Handler傳遞消息,從而實現(xiàn)間接訪問。程序會周期性地變換顯示的圖片


和Handler一起工作的組件有三個:
Message:Hanlder接收和處理的消息對象
Looper:每個線程只能擁有一個Looper,他的loop方法負責讀取MessageQueue中的消息,讀到信息之后就把消息交給發(fā)送該消息的Handler進行處理
MessageQueue:消息隊列,他用先進先出的方式來管理Message
我們通常會將比較耗時的操作放到一個新的線程中去執(zhí)行,如果使用UI線程執(zhí)行耗時操作,那么線程很可能被阻塞,從而降低用戶體驗。
我們可以看一下Looper對象中的prepare()方法
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}這是一個靜態(tài)方法,大概的邏輯就是實例化一個Looper對象放到sThreadLocal容器中,并且容器中只能有一個Looper對象,假如在實例化前就已經(jīng)存在了Looper對象,那么就拋異常。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}我們可以看到Looper對象的構造方法是用private修飾的,也就是說我們不能自己實例化Looper對象,只能通過調(diào)用靜態(tài)的prepare()方法進行構造。
最后構造得到的實例對象是放到ThreadLocal容器中的
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
Looper對象最重要的方法就是loop(),該方法會反復檢查MessageQueue中是否有消息,如果有消息就會取出來進行處理,如果沒有消息就會進行阻塞,直到取出消息為止
/**
* Poll and deliver single message, return true if the outer loop should continue.
*/
@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (me.mSlowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
me.mSlowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
me.mSlowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
return true;
}
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
@SuppressWarnings("AndroidFrameworkBinderIdentity")
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
me.mSlowDeliveryDetected = false;
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}下面我們就寫一個簡單的程序計算到某個指定數(shù)為止的所有質數(shù),并且用Toast顯示出來。
界面代碼比較簡單,就是一個文本框和一個按鈕
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="50pt"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="點我進行計算"
android:textSize="25pt"
android:id="@+id/cal"
android:onClick="cal"/>
</LinearLayout>在Java代碼中我們需要定義一個線程,里面定義一個Handler類,該Handler類的處理消息的邏輯是先從消息中取出數(shù)據(jù),然后進行計算,最后使用Toast顯示計算結果。
按鈕的點擊事件的處理邏輯是,首先封裝一個Message對象,然后將Message對象傳遞給線程中的Handler對象。
public class CalNumActivity extends Activity {
private final String UPPER = "UPPER_NUM";
private CalThread calThread;
private EditText editText;
class CalThread extends Thread{
public Handler mHandler;
@Override
public void run() {
// 實例化Looper對象
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if(msg.what == 0x123){
// 獲取最大的那個數(shù),從Message中取出Data
// 該Data是Bundle對象,采用鍵值對的形式傳遞數(shù)據(jù)
int upper = msg.getData().getInt(UPPER);
List<Integer> numlist = new ArrayList<>();
outer:
for(int i=2; i<=upper; i++){
for(int j=2; j<=Math.sqrt(i); j++){
// 只能被1和它本身整除的才是質數(shù)
if(j == i){
continue;
}
if(j % i == 0){
continue outer;
}
}
numlist.add(i);
}
// 顯示計算出來的質數(shù)
Toast.makeText(CalNumActivity.this, numlist.toString(), Toast.LENGTH_LONG).show();
}
}
};
// 執(zhí)行l(wèi)oop()方法,從MessageQueue中取出消息
Looper.loop();
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.calnum);
editText = findViewById(R.id.input);
Button button = findViewById(R.id.cal);
// 啟動新線程
calThread = new CalThread();
calThread.start();
}
// 為按鈕的點擊事件添加事件處理函數(shù)
public void cal(View source){
// 構建消息
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt(UPPER, Integer.parseInt(editText.getText().toString()));
msg.setData(bundle);
calThread.mHandler.sendMessage(msg);
}
}
到此這篇關于Android事件處理的兩種方式詳解的文章就介紹到這了,更多相關Android事件處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android自定義view實現(xiàn)標簽欄功能(只支持固定兩個標簽)
這篇文章主要介紹了Android自定義view實現(xiàn)標簽欄(只支持固定兩個標簽),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
Android彈窗ListPopupWindow的簡單應用詳解
這篇文章主要為大家詳細介紹了Android彈窗ListPopupWindow的簡單應用,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11
Android實現(xiàn)退出時關閉所有Activity的方法
這篇文章主要介紹了Android實現(xiàn)退出時關閉所有Activity的方法,主要通過自定義類CloseActivityClass實現(xiàn)這一功能,需要的朋友可以參考下2014-09-09
Android開發(fā)中總結的Adapter工具類【附完整源碼下載】
這篇文章主要介紹了Android開發(fā)中總結的Adapter工具類,簡單說明了Adapter的功能,并結合實例形式分析了Adapter工具類的相關使用方法,并附帶完整源碼供讀者下載參考,需要的朋友可以參考下2017-11-11
Android模擬器"Failed To Allocate memory 8"錯誤如何解決
這篇文章主要介紹了Android模擬器"Failed To Allocate memory 8"錯誤如何解決的相關資料,需要的朋友可以參考下2017-03-03

