Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读

时间:2023-03-09 21:38:34
Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读

 编者按

JRedu

  杰瑞教育原创系列教材将于年后与大家正式见面。为更好的借鉴读者意见,我们将会陆续地在博客园推出一系列教材试读。我们也热忱的欢迎广大博友与我们互动,提出宝贵意见。

  本篇博客将推出教材第三章第二部分的试读(传送门:第一部分),请大家继续提出宝贵意见,我们也将为积极互动的博友,免费提供我们的原创教材以及更多福利,也欢迎大家加入最下方QQ群与我们交流,谢谢大家!

3.5 系统设置事件处理

3.5.1onConfigurationChanged响应事件

  在App开发过程中,有时候需要获取手机设备的配置信息或者根据手机设备配置信息的更改而调整App,比如屏幕方向,触摸屏的触摸方式等。通过重写Activity的onConfigurationChanged方法,可以监听系统设置的更改。

  通过点击一个按钮进行横竖切换的案例来演示onConfigurationChanged的具体用法。Java程序代码如下,通过重写onConfigurationChanged的方法获取最新的设备配置信息。

程序清单3-11 系统设置事件处理
public class MainActivity extends AppCompatActivity {
private TextView show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
show = (TextView)findViewById(R.id.show);
show.setText("方向未发生变化");
findViewById(R.id.btnChange).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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); //将屏幕方向改为横屏
}
}
});
} @Override
public void onConfigurationChanged(Configuration newConfig) { //重写onConfigurationChanged方法,最新的系统配置信息
super.onConfigurationChanged(newConfig);
String screenOrientation="方向未发生变化";
if(newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE){
screenOrientation="横屏";
}else if(newConfig.orientation==Configuration.ORIENTATION_PORTRAIT){
screenOrientation="竖屏";
}
show.setText(screenOrientation); //将最新的屏幕信息显示在屏幕上
}
}

  上面程序中,在按钮的单击事件监听器的实现类中通过代码getResources().getConfiguration()获取了系统配置对象Configuration。Configuration为系统配置信息类,具体内容在下节中详细讲解。根据Configuration中封装的屏幕方向信息,调用Activity的setRequestedOrientation(int)方法更改屏幕方向。

  布局文件中只有一个按钮和文本框,在此不再给出。此外为了能让Activity能够监听系统配置更改事件,必须为Activity设置android:configChanges属性。要想监听屏幕方向向改变的事件,需要设置为orientation|screenSize。

程序清单3-1 布局文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jredu.configuration">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize"> //设置为orientation|screenSize用来监听屏幕方向变化事件
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application> </manifest>

  这里需要注意的是如果只为android:configChanges属性配置了orientation,则必须保证builder.gradle文件中的targetSdkVersion最大值为12,否则onConfigurationChanged不会被触发。

  程序运行后效果如图3-6。

Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读

(图3-6)

3.5.2Configuration配置类

  在上一节的案例中使用到了Configuration,在本节中将会详细介绍Configuration类。该类用来描述了设备的所有配置信息,这些信息会影响到程序检索的资源,包括用户指定的选项以及设备配置,如输入模式、屏幕大小和屏幕方向等。

  Configuration使用了属性描述了系统的配置信息,具体内容如表3-5.

表3-5 Configuration属性
属性
说明
public int densityDpi 屏幕密度
public float fontScale 当前用户设置的字体的缩放因子
public int hardKeyboardHidden 用于标识硬键盘是否隐藏
public int keyboard 获取当前设备关联的键盘类型。有如下值:KEYBOARD_12KEY(只有12个键的小键盘)、KEYBOARD_NOKEYS、KEYBOARD_QWERTY(普通键盘)
public int keyboardHidden 表示当前键盘是否可用
public Locale locale 获取当前用户的语言环境。
public int mcc 获取移动信号的国家编码
public int mnc 获取移动信号的网络吗
public int navigation 判断系统上方向导航设备的类型。该属性的返回值:NAVIGATION_NONAV(无导航)、 NAVIGATION_DPAD(DPAD导航)NAVIGATION_TRACKBALL(轨迹球导航)、NAVIGATION_WHEEL(滚轮导航)
public int navigationHidden 用于标识导航是否可用。
public int orientation 获取系统屏幕的方向。该属性的返回值:ORIENTATION_LANDSCAPE(横向屏幕)、ORIENTATION_PORTRAIT(竖向屏幕)
public int screenHeightDp 屏幕可用高度,单位为dp
public int screenWidthDp 屏幕可用宽度,单位为dp
public int touchscreen 获取系统触摸屏的触摸方式。该属性的返回值:TOUCHSCREEN_NOTOUCH(无触摸屏)、TOUCHSCREEN_STYLUS(触摸笔式触摸屏)、TOUCHSCREEN_FINGER(接收手指的触摸屏)

  在Activity中,可以通过如下方法获取Configuration对象:

Configuration cfg = getResources().getConfiguration();

  下面通过一个实例来讲解Configuration的具体用法,该程序用于获取设备的各种配置信息。

程序清单3-13 获取Configuration信息
public class ConfigurationActivity extends AppCompatActivity {

    private TextView show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_configuration);
show = (TextView)findViewById(R.id.show);
}
public void getConfiguration(View v){ //获取Configuration对象
Configuration cfg = getResources().getConfiguration();
StringBuilder sbConfiguration=new StringBuilder();
sbConfiguration.append("屏幕密度:").append(cfg.densityDpi).append("\n");
sbConfiguration.append("字体缩放因子:").append(cfg.fontScale).append("\n");
sbConfiguration.append("键盘类型:").append(getKeyboard(cfg.keyboard)).append("\n");
sbConfiguration.append("语言环境:").append(cfg.locale.getDisplayName()).append("\n");
sbConfiguration.append("国家编码:").append(cfg.mcc).append("\n");
sbConfiguration.append("网络编码:").append(cfg.mnc).append("\n");
String orientation=cfg.orientation==Configuration.ORIENTATION_LANDSCAPE?"横屏":"竖屏";
sbConfiguration.append("屏幕方向:").append(orientation).append("\n");
sbConfiguration.append("可用高度:").append(cfg.screenHeightDp).append("\n");
sbConfiguration.append("可用宽度:").append(cfg.screenWidthDp).append("\n");
show.setText(sbConfiguration.toString());
}
public String getKeyboard(int keyboard){ //根据键盘类型码,得到键盘名称。
String name=null;
switch (keyboard){
case Configuration.KEYBOARD_12KEY:
name="KEYBOARD_12KEY";
break;
case Configuration.KEYBOARD_NOKEYS:
name="KEYBOARD_NOKEYS";
break;
case Configuration.KEYBOARD_QWERTY:
name="KEYBOARD_QWERTY";
break;
case Configuration.KEYBOARD_UNDEFINED:
name="KEYBOARD_UNDEFINED";
break;
}
return name;
}
}

在上面程序中获取Configuration对象之后,通过Configuration的属性来获取系统的各项配置信息。运行程序后,点击界面中的按钮,可以获取如图3-7中的配置信息。

Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读

 (图3-7)

3.6 Handler消息处理机制

  在Android中UI的操作并不是线程安全的,为了解决这个问题Android提供了Handler消息传递机制。

Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读

(图3-8)

3.6.1Handler类

  Handler最主要的作用就是在子线程中发送消息,在UI线程中接收消息并处理消息。下面通过一个具体案例来演示Handler的用法。

  案例Java代码如下:

程序清单3-14 Handler消息处理机制
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView show;
private int counter = 0; //计数变量
private boolean flag = true; //线程运行标识
private static final int MSG_COUNTER=1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
show = (TextView)findViewById(R.id.show);
findViewById(R.id.btnStart).setOnClickListener(this);
findViewById(R.id.btnEnd).setOnClickListener(this);
} private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) { //定义一个Handler对象,并重写handleMessage方法,用于处理接收的消息。该方法运行在UI线程中
super.handleMessage(msg);
if(msg.what==MSG_COUNTER){
counter++;
show.setText(counter+"");
}
}
};
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnStart:
flag = true;
new Thread(new Runnable() {
@Override
public void run() {
while(flag) {
Message msg = mHandler.obtainMessage(MSG_COUNTER); //创建消息对象
mHandler.sendMessage(msg); //发送消息
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
break;
case R.id.btnEnd:
flag = false;
break;
}
}
}

  在上面的程序中定义了一个Handler对象,并重写了handleMessage(Message msg)方法,该方法用来处理接收到的消息。该程序界面中有两个按钮,当点击“开始”按钮是,会启动一个子线程。在子线程中,每隔500毫秒,Handler对象就会向主线程发送一个消息。

  该程序的布局文件如下:

程序清单3-15 Handler消息处理机制布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.jredu.handler.MainActivity"> <TextView
android:textSize="20sp"
android:id="@+id/show" //显示计数的文本
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginBottom="20dp"
android:textColor="#f00" /> <Button
android:id="@+id/btnStart" //开始计数按钮
android:layout_below="@id/show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="开始"
android:textSize="16sp"/>
<Button
android:id="@+id/btnEnd" //停止计数按钮
android:layout_below="@id/show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="结束"
android:textSize="16sp"/>
</RelativeLayout>

  布局文件较为简单,含有一个文本和两个按钮,运行上述程序,效果界面如图3-9所示。

Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读

(图3-9) 

  通过上面的案例,我们大体知道了如何使用Handler,总结起来总共两个步骤。

1、在主线程中定义Handler对象,并重写handlerMessage方法用于处理接收到的Message对象。该方法运行在主线程中,可以操作UI组件,是线程安全的。

2、在子线程中使用主线程中定义好的Handler对象,将子线程的数据封装到Message中并发送的主线程。

  Handler类中包含的用于处理和发送消息的方法如表3-6.

表3-6
方法 说明
void handleMessage(Message msg) 用于处理消息的方法。
final boolean hasMessage(int what) 检查消息队列中是否有包含what属性值得消息。
Message obtainMessage() 该方法有多个重载,可以获取重用的消息。
final boolean sendEmptyMessage(int what) 发送一个编号为what的空消息。
final boolean sendEmptyMessageDelayed(int what,long delayMillis) 指定多长时间后发送空消息,时间单位为毫秒。
final boolean sendMessage(Message msg) 立即发送消息。
final boolean sendMessageDelayed(Message msg,long delayMillis) 指定时间后发送消息,时间单位为毫秒。

3.6.2Handler消息机制的原理

Handler消息机制工作原理如图3-10。

Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读

(图3-10)

  从图3-10,我们可以知道Handler工作原理中涉及到几个组件,具体如下:

1、Message:消息,由Handler发送、接收和处理。常用的属性有:

1)        public int what:用于定义消息的识别码。

2)        public int arg1:存储用于传递的整型数据。

3)        public int arg2:存储用于传递的整型数据。

4)        public Object obj:存储用于传递的数据。

2、MessageQueue:消息队列,采用先进先出的方式来管理Message。

3、Looper:每个线程只能有一个Looper。当创建一个Looper时,会自动创建一个MessageQueue与之关联。Looper主要作用就是不断从MessageQueue中取出Message分发给对象的Handler进行处理。在Android中当启动主线程时,默认已经关联了一个Looper。

4、Handler:可以将Message发送到对应Looper管理的MessageQueue中,并负责处理Looper从MessageQueue中取出的消息。

3.7 AsyncTask异步任务类

  主线程主要负责界面事件的处理,因此不能在主线程中处理一些耗时的操作,否则会出现ANR,即Application Not Responding。为了避免出现ANR,需要将耗时操作放到子线程中进行处理,同时可以使用Handler进行消息的传递。除了使用Handler,Android为了简化操作为我们提供了一个封装好的异步任务类AsyncTask。

  AsyncTask<Params,Progress,Result>是一个抽象类,使用的时候需要继承该类并需要传递三个泛型参数,分别为:

  1. Params:后台任务执行所需要的参数。
  2. Progress:后台任务执行的进度。
  3. Result:后台任务执行的结果。

  如果不需要某个参数的时候,可以使用Void进行替换。根据需要可以重写AsyncTask中的相关方法。AsyncTask中方法执行顺序及关系可参照图3-11。

Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读

 (图3-11)

  AsyncTask中常用方法如表3-7。

表3-7
方法
说明
onPreExecuter() 该方法在主线程中执行,在后台操作执行前被调用,主要用于初始化操作。
doInBackground(Params...) 该方法用于完成耗时操作,运行在子线程中。在该方法中可以调用publishProgress(Progress...)方法公布执行进度。
onPostExecute(Result r) 当doInBackground方法执行完成后,会调用该方法,并将操作结果传递给该方法。该方法运行在主线程中。
publishProgress(Progress...) 在doInBackground方法中调用,对外公布执行进度。该方法一旦被调用,将会执行onProgressUpdate方法。该方法在子线程执行。
onProgressUpdate(Progress...) 该方法运行在主线程中。用于接收publishProgress公布的进度信息。

  使用AsyncTask需要注意以下几点:

  1. AsyncTask对象只能在主线程中创建。
  2. AsyncTask对象的执行方法execute(Params... params)只能在主线程中调用。
  3. AsyncTask中的方法,如doInBackground、onPreExecute等是由系统调用的。
  4. AsyncTask对象的只能被执行一次。

  下面通过一个案例来讲解AsyncTask的具体用法。Java程序如下:

程序清单3-16 AsyncTask异步任务类
public class AnsyncTaskActivity extends AppCompatActivity {
private ProgressBar progressBar;
private TextView show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ansync_task);
progressBar = (ProgressBar)findViewById(R.id.progressBar);
show=(TextView)findViewById(R.id.show);
findViewById(R.id.btnDownload).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DownloadTask task= new DownloadTask(); //创建异步任务类对象
task.execute(); //调用execute方法,执行异步任务
}
});
} private class DownloadTask extends AsyncTask<Void,Integer,String>{ //自定义异步任务类
@Override
protected void onPreExecute() {
super.onPreExecute();
progressBar.setMax(100);
progressBar.setProgress(0); //重写onPreExecute,进行初始化操作:将进度条最大值设为100,当前进度为0.
show.setText("开始下载……");
}
@Override
protected void onPostExecute(String result) { //处理后台返回的结果
super.onPostExecute(result);
show.setText(result);
} @Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
int curProgress = values[0];
progressBar.setProgress(curProgress); //获取后台公布的任务进度,并展示到进度条和文本框内。
show.setText("当前进度为:"+curProgress+"%");
}
@Override
protected String doInBackground(Void... params) { //后台任务,用于执行耗时操作,运行在子线程中。
for(int i=1;i<=100;i++){
publishProgress(i); //调用publisProgress对外公布任务进度。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "下载完成!";
}
}
}

  上面程序模拟了网络下载过程,通过继承AsyncTask定义了一个DownloadTask异步任务类。在该类重新了onPreExecute、doInBackground、onPostExecute、onProgressUpdate等方法。在onPreExecute方法中进行了初始化操作;在doInBackground方法模拟下载并调用publishProgress方法对外公布进度,该方法执行完成后将结果返回给onPostExecute;方法onPostExecute进行处理结果。当doInBackground调用publisProgress时,主线程会调用onProgressUpdate方法获取publishProgress公布的进度并更新界面数据。

  该程序的布局界面较为简单,包括一个TextView,一个ProgressBar、一个Button。当点击Button时,开始进行执行异步任务类。布局文件如下:

程序清单3-17 AsyncTask异步任务布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.jredu.handler.AnsyncTaskActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp" //提示信息显示文本框
android:textColor="#f00"
android:id="@+id/show"/>
<ProgressBar
android:layout_marginTop="20dp" //进度条
android:layout_below="@id/show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"/> <Button
android:layout_width="match_parent" //按钮,点击该按钮可执行异步任务
android:layout_height="wrap_content"
android:text="下载"
android:textSize="18sp"
android:id="@+id/btnDownload"
android:layout_marginTop="20dp"
android:layout_below="@id/progressBar"/>
</RelativeLayout>

运行上面程序,界面效果如图3-12。

Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读

 (图3-12)

作者:杰瑞教育
出处:http://www.cnblogs.com/jerehedu/ 
版权声明:本文版权归烟台杰瑞教育科技有限公司和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

技术咨询:Android教材 | 第三章 Android界面事件处理(二)—— 杰瑞教育原创教材试读