学而实习之 不亦乐乎

Android:事件处理机制

2022-07-31 21:55:53

一、Android 中的事件处理方式

Android中的事件处理方式有以下几种,如:

  1. 基于监听器的事件处理
  2. 基于回调的事件处理
  3. 响应系统设置的事件
  4. Handler消息传递机制
  5. 异步任务操作

二、处理机制

1、基于监听器的事件处理

相比于基于回调的事件处理,这是更具“面向对象”性质的事件处理方式。在监听器模型中,主要涉及三类对象:
    1)事件源Event Source:产生事件的来源,通常是各种组件,如按钮,窗口等。
    2)事件Event:事件封装了界面组件上发生的特定事件的具体信息,如果监听器需要获取界面组件上所发生事件的相关信息,一般通过事件Event对象来传递。
    3)事件监听器Event Listener:负责监听事件源发生的事件,并对不同的事件做相应的处理。

实现事件监听器的方法:
1)内部类形式:将事件监听器类定义在当前类的内部。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn_sendmsg = (Button)findViewById(R.id.btn_sendmsg);
        btn_sendmsg.setOnClickListener(new MyListener());
    }

    class MyListener implements View.OnClickListener{
        @Override
        public void onClick(View v){
            Intent intent = new Intent();
            intent.setClass(MainActivity.this,Main2Activity.class);
            startActivity(intent);
        }
    }
}

2)外部类形式:将事件监听器类定义成一个外部类。
使用外部顶级类定义事件监听器的形式比较少见,主要原因如下:
    1)事件监听器通常属于特定的GUI界面,定义成外部类不利于提高程序的内聚性。
    2)外部类形式的事件监听器不能自由访问创建GUI界面的类中的组件,编程不简介。
    

public class SendSmsListener implements OnLongClickListener
{
    private Activity act;
    private EditText address;
    private EditText content;

    public SendSmsListener(Activity act, EditText address, EditText content)
    {
        this.act = act;
        this.address = address;
        this.content = content;
    }

    @Override
    public boolean onLongClick(View source)
    {
        String addressStr = address.getText().toString();
        String contentStr = content.getText().toString();
        SmsManager smsManager = SmsManager.getDefault();
        PendingIntent sentIntent = PendingIntent.getBroadcast(act, 0, new Intent(), 0);

        try {
            smsManager.sendTextMessage(addressStr, null, contentStr, sentIntent, null);
        }
        catch(Exception e){
        System.out.print(e.toString());
    }
        Toast.makeText(act, "短信发送完成", Toast.LENGTH_LONG).show();
        return false;
    }
}

public class Main2Activity extends AppCompatActivity {

    EditText address;
    EditText content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sendmsg);

        address = (EditText)findViewById(R.id.txtAddress);
        content = (EditText)findViewById(R.id.txtContent);
        Button bn = (Button)findViewById(R.id.btn_send);
        bn.setOnLongClickListener(new SendSmsListener(this , address, content));

    }
}

3)Activity本身作为事件监听器类:让Activity本身实现监听器接口,并实现事件处理方法。

public class ActivityListener extends Activity implements View.OnClickListener {

    EditText show;
    Button btn;

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        show=(EditText)findViewById(R.id.show);
        btn = (Button)findViewById(R.id.btn);

        btn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v)
    {
        show.setText("btn is clicked!!!");
    }
}

4)匿名内部类形式:使用匿名内部类创建事件监听器对象。

 public class MainActivity extends AppCompatActivity {

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

        Button btn_mybutton = (Button) findViewById(R.id.myButton);
        btn_mybutton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "匿名内部类的方式实现事件处理", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

5)直接绑定到标签

直接在布局文件中为指定标签绑定事件处理方法。
格式如: android:onClick="clickButton"
并在类中实现事件处理方法clickButton(View view)。
有个有意思的比喻:说android的事件处理机制是一种委派式事件处理方式,普通UI组件将发生的事件交给监听器去处理,自己不处理。好比如:发生火灾了,交给消防局处理,发生打架了交给公安局处理,并且消防局和公安局又可以同时处理多个火灾和打架事件。  这样的委派式处理方式提高了程序的可维护性。挺好。

2、基于回调的事件处理

相比基于监听器的事件处理模型,基于回调的事件处理模型要简单些,该模型中,事件源和事件监听器是合一的,也就是说没有独立的事件监听器存在。当用户在GUI组件上触发某事件时,由该组件自身特定的函数负责处理该事件。通常通过重写Override组件类的事件处理函数实现事件的处理。
所有基于回调的事件处理方法都有一个boolean类型的返回值,该返回值用于标识该处理方法能否完全处理该事件(基于回调的事件处理多用在自定义的UI组件中)
    True : 表示该处理方法已完全处理该事件,事件不会传播出去
    False: 没有完全处理该事件,事件会传播出去
 

public class MyButton extends Button
{
   public MyButton(Context context, AttributeSet set)
   {
      super(context, set);
   }
   @Override
   public boolean onKeyDown(int keyCode, KeyEvent event)
   {
      super.onKeyDown(keyCode, event);
      Log.v("-ddd.org-", "the onKeyDown in MyButton");
      return true;
   }
}

3、响应系统设置的事件

public class ConfigrationTest extends Activity
{
    EditText ori;
    EditText navigation;
    EditText touch;
    EditText mnc;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.configration);
        // 获取应用界面中的界面组件
        ori = (EditText)findViewById(R.id.ori);
        navigation = (EditText)findViewById(R.id.navigation);
        touch = (EditText)findViewById(R.id.touch);
        mnc = (EditText)findViewById(R.id.mnc);
        Button bn = (Button)findViewById(R.id.bn);
        bn.setOnClickListener(new OnClickListener()
        {
            // 为按钮绑定事件监听器
            @Override
            public void onClick(View source)
            {
                // 获取系统的Configuration对象
                Configuration cfg = getResources().getConfiguration();
                String screen = cfg.orientation ==
                        Configuration.ORIENTATION_LANDSCAPE
                        ? "横向屏幕": "竖向屏幕";
                String mncCode = cfg.mnc + "";
                String naviName = cfg.orientation ==
                        Configuration.NAVIGATION_NONAV
                        ? "没有方向控制" :
                        cfg.orientation == Configuration.NAVIGATION_WHEEL
                                ? "滚轮控制方向" :
                                cfg.orientation == Configuration.NAVIGATION_DPAD
                                        ? "方向键控制方向" : "轨迹球控制方向";
                navigation.setText(naviName);
                String touchName = cfg.touchscreen ==
                        Configuration.TOUCHSCREEN_NOTOUCH
                        ? "无触摸屏" : "支持触摸屏";
                ori.setText(screen);
                mnc.setText(mncCode);
                touch.setText(touchName);
            }
        });
    }
}

4、Handler消息传递机制

在Android系统中,类Handler主要有以下两个作用

  1. 在新启动的线程中发送消息
  2. 在主线程中获取、处理消息

主程序处理新启动线程所发的消息,只能通过回调的方式来实现,只要重写Handler类中处理消息的方法,当新线程启动时,消息会发送到与之关联的MessageQueue,而Handler会不断地从MessageQueue中获取并处理消息。

public class HandlerTest extends Activity
{
   // 定义周期性显示的图片的ID
   int[] imageIds = new int[]
   {
      R.drawable.java,
      R.drawable.ee,
      R.drawable.ajax,
      R.drawable.xml,
      R.drawable.classic
   };
   int currentImageId = 0;

   @Override
   public void onCreate(Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);
      final ImageView show = (ImageView) findViewById(R.id.show);
      final Handler myHandler = new Handler()
      {
         @Override
         public void handleMessage(Message msg)
         {
            // 如果该消息是本程序所发送的
            if (msg.what == 0x1233)
            {
               // 动态地修改所显示的图片show.setImageResource(imageIds[currentImageId++
                  % imageIds.length]);
            }
         }
      };
      // 定义一个计时器,让该计时器周期性地执行指定任务
      new Timer().schedule(new TimerTask()
      {
         @Override
         public void run()
         {
            // 发送空消息
            myHandler.sendEmptyMessage(0x1233);
         }
      }, 0, 1200);
   }
}

Handler、Loop、MessageQueue的工作原理:
Message:Handler接收和处理的消息对象
Looper:每个线程只能拥有一个Looper。它的looper()方法负责读取MessageQueue中的消息,读取到消息后就把消息交给Handler处理
MessageQueue:消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时,会在它的构造器中创建MessageQueue对象。
Handler:要Handler正常工作,必须在当前线程中有一个Looper对象,为了保证当前线程中有looper对象分两种情况

  1. 在主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Hanler即可。
  2. 程序员自己启动的子线程,必须自己创建一个Looper对象,并启动它。创建Looper对象调用它的prepare()方法即可。

Prepare方法保证每个线程最多只有一个Looper对象。接下来调用Looper的静态方法loop()方法来启动它。

public class CalPrime  extends Activity {

    static final String UPPER_NUM = "upper";
    EditText etNum;
    Button bn;
    CalThread calThread;
    String num = null;

    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(CalPrime.this, nums.toString(), Toast.LENGTH_LONG).show();
                    }
                }
            };
            Looper.loop();
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        etNum = (EditText)findViewById(R.id.etNum);

        bn = (Button)findViewById(R.id.bn);
        bn.setOnClickListener(new View.OnClickListener(){

                                  public void onClick(View v)
                                  {
                                      cal();
                                  }
                              }

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

    public void cal()
    {
        Message msg = new Message();
        msg.what = 0x123;
        Bundle bundle = new Bundle();
        bundle.putInt(UPPER_NUM, Integer.parseInt(etNum.getText().toString()));
        msg.setData(bundle);
        calThread.mHandler.sendMessage(msg);
    }

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

5、异步任务操作

Android中实现异步任务有两种方式,分别是Handler和AsyncTask.
Handler方式需要为每一个任务创建一个线程,这种方式对于整个过程控制比较精细,缺点是代码臃肿,多个任务同时执行时不易对线程进行精确控制。
AsyncTask适用于简单的异步处理,不需要借助于线程和Handler即可实现。

public class AsynchronousTest extends Activity {

    private static final String TAG = "ASYNC_TASK";
    private Button execute;
    private Button cancel;
    private ProgressBar progressBar;
    private TextView textView;
    private MyTask mTask;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.handlertest);

        execute = (Button) findViewById(R.id.execute);
        execute.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTask = new MyTask();                
                mTask.execute("http://yc.xjlz365.com/zhongcaoyao/");

                execute.setEnabled(false);
                cancel.setEnabled(true);
            }
        });
        cancel = (Button) findViewById(R.id.cancel);
        cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                mTask.cancel(true);
            }
        });
        progressBar = (ProgressBar) findViewById(R.id.progress_bar);
        textView = (TextView) findViewById(R.id.text_view);
    }

    private class MyTask extends AsyncTask<String, Integer, String> {
        @Override
        protected void onPreExecute() {
            Log.i(TAG, "onPreExecute() called");
            textView.setText("loading...");
        }

        @Override
        protected String doInBackground(String... params) {
            Log.i(TAG, "doInBackground(Params...params)called");
            try {
                HttpClient client = new DefaultHttpClient();
                HttpGet get = new HttpGet(params[0]);
                HttpResponse response = client.execute(get);
                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                    HttpEntity entity = response.getEntity();
                    InputStream is = entity.getContent();
                    long total = entity.getContentLength();
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    byte[] buf = new byte[1024];
                    int count = 0;
                    int length = -1;
                    while ((length = is.read(buf)) != -1) {
                        baos.write(buf, 0, length);
                        count += length;
                        publishProgress((int) ((count / (float) total) * 100));
                        Thread.sleep(500);
                    }
                    return new String(baos.toByteArray(), "gb2312");

                }

            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... progresses) {
            Log.i(TAG, "onProgressUpdate(Progress... progresses)called");
            progressBar.setProgress(progresses[0]);
            textView.setText("loading..." + progresses[0] + "%");
        }

        @Override
        protected void onPostExecute(String result) {
            Log.i(TAG, "onPostExecute(Result result)called");
            textView.setText(result);
            execute.setEnabled(true);
            cancel.setEnabled(false);
        }

        @Override
        protected void onCancelled() {
            Log.i(TAG, "onCancelled()called");
            textView.setText("cancelled");
            progressBar.setProgress(0);

            execute.setEnabled(true);
            cancel.setEnabled(false);
        }
    }
}