A. android 資料庫怎麼監聽數據變化
在android中經常會用到改變資料庫內容後再去使用資料庫更新的內容,很多人會重新去query一遍,但是這樣的問題就是程序會特別占內存,而且有可能會摟關cursor而導致程序內存未釋放等等。其實android內部提供了一種ContentObserver的東西來監聽資料庫內容的變化。
ContentObserver的構造函數需要一個參數Hanlder,因為ContentObserver內部使用了一個實現Runnable介面的內部類NotificationRunnable,來實現資料庫內容的變化。需要使用hanlder去post消息。注冊ContentObserver的方法是:getContentResolver().registerContentObserver(uri, notifyForDescendents, observer).
上面3個參數為:uri----Uri類型,是需要監聽的資料庫的uri.
notifyForDescendents---boolean true的話就會監聽所有與此uri相關的uri。false的話則是直接特殊的uri才會監聽。一般都設置為true.
observer-----ContentObserver 就是需要的contentobserver.
初始化一個ContentObserver對象,重載onChange(boolean ),在這個方法里去操作資料庫的使用,針對變化後的使用。
寫了一個小demo,可以參考下。提示這種監聽方式必須是contentprovider才能使用,因為contentprovider有uri.簡單的那種sqlite資料庫沒有uri是使用不了的。
下面demo操作的是在一個activityA里點擊button跳轉至另外一個activityB,在B中點擊button往資料庫中加數據,加完後關閉B回到A。A的button的文字自動變化設置到資料庫中的字元串。[code]
packagety.com.lto;
02
03importandroid.app.Activity;
04importandroid.content.Intent;
05importandroid.database.ContentObserver;
06importandroid.os.Bundle;
07importandroid.os.Handler;
08importandroid.view.View;
09importandroid.widget.Button;
10
{
12privateButtontestBtn;
13
14@Override
15protectedvoidonCreate(BundlesavedInstanceState){
16super.onCreate(savedInstanceState);
17setContentView(R.layout.listen_data_test);
18getContentResolver().registerContentObserver(DataChangeProvider.CONTENT_URI,
19true,cob);
20
21testBtn=(Button)findViewById(R.id.test_btn);
22testBtn.setOnClickListener(newView.OnClickListener(){
23
24publicvoidonClick(Viewv){
25Intentin=newIntent(ListenDataTest.this,DataChangeTest.class);
26startActivity(in);
27
28}
29});
30
31}
32
33privateContentObservercob=newContentObserver(newHandler()){
34
35@Override
(){
37returnsuper.deliverSelfNotifications();
38}
39
40@Override
41publicvoidonChange(booleanselfChange){
42super.onChange(selfChange);
43testBtn.setText(DataUtils.getChangeName(getApplicationContext()));
44}
45
46};
47
48@Override
49protectedvoidonDestroy(){
50super.onDestroy();
51getContentResolver().unregisterContentObserver(cob);
52}
53
54
55}
[code]01packagety.com.lto;
02
03importandroid.app.Activity;
04importandroid.content.ContentValues;
05importandroid.content.Intent;
06importandroid.database.ContentObservable;
07importandroid.database.ContentObserver;
08importandroid.os.Bundle;
09importandroid.os.Handler;
10importandroid.view.View;
11importandroid.widget.Button;
12
{
14privateButtondataBtn;
15DataSqlitemDataSqlite;
16@Override
17protectedvoidonCreate(BundlesavedInstanceState){
18super.onCreate(savedInstanceState);
19setContentView(R.layout.data_change_test);
20dataBtn=(Button)findViewById(R.id.data_test_btn);
21mDataSqlite=newDataSqlite(this);
22dataBtn.setOnClickListener(newView.OnClickListener(){
23
24publicvoidonClick(Viewv){
25ContentValuescon=newContentValues();
26con.put("name","數據變化了");
27getContentResolver().insert(DataChangeProvider.CONTENT_URI,con);
28finish();
29}
30});
31}
32}
[code]viewsourceprint?
001packagety.com.lto;
002
003
004importandroid.content.ContentProvider;
005importandroid.content.ContentUris;
006importandroid.content.ContentValues;
007importandroid.content.Context;
008importandroid.content.UriMatcher;
009importandroid.database.Cursor;
010importandroid.database.SQLException;
011importandroid.database.sqlite.SQLiteDatabase;
012importandroid.database.sqlite.SQLiteOpenHelper;
013importandroid.database.sqlite.SQLiteQueryBuilder;
014importandroid.database.sqlite.SQLiteDatabase.CursorFactory;
015importandroid.net.Uri;
016importandroid.text.TextUtils;
017
{
;
=1;
_ID=2;
=newUriMatcher(UriMatcher.NO_MATCH);
_URI=Uri.parse("content://ty.com.lto/test");
024
025static{
026sURLMatcher.addURI("ty.com.lto","test",ALARMS);
027sURLMatcher.addURI("ty.com.lto","test/#",ALARMS_ID);
028}
029
{
_DATABASE="test.db";
=1;
033
034publicDatabaseHelper(Contextcontext){
035super(context,TEST_DATABASE,null,VERSION);
036//TODOAuto-generatedconstructorstub
037}
038
039
040@Override
041publicvoidonCreate(SQLiteDatabasedb){
042Stringsql="CREATETABLE"+"test"+"("+
043"_idINTEGERPRIMARYKEY,"+
044"nameTEXT"+
045");";
046db.execSQL(sql);
047}
048
049@Override
050publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){
051Stringsql="DROPTABLEIFEXIST"+TEST_DATABASE;
052db.execSQL(sql);
053onCreate(db);
054}
055
056}
057
058publicDataChangeProvider(){
059}
060
061@Override
062publicintdelete(Uriurl,Stringwhere,String[]whereArgs){
063SQLiteDatabasedb=mOpenHelper.getWritableDatabase();
064intcount;
065longrowId=0;
066switch(sURLMatcher.match(url)){
067caseALARMS:
068count=db.delete("test",where,whereArgs);
069break;
070caseALARMS_ID:
071Stringsegment=url.getPathSegments().get(1);
072
B. android怎麼鏈接資料庫mysql
有點多請耐心看完。
希望能幫助你,還請及時採納謝謝。
一.前言
android連接資料庫的方式有兩種,第一種是通過連接伺服器,再由伺服器讀取資料庫來實現數據的增刪改查,這也是我們常用的方式。第二種方式是android直接連接資料庫,這種方式非常耗手機內存,而且容易被反編譯造成安全隱患,所以在實際項目中不推薦使用。
二.准備工作
1.載入外部jar包
在Android工程中要使用jdbc的話,要導入jdbc的外部jar包,因為在Java的jdk中並沒有jdbc的api,我使用的jar包是mysql-connector-java-5.1.18-bin.jar包,網路上有使用mysql-connector-java-5.1.18-bin.jar包的,自己去用的時候發現不兼容,所以下載了比較新版本的,jar包可以去官網下載,也可以去網路,有很多前人們上傳的。
2.導入jar包的方式
方式一:
可以在項目的build.gradle文件中直接添加如下語句導入
compile files('libs/mysql-connector-java-5.1.18-bin.jar')
方式二:下載jar包復制到項目的libs目錄下,然後右鍵復制過來的jar包Add as libs
三.建立資料庫連接
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jdbc);
new Thread(runnable).start();
}
Handler myHandler=new Handler(){
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
Bundle data=new Bundle();
data=msg.getData();
//System.out.println("id:"+data.get("id").toString()); //輸出第n行,列名為「id」的值
Log.e("TAG","id:"+data.get("id").toString());
TextView tv= (TextView) findViewById(R.id.jdbc);
//System.out.println("content:"+data.get("content").toString());
}
};
Runnable runnable=new Runnable() {
private Connection con = null;
@Override
public void run() {
// TODO Auto-generated method stub
try {
Class.forName("com.mysql.jdbc.Driver");
//引用代碼此處需要修改,address為數據IP,Port為埠號,DBName為數據名稱,UserName為資料庫登錄賬戶,Password為資料庫登錄密碼
con =
//DriverManager.getConnection("jdbc:mysql://192.168.1.202:3306/b2b", "root", "");
DriverManager.getConnection("jdbc:mysql://http://192.168.1.100/phpmyadmin/index.php:8086/b2b",
UserName,Password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
testConnection(con); //測試資料庫連接
} catch (java.sql.SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void testConnection(Connection con1) throws java.sql.SQLException {
try {
String sql = "select * from ecs_users"; //查詢表名為「oner_alarm」的所有內容
Statement stmt = con1.createStatement(); //創建Statement
ResultSet rs = stmt.executeQuery(sql); //ResultSet類似Cursor
//<code>ResultSet</code>最初指向第一行
Bundle bundle=new Bundle();
while (rs.next()) {
bundle.clear();
bundle.putString("id",rs.getString("userid"));
//bundle.putString("content",rs.getString("content"));
Message msg=new Message();
msg.setData(bundle);
myHandler.sendMessage(msg);
}
rs.close();
stmt.close();
} catch (SQLException e) {
} finally {
if (con1 != null)
try {
con1.close();
} catch (SQLException e) {}
}
}
};
注意:
在Android4.0之後,不允許在主線程中進行比較耗時的操作(連接資料庫就屬於比較耗時的操作),需要開一個新的線程來處理這種耗時的操作,沒新線程時,一直就是程序直接退出,開了一個新線程處理直接,就沒問題了。
當然,連接資料庫是需要網路的,千萬別忘了添加訪問網路許可權:
<uses-permission android:name=」android.permission.INTERNET」/>
四.bug點
1.導入的jar包一定要正確
2.連接資料庫一定要開啟新線程
3.資料庫的IP一定要是可以ping通的,區域網地址手機是訪問不了的
4.資料庫所在的伺服器是否開了防火牆,阻止了訪問
————————————————
版權聲明:本文為CSDN博主「shuaiyou_comon」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/shuaiyou_comon/article/details/75647355
C. android開啟handler查詢資料庫可以么
1定義Handler對象並初始化,重寫handleMessage()函數
2定義Thread線程對象,通常寫成一個類形式(如class ThreadTest implements Runnable),在run()方法中操作數據,並把數據handler.sendMessage()方法傳輸 到handler對象中,並開啟線程。(注意:該步驟不一定用Thread實現,也可以利用TimeTask實現,具體的操作同樣放在run()方法中)
3在handleMessage()函數中根據不同的數據形式實現不同的方法。
D. Android的handler機制的原理
Android的handler機制的原理分為非同步通信准備,消息發送,消息循環,消息處理。
1、非同步通信准備
在主線程中創建處理器對象(Looper)、消息隊列對象(Message Queue)和Handler對象。
2、消息入隊
工作線程通過Handler發送消息(Message) 到消息隊列(Message Queue)中。
3、消息循環
消息出隊: Looper循環取出消息隊列(Message Queue) 中的的消息(Message)。
消息分發: Looper將取出的消息 (Message) 發送給創建該消息的處理者(Handler)。
4、消息處理
處理者(Handler) 接收處理器(Looper) 發送過來的消息(Message),根據消息(Message) 進行U操作。
handler的作用
handler是android線程之間的消息機制,主要的作用是將一個任務切換到指定的線程中去執行,(准確的說是切換到構成handler的looper所在的線程中去出處理)android系統中的一個例子就是主線程中的所有操作都是通過主線程中的handler去處理的。
Handler的運行需要底層的 messagequeue和 looper做支撐。
E. [Android源碼分析] - 非同步通信Handler機制
一、問題:在Android啟動後會在新進程里創建一個主線程,也叫UI線程( 非線程安全 )這個線程主要負責監聽屏幕點擊事件與界面繪制。當Application需要進行耗時操作如網路請求等,如直接在主線程進行容易發生ANR錯誤。所以會創建子線程來執行耗時任務,當子線程執行完畢需要通知UI線程並修改界面時,不可以直接在子線程修改UI,怎麼辦?
解決方法:Message Queue機制可以實現子線程與UI線程的通信。
該機制包括Handler、Message Queue、Looper。Handler可以把消息/ Runnable對象 發給Looper,由它把消息放入所屬線程的消息隊列中,然後Looper又會自動把消息隊列里的消息/Runnable對象 廣播 到所屬線程里的Handler,由Handler處理接收到的消息或Runnable對象。
1、Handler
每次創建Handler對象時,它會自動綁定到創建它的線程上。如果是主線程則默認包含一個Message Queue,否則需要自己創建一個消息隊列來存儲。
Handler是多個線程通信的信使。比如在線程A中創建AHandler,給它綁定一個ALooper,同時創建屬於A的消息隊列AMessageQueue。然後在線程B中使用AHandler發送消息給ALooper,ALooper會把消息存入到AMessageQueue,然後再把AMessageQueue廣播給A線程里的AHandler,它接收到消息會進行處理。從而實現通信。
2、Message Queue
在主線程里默認包含了一個消息隊列不需要手動創建。在子線程里,使用Looper.prepare()方法後,會先檢查子線程是否已有一個looper對象,如果有則無法創建,因為每個線程只能擁有一個消息隊列。沒有的話就為子線程創建一個消息隊列。
Handler類包含Looper指針和MessageQueue指針,而Looper里包含實際MessageQueue與當前線程指針。
下面分別就UI線程和worker線程講解handler創建過程:
首先,創建handler時,會自動檢查當前線程是否包含looper對象,如果包含,則將handler內的消息隊列指向looper內部的消息隊列,否則,拋出異常請求執行looper.prepare()方法。
- 在 UI線程 中,系統自動創建了Looper 對象,所以,直接new一個handler即可使用該機制;
- 在 worker線程 中,如果直接創建handler會拋出運行時異常-即通過查『線程-value』映射表發現當前線程無looper對象。所以需要先調用Looper.prepare()方法。在prepare方法里,利用ThreadLocal<Looper>對象為當前線程創建一個Looper(利用了一個Values類,即一個Map映射表,專為thread存儲value,此處為當前thread存儲一個looper對象)。然後繼續創建handler, 讓handler內部的消息隊列指向該looper的消息隊列(這個很重要,讓handler指向looper里的消息隊列,即二者共享同一個消息隊列,然後handler向這個消息隊列發送消息,looper從這個消息隊列獲取消息) 。然後looper循環消息隊列即可。當獲取到message消息,會找出message對象里的target,即原始發送handler,從而回調handler的handleMessage() 方法進行處理。
- handler與looper共享消息隊列 ,所以handler發送消息只要入列,looper直接取消息即可。
- 線程與looper映射表 :一個線程最多可以映射一個looper對象。通過查表可知當前線程是否包含looper,如果已經包含則不再創建新looper。
5、基於這樣的機制是怎樣實現線程隔離的,即在線程中通信呢。
核心在於 每一個線程擁有自己的handler、message queue、looper體系 。而 每個線程的Handler是公開 的。B線程可以調用A線程的handler發送消息到A的共享消息隊列去,然後A的looper會自動從共享消息隊列取出消息進行處理。反之一樣。
二、上面是基於子線程中利用主線程提供的Handler發送消息出去,然後主線程的Looper從消息隊列中獲取並處理。那麼還有另外兩種情況:
1、主線程發送消息到子線程中;
採用的方法和前面類似。要在子線程中實例化AHandler並設定處理消息的方法,同時由於子線程沒有消息隊列和Looper的輪詢,所以要加上Looper.prepare(),Looper.loop()分別創建消息隊列和開啟輪詢。然後在主線程中使用該AHandler去發送消息即可。
2、子線程A與子線程B之間的通信。
1、 Handler為什麼能夠實現不同線程的通信?核心點在哪?
不同線程之間,每個線程擁有自己的Handler、消息隊列和Looper。Handler是公共的,線程可以通過使用目標線程的Handler對象來發送消息,這個消息會自動發送到所屬線程的消息隊列中去,線程自帶的Looper對象會不斷循環從裡面取出消息並把消息發送給Handler,回調自身Handler的handlerMessage方法,從而實現了消息的線程間傳遞。
2、 Handler的核心是一種事件激活式(類似傳遞一個中斷)的還是主要是用於傳遞大量數據的?重點在Message的內容,偏向於數據傳輸還是事件傳輸。
目前的理解,它所依賴的是消息隊列,發送的自然是消息,即類似事件中斷。
0、 Android消息處理機制(Handler、Looper、MessageQueue與Message)
1、 Handler、Looper源碼閱讀
2、 Android非同步消息處理機制完全解析,帶你從源碼的角度徹底理解
謝謝!
wingjay

F. 如何生動形象的理解Android Handler消息處理機制
在一個Android 程序開始運行的時候,會單獨啟動一個Process。默認的情況下,所有這個程序中的Activity,Service,Content Provider,Broadcast Receiver(Android 4大組件)都會跑在這個Process。一個Android 程序默認情況下也只有一個Process,但一個Process下卻可以有許多個Thread。在這么多Thread當中,有一個Thread,稱之為UI Thread。UI Thread在Android程序運行的時候就被創建,是一個Process當中的主線程Main Thread,主要是負責控制UI界面的顯示、更新和控制項交互。在Android程序創建之初,一個Process呈現的是單線程模型,所有的任務都在一個線程中運行。因此,UI Thread所執行的每一個函數,所花費的時間都應該是越短越好。而其他比較費時的工作(訪問網路,下載數據,查詢資料庫等),都應該交由子線程去執行,以免阻塞主線程,導致ANR。那麼問題來了,UI 主線程和子線程是怎麼通信的呢。這就要提到這里要講的Handler機制。
簡單來說,handler機制被引入的目的就是為了實現線程間通信的。handler一共幹了兩件事:在子線程中發出message,在主線程中獲取、處理message。聽起來好像so easy,如果面試中讓闡述下Handler機制,這么回答顯然不是面試官想要的答案。忽略了一個最重要的問題:子線程何時發送message,主線程何時獲取處理message。
為了能讓主線程「適時」得處理子線程所發送的message,顯然只能通過回調的方式來實現——開發者只要重寫Handler類中處理消息的方法,當子線程發送消時,Handler類中處理消息的方法就會被自動回調。
Handler類包含如下方法用於發送處理消息
void handleMessage(Message msg):處理消息的方法,該方法通常用於被重寫。
final boolean hasMessage(int what):檢查消息隊列中是否包含what屬性為指定值的消息。
final boolean hasMessage(int what,Object object):檢查消息隊列中是否包含what屬性為指定值且object屬性為指定對象的消息。
sendEmptyMessage(int what)發送空消息。
sendEmptyMessageDelayed(int what,longdelayMillis);指定多少毫秒之後發送空消息。
sendMessage(Message msg)立即發送消息。
sendMessageDelayed(int what,longdelayMillis);指定多少毫秒之後發送消息。
藉助以上方法,就可以自由穿梭於主線程和子線程之中了。
到這里就結束了么?當然沒有。要講的東西才剛剛開始,要知道消息處理這件事,不是handler一個人在戰斗,android的消息處理有三個核心類:Handler,Looper,和Message。其實還有一個MessageQueue(消息隊列),但是Message Queue被封裝到Looper裡面了,不會直接與Message Queue打交道。
Looper的字面意思是「循環裝置」,它被設計用來使一個普通線程變成Looper線程。所謂Looper線程就是循環工作的線程。在程序開發中,經常會需要一個線程不斷循環,一旦有新任務則執行,執行完繼續等待下一個任務,這就是Looper線程。Looper是用於給一個線程添加一個消息隊列(MessageQueue),並且循環等待,當有消息時會喚起線程來處理消息的一個工具,直到線程結束為止。通常情況下不會用到Looper,因為對於Activity,Service等系統組件,Frameworks已經為初始化好了線程(俗稱的UI線程或主線程),在其內含有一個Looper,和由Looper創建的消息隊列,所以主線程會一直運行,處理用戶事件,直到某些事件(BACK)退出。
如果,需要新建一個線程,並且這個線程要能夠循環處理其他線程發來的消息事件,或者需要長期與其他線程進行復雜的交互,這時就需要用到Looper來給線程建立消息隊列。
使用Looper也非常的簡單,它的方法比較少,最主要的有四個:
public static prepare();為線程初始化消息隊列。
public static myLooper();獲取此Looper對象的引用
public static loop();讓線程的消息隊列開始運行,可以接收消息了。
public void quit();退出具體哪個Looper
在整個消息處理機制中,message又叫task,封裝了任務攜帶的信息和處理該任務的handler,這個很好理解,就不做介紹了。
說了這么多,一定沒聽太明白。沒關系,再對Handler運行機製做個總結:
子線程(無looper)借用主線程(有looper)里的Hander發送一條message到主線程,這個message會被主線程放入message queue,主線程裡面有一個looper,在輪詢message queue的時候發現有一條message。調用handler消息處理者,執行handlemessage方法,去處理這個message,就可以在handlemessage的方法裡面更新ui。好像畫面感不是太強,舉個例子吧。試想有一個生產方便麵的車間,這個車間有兩條生產線,一條是生產面餅的,一條是生產調料包的,面餅的生產線比較先進一點,配備了一個工人叫handler,還配備了一個調料包分類循環器。這個車間能生產很多種方便麵,有老壇酸菜,有泡椒鳳爪,有香菇燉雞,有紅燒牛肉等等。其中方便麵的面餅都是一樣的,只有調料包和包裝袋不一樣,包裝袋是根據調料包來定的。那麼生產線運作起來了:工人Handler把子生產線(子線程)上的調料包(message)放到了主生產線(主線程)上的調料包分類循環器(Looper)上,通過輪詢分類篩選後工人Handler確定這是一包合格的老壇酸菜味調料包,於是工人把老壇酸菜調料包和面餅放在一塊(sendmessage),告訴主生產線,這是一包老壇酸菜方便麵,請給這包方便麵包一個老壇酸菜的包裝袋(更新UI).
G. android如何連接SQLServer資料庫我的出錯!怎麼解決
看你的code 在ui線程做資料庫操作,很容易造成anr,可以用handler將資料庫操作放在後台操作,顯示的時候在ui線程。資料庫連接的操作 搜索下一大堆
H. Android中的Handler和AsyncTask的區別
Android平台很多應用使用的都是AsyncTask,而並非Thread和Handler去更新UI,這里Android123給大家說下他們到底有什麼區別,我們平時應該使用哪種解決方案。從Android 1.5開始系統將AsyncTask引入到android.os包中,過去在很早1.1和1.0 SDK時其實官方將其命名為UserTask,其內部是jdk 1.5開始新增的concurrent庫,做過j2ee的網友可能明白並發庫效率和強大性,比Java原始的Thread更靈活和強大,但對於輕量級的使用更為佔用系統資源。Thread是Java早期為實現多線程而設計的,比較簡單不支持concurrent中很多特性在同步和線程池類中需要自己去實現很多的東西,對於分布式應用來說更需要自己寫調度代碼,而為了Android UI的刷新google引入了Handler和Looper機制,它們均基於消息實現,有事可能消息隊列阻塞或其他原因無法准確的使用。 Android開發網推薦大家使用AsyncTask代替Thread+Handler的方式,不僅調用上更為簡單,經過實測更可靠一些,Google在Browser中大量使用了非同步任務作為處理耗時的I/O操作,比如下載文件、讀寫資料庫等等,它們在本質上都離不開消息,但是 AsyncTask相比Thread加Handler更為可靠,更易於維護,但AsyncTask缺點也是有的比如一旦線程開啟即 dobackground方法執行後無法給線程發送消息,僅能通過預先設置好的標記來控制邏輯,當然可以通過線程的掛起等待標志位的改變來通訊,對於某些應用Thread和Handler以及Looper可能更靈活。
I. Android Handler那些事兒,消息屏障IdelHandlerANR
Handler 是Android SDK中用來處理非同步消息的核心類,子線程可以通過handler來通知主線程進行ui更新。
備註:本文源碼截圖 基於Android sdk 28
Handler機制 消息發送主要流程如圖
應用程序啟動後,zygote fork一個應用進程後,和普通java程序一樣,程序會首先執行ActivityThread中的main函數。在main函數中,程序首先會創建Looper對象並綁定到主線程中,然後開啟loop循環。(ps:主線程loop循環不能退出)
在prepareMainLooper方法中,最終會創建Looper,MessageQueue對象 以及創建native層MessageQueue對象。
使用Handler.sendMessageXXX或這 postDedayXXX發送消息後,最終會調用到SendMessageAtTime方法中。
然後調用MessageQueue.enqueueMessage將消息存到消息隊列中。
存入消息後,然後通過調用native方法 喚醒主線程進行消息處理。
當應用程序啟動,做完一些必要工作之後,便會開啟Loop循環,除非系統異常,否則該循環不會停止。loop循環中,主要做兩件事,第一,從消息隊列中取消息。第二,進行消息分發處理。
MessageQueue.next() 方法 通過調用 native方法 nativePollOnce(ptr, nextPollTimeoutMillis)實現無消息處理時,進入阻塞的功能。
當nextPollTimeoutMillis 值為0時,該方法會立刻返回;
當nextPollTimeoutMillis 值為-1時,該方法會無限阻塞,直到被喚醒;
當nextPollTimeoutMillis 值大於0時,該方法會將該值設置為超時時間,阻塞到達一定時間後,返回;
在loop循環中 ,通過調用 msg.target.dispatchMessage(msg) 進行消息的分發處理
使用當前線程的MessageQueue.addIdleHandler方法可以在消息隊列中添加一個IdelHandler。
當MessageQueue 阻塞時,即當前線程空閑時,會回調IdleHandler中的方法;
當IdelHandler介面返回false時,表示該IdelHandler只執行一次,
a,延遲執行
例如,當啟動Activity時,需要延時執行一些操作,以免啟動過慢,我們常常使用以下方式延遲執行任務,但是在延遲時間上卻不好控制。
其實,這時候使用IdelHandler 會更優雅
b,批量任務,任務密集,且只關注最終結果
例如,在開發一個IM類型的界面時,通常情況下,每次收到一個IM消息時,都會刷新一次界面,但是當短時間內, 收到多條消息時,就會刷新多次界面,容易造成卡頓,影響性能,此時就可以使用一個工作線程監聽IM消息,在通過添加IdelHandler的方式通知界面刷新,避免短時間內多次刷新界面情況的發生。
在Android的消息機制中,其實有三種消息: 普通消息、非同步消息及消息屏障。
消息屏障 也是一種消息,但是它的target為 null。可以通過MessageQueue中的postSyncBarrier方法發送一個消息屏障(該方法為私有,需要反射調用)。
在消息循環中,如果第一條消息就是屏障消息,就往後遍歷,看看有沒有非同步消息:
如果沒有,則無限休眠,等待被喚醒
如果有,就看離這個消息被觸發時間還有多久,設置一個超時時間,繼續休眠
非同步消息 和普通消息一樣,只不過它被設置setAsynchronous 為true。有了這個標志位,消息機制會對它有些特別的處理,我們稍後說。
所以 消息屏障和非同步消息的作用 很明顯,在設置消息屏障後,非同步消息具有優先處理的權利。
這時候我們回顧將消息添加到消息隊列中時,可以發現,其實並不是每一次添加消息時,都會喚醒線程。
當該消息插入到隊列頭時,會喚醒該線程;
當該消息沒有插入到隊列頭,但隊列頭是屏障,且該消息是隊列中 靠前的一個非同步消息,則會喚醒線程,執行該消息;
調用MessageQueue.removeSyncBarrier 方法可以移除指定的消息屏障
ANR 即 Application Not Response, 是系統進程對應用行為的一種監控,如果應用程序沒有在規定時間內完成任務的話,就會引起ANR。
ANR類型
Service Timeout : 前台服務20s, 後台服務200s
BroadcastQueue Timeout : 前台廣播 10s,後台廣播60s
ContentPrivider Timeout : 10s
InputDispatching Timeout : 5s
比如,在啟動一個服務時, AMS端通過應用進程的Binder對象創建Service, 在scheleCreateService()方法中 會調用到當前service的onCreate()生命周期函數;
bumpServiceExecutingLocked()方法內部實際上會調用到scheleServiceTimeoutLocked()方法,發送一個ActivityManagerService.SERVICE_TIMEOUT_MSG類型消息到AMS工作線程中。
消息的延時時間,如果是前台服務,延時20s, 如果是後台服務,延時200s;
如果Service的創建 工作在 上訴消息的延時時間內完成,則會移除該消息,
否則,在Handler正常收到這個消息後,就會進行服務超時處理,即彈出ANR對話框。
復雜情況下,可能會頻繁調用sendMessage 往消息隊列中,添加消息,導致消息積壓,造成卡頓,
1,重復消息過濾
頻繁發送同類型消息時,有可能隊列中之前的消息還沒有處理,又發了一條相同類型的消息,更新之前的數據,這時候,可以採用移除前一個消息的方法,優化消息隊列。
2,互斥消息取消
在發送消息時,優先將消息隊列中還未處理的信息已經過時的消息 移除,優化隊列
3,隊列優化-復用消息
創建消息時,優先採用之前回收的消息,避免重復創建對象,引起GC
完~
(如果錯誤或不足,望指出, 大家共同進步)
J. android中handler如何使用
Handler在Android中主要是負責發送和處理消息。它的主要用途大致是下面兩個:
1)按計劃發送消息或執行某個Runnanble;
2)從其他線程中發送來的消息放入消息隊列中,避免線程沖突(常見於更新UI線程)
學寫一下,在UI線程中,系統已經有一個Activity來處理了,你可以再起若干個Handler來處理。在實例化Handler的時候,只要有Handler的指針,任何線程也都可以sendMessage。
Handler對於Message的處理是非同步處理的。一個Looper 只有處理完一條Message才會讀取下一條,所以消息的處理是阻塞形式的(handleMessage()方法里不應該有耗時操作,可以將耗時操作放在其他線程執行,操作完後發送Message(通過sendMessges方法),然後由handleMessage()更新UI)。
根據對視頻的學習寫了一個通過Button控制項啟動進度條(類似於下載等操作)的程序,簡單介紹一下,有兩個Button控制項,一個是開始,點擊之後會顯示一個進度條以每次十分之一的進度進行(一開始是隱藏的),另一個是停止,可以中斷進度。
java代碼:
1 package zzl.handleactivity;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.os.Handler;
6 import android.os.Message;
7 import android.view.Gravity;
8 import android.view.View;
9 import android.view.View.OnClickListener;
10 import android.widget.Button;
11 import android.widget.ProgressBar;
12 import android.widget.Toast;
13
14 public class Handler_01 extends Activity {
15
16 //聲明變數
17 private Button startButton=null;
18 private Button endButton=null;
19 private ProgressBar firstBar=null;
20 private Toast toast=null;
21 @Override
22 protected void onCreate(Bundle savedInstanceState) {
23 super.onCreate(savedInstanceState);
24 setContentView(R.layout.main);
25
26 //根據ID獲取對象
27 startButton =(Button)findViewById(R.id.startButton);
28 endButton=(Button)findViewById(R.id.endButton);
29 firstBar=(ProgressBar)findViewById(R.id.firstBar);
30 //給對象設置動作監聽器
31 startButton.setOnClickListener(new StartButtonListener());
32 endButton.setOnClickListener(new EndButtonListener());
33 }
34
35 class StartButtonListener implements OnClickListener{
36
37 @Override
38 public void onClick(View v) {
39 // TODO Auto-generated method stub
40 //一開始執行,加入到消息隊列,不延遲,
41 //然後馬上執行run方法
42 firstBar.setVisibility(View.VISIBLE);
43 firstBar.setMax(100);
44 handler.post(upRunnable);
45 toast=Toast.makeText(Handler_01.this, "運行開始", Toast.LENGTH_SHORT);
46 toast.setGravity(Gravity.CENTER, 0, 0);
47 toast.show();
48 }
49 }
50 class EndButtonListener implements OnClickListener{
51
52 @Override
53 public void onClick(View v) {
54 // TODO Auto-generated method stub
55 //停止
56 handler.removeCallbacks(upRunnable);
57 System.out.println("It's time to stop...");
58 }
59 }
60
61 //創建handler對象,在調用post方法
62 //非同步消息處理:將下載或者處理大數據等等單獨放到另一個線程
63 //更好的用戶體驗
64 Handler handler=new Handler(){
65
66 @Override
67 public void handleMessage(Message msg){
68 firstBar.setProgress(msg.arg1);
69 firstBar.setSecondaryProgress(msg.arg1+10);
70 //handler.post(upRunnable);
71 if(msg.arg1<=100) {
72 handler.post(upRunnable); //將要執行的線程放入到隊列當中
73 }else {
74 handler.removeCallbacks(upRunnable);
75 }
76 }
77 };
78
79 //聲明線程類:實現Runnable的介面
80 Runnable upRunnable=new Runnable() {
81
82 int i=0;
83 @Override
84 public void run() {//程序的運行狀態
85 // TODO Auto-generated method stub
86 //postDelayed方法:把線程對象加入到消息隊列中
87 // 隔2000ms(延遲)
88 System.out.println("It's time to start...");
89 i=i+10;
90 //獲取Message消息對象
91 Message msg=handler.obtainMessage();
92 //將msg對象的arg1(還有arg2)對象的值設置
93 //使用這兩個變數傳遞消息優點:系統消耗性能較少
94 msg.arg1=i;
95 try{
96 //設置當前顯示睡眠1秒
97 Thread.sleep(1000);
98 }catch(InterruptedException e){
99 e.printStackTrace();
100 }
101 //將msg對象加入到消息隊列當中
102 handler.sendMessage(msg);
103 if(i==100){//當值滿足時,將線程對象從handle中剔除
104 handler.removeCallbacks(upRunnable);
105 firstBar.setVisibility(View.GONE);
106 //臨時彈出
107
108 toast=Toast.makeText(Handler_01.this, "運行結束", Toast.LENGTH_SHORT);
109 toast.setGravity(Gravity.CENTER, 0, 0);
110 toast.show();
111 }
112 }
113 };
114 }
main.xml
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:orientation="vertical"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 tools:context=".Handler_01" >
7
8 <ProgressBar
9 android:id="@+id/firstBar"
10 style="?android:attr/progressBarStyleHorizontal"
11 android:layout_width="200dp"
12 android:layout_height="wrap_content"
13 android:visibility="gone"/>
14
15 <Button
16 android:id="@+id/startButton"
17 android:layout_width="wrap_content"
18 android:layout_height="wrap_content"
19 android:text="@string/start" />
20
21 <Button
22 android:id="@+id/endButton"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:text="@string/end" />
26
27 </LinearLayout>
總結:
1)當點擊開始或者運行結束的時候,都會通過調用Toas彈出臨時窗口,Toast.makeText(Handler_01.this, "運行結束", Toast.LENGTH_SHORT),這一句一開始總是執行出錯,原因在於必須調用它的show方法才可以顯示出來,還可以通過設置它的位置來顯示;
2)在xml中 android:text="@string/end",則需要在layout下的string文件中敲寫相應的代碼
3)原本代碼有一些瑕疵,就是沒有下面這一段代碼:
1 if(msg.arg1<=100) {
2 handler.post(upRunnable); //將要執行的線程放入到隊列當中
3 }else {
4 handler.removeCallbacks(upRunnable);
5 }
這樣就導致了upRunnable的run方法出現了死循環,這樣,雖然程序UI本身沒有問題,但是內部卻又很大的缺陷
這是因為
1 if(i==100){//當值滿足時,將線程對象從handle中剔除
2 handler.removeCallbacks(upRunnable);
3 firstBar.setVisibility(View.GONE);
4 toast=Toast.makeText(Handler_01.this, "運行結束", Toast.LENGTH_SHORT);
5 toast.setGravity(Gravity.CENTER, 0, 0);
6 toast.show();
7 }
這一段代碼看似是把upRunnable線程從線程對象隊列中移除,但是再次之前又前執行了handler.sendMessage(msg);這句代碼
從而導致下面的代碼又被執行到
1 public void handleMessage(Message msg){
2 firstBar.setProgress(msg.arg1);
3 firstBar.setSecondaryProgress(msg.arg1+10);
4
5 }
這樣肯定會使upRunnable線程重新加入到線程對象隊列中,updateThread的run方法重復執行,這就導致了死循環。所以必須加上之前的那段代碼,通過判斷來控制循環終止。並且run方法中的if(i==100)的那段代碼也是可以不用的,不過我是寫了一些其他代碼就懶得優化了,這是後話了。
4) 剛剛大家看到我們可以通過敲寫System.out.println在logcat中顯示,一開始eclipse編譯器中是沒有,這是如何顯示出來的?
大家可以再window的show view中找到logCat(deprecated)通過下圖中綠色的「+」添加出來
然後顯示內容的時候,選擇右上角「V D I W E 」的I就可以比較清晰的顯示出來了,當然你也可以選擇另外一個logCat來顯示,方法類似。
5)實際上,Handler在默認情況下,和調用它的Activity是處於同一個線程的。 上述Handler的使用示例中,雖然聲明了線程對象,但是在實際調用當中它並沒有調用線程的start()方法,而是直接調用當前線程的run()方法。
如果要實現調用另一個新的線程,只要注釋post方法,然後加上這樣兩段代碼即可: Thread t = new Thread(r); t.start();