編者按html
JReduandroid
傑瑞教育原創系列教材將於年後與你們正式見面。爲更好的借鑑讀者意見,咱們將會陸續地在博客園推出一系列教材試讀。咱們也熱忱的歡迎廣大博友與咱們互動,提出寶貴意見。web
本篇博客將推出教材第三章第二部分的試讀(傳送門:第一部分),請你們繼續提出寶貴意見,咱們也將爲積極互動的博友,免費提供咱們的原創教材以及更多福利,也歡迎你們加入最下方QQ羣與咱們交流,謝謝你們!安全
3.5 系統設置事件處理 |
在App開發過程當中,有時候須要獲取手機設備的配置信息或者根據手機設備配置信息的更改而調整App,好比屏幕方向,觸摸屏的觸摸方式等。經過重寫Activity的onConfigurationChanged方法,能夠監聽系統設置的更改。網絡
經過點擊一個按鈕進行橫豎切換的案例來演示onConfigurationChanged的具體用法。Java程序代碼以下,經過重寫onConfigurationChanged的方法獲取最新的設備配置信息。併發
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)方法更改屏幕方向。app
佈局文件中只有一個按鈕和文本框,在此再也不給出。此外爲了能讓Activity可以監聽系統配置更改事件,必須爲Activity設置android:configChanges屬性。要想監聽屏幕方向向改變的事件,須要設置爲orientation|screenSize。異步
<?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不會被觸發。ide
程序運行後效果如圖3-6。oop
(圖3-6)
在上一節的案例中使用到了Configuration,在本節中將會詳細介紹Configuration類。該類用來描述了設備的全部配置信息,這些信息會影響到程序檢索的資源,包括用戶指定的選項以及設備配置,如輸入模式、屏幕大小和屏幕方向等。
Configuration使用了屬性描述了系統的配置信息,具體內容如表3-5.
屬性 | 說明 |
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的具體用法,該程序用於獲取設備的各類配置信息。
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中的配置信息。
(圖3-7)
3.6 Handler消息處理機制 |
在Android中UI的操做並非線程安全的,爲了解決這個問題Android提供了Handler消息傳遞機制。
(圖3-8)
Handler最主要的做用就是在子線程中發送消息,在UI線程中接收消息並處理消息。下面經過一個具體案例來演示Handler的用法。
案例Java代碼以下:
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對象就會向主線程發送一個消息。
該程序的佈局文件以下:
<?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所示。
(圖3-9)
經過上面的案例,咱們大致知道了如何使用Handler,總結起來總共兩個步驟。
一、在主線程中定義Handler對象,並重寫handlerMessage方法用於處理接收到的Message對象。該方法運行在主線程中,能夠操做UI組件,是線程安全的。
二、在子線程中使用主線程中定義好的Handler對象,將子線程的數據封裝到Message中併發送的主線程。
Handler類中包含的用於處理和發送消息的方法如表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) | 指定時間後發送消息,時間單位爲毫秒。 |
Handler消息機制工做原理如圖3-10。
(圖3-10)
從圖3-10,咱們能夠知道Handler工做原理中涉及到幾個組件,具體以下:
一、Message:消息,由Handler發送、接收和處理。經常使用的屬性有:
1) public int what:用於定義消息的識別碼。
2) public int arg1:存儲用於傳遞的整型數據。
3) public int arg2:存儲用於傳遞的整型數據。
4) public Object obj:存儲用於傳遞的數據。
二、MessageQueue:消息隊列,採用先進先出的方式來管理Message。
三、Looper:每一個線程只能有一個Looper。當建立一個Looper時,會自動建立一個MessageQueue與之關聯。Looper主要做用就是不斷從MessageQueue中取出Message分發給對象的Handler進行處理。在Android中當啓動主線程時,默認已經關聯了一個Looper。
四、Handler:能夠將Message發送到對應Looper管理的MessageQueue中,並負責處理Looper從MessageQueue中取出的消息。
3.7 AsyncTask異步任務類 |
主線程主要負責界面事件的處理,所以不能在主線程中處理一些耗時的操做,不然會出現ANR,即Application Not Responding。爲了不出現ANR,須要將耗時操做放到子線程中進行處理,同時可使用Handler進行消息的傳遞。除了使用Handler,Android爲了簡化操做爲咱們提供了一個封裝好的異步任務類AsyncTask。
AsyncTask<Params,Progress,Result>是一個抽象類,使用的時候須要繼承該類並須要傳遞三個泛型參數,分別爲:
若是不須要某個參數的時候,可使用Void進行替換。根據須要能夠重寫AsyncTask中的相關方法。AsyncTask中方法執行順序及關係可參照圖3-11。
(圖3-11)
AsyncTask中經常使用方法如表3-7。
方法 |
說明 |
onPreExecuter() | 該方法在主線程中執行,在後臺操做執行前被調用,主要用於初始化操做。 |
doInBackground(Params...) | 該方法用於完成耗時操做,運行在子線程中。在該方法中能夠調用publishProgress(Progress...)方法公佈執行進度。 |
onPostExecute(Result r) | 當doInBackground方法執行完成後,會調用該方法,並將操做結果傳遞給該方法。該方法運行在主線程中。 |
publishProgress(Progress...) | 在doInBackground方法中調用,對外公佈執行進度。該方法一旦被調用,將會執行onProgressUpdate方法。該方法在子線程執行。 |
onProgressUpdate(Progress...) | 該方法運行在主線程中。用於接收publishProgress公佈的進度信息。 |
使用AsyncTask須要注意如下幾點:
下面經過一個案例來說解AsyncTask的具體用法。Java程序以下:
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時,開始進行執行異步任務類。佈局文件以下:
<?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。
(圖3-12)