androidui刷新
1. Android更新UI界面的幾種方法的使用
1. 利用Android Handler機制和message消息傳遞
我們知道 , Android Handler機制主要用作線程之間的通信,為了易於理解,我們暫不考慮每個線程的Looper問題。UI更新一般是在主線程中完成的,而Handler就是定義在主線程中,然後通過在Handler構造方法中重寫HandlerMessage()方法,來處理有其他線程(子線程)傳遞過來的消息,從而達到更新UI的目的。相應的,在其他線程(子線程)中,我們通過SendMessage(message)方法來傳遞消息。
2.利用Android Handler機制和post
這個比較容易理解,也是UI更新常用的方法。 在一個新建的線程中進行更新界面的操作,然後在主線程中利用mHandler.post(Runnable runnable)來達到更新界面的目的,其中mHandler是在主線程中定義的。
3、通過runOnUiThread()方法來實現
java">classMyThreadextendsThread{
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
super.run();
//數據處理
//...
runOnUiThread(newnewRunnable(){
publicvoidrun(){
//刷新界面
list.add(dog);
adapter.notifyDataSetChanged();
}
}
})
}
2. Android中Fragment怎樣刷新UI
刷新UI要在主線程,Fragment和Activity是類似的,所以在要刷新UI的地方handler發送消息,在主線程中定義的hanler處理消息,更新UI,建議看下安卓的安卓handler機制。
3. android怎麼更新UI
首先,android的UI刷新是在主線程(UI線程)中完成的。四大組件中,activity和service運行在主線程中。現在總結自己在項目中常用到的UI刷新方式。
第一,利用子線程發消息刷新UI。
子線程負責處理UI需要的數據,然後發消息到主線程來刷新UI。代碼結構如下:
new Thread(new Runnable() {
@Override
public void run() {
Person person=new Person();
person.setName(mName.getText().toString().trim());
person.setPhone(mPhone.getText().toString().trim());
Log.i("person",person.toString());
DatabaseInfoFactory.getPersonDao(mContext).addPerson(person);
Looper.prepare();
Message msg=Message.obtain();
msg.what=0x123456;
handler.sendMessage(msg);
Looper.loop();
}
}).start();
主線程中:
private Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
if(msg.what==0x123456||msg.what==0x123){
fillData();
setListener();
}
}
};
第二,利用非同步任務更新UI。代碼結構如下:
new AsyncTask<void,void,void>() {
@Override
protected void onPostExecute(Void result) {
if(mAdapter==null){
mAdapter=new LeaveInfoAdapter();
//設置數據適配器
mLVleaveInfos.setAdapter(mAdapter);
Log.i("測試", "非同步任務顯示後台獲得資料庫數據");
}
else {
mAdapter.notifyDataSetChanged();
}
super.onPostExecute(result);
}
@Override
protected Void doInBackground(Void... params) {
//獲得要顯示的數據
mleaveInfos=mLeaveInfosDao.findAll();
if (mleaveInfos==null) {
Toast.makeText(HomeActivity.this,"請假數據不存在或是已經清除!", 500).show();
}
Log.i("測試", "非同步任務後台獲得資料庫數據"+mleaveInfos.size());
return null;
}
}.execute();</void,void,void>
第三,利用配置文件+activity的生命周期方法刷新UI。
4. android中如何實現UI的實時更新
1、在主線程中啟動一個子線程
首先,我們需要在主線程中啟動一個子線程,這個比較簡單,直接在MainActivity的onCreate()方法中調用如下方法即可:
newThread(mRunnable).start();
2、在子線程中發送Message給Handler
在創建子線程時,我們使用了Runnable介面對象mRunnable。這里,只需要實現Runnable介面,並重寫該介面的run()方法,在run()方法中實現每1秒發送一條Message給Handler即可。具體實現方法如下:
/*
*Function:實現run()方法,每1秒發送一條Message給Handler
*/
privateRunnablemRunnable=newRunnable(){
publicvoidrun(){
while(true){
try{
Thread.sleep(1000);
mHandler.sendMessage(mHandler.obtainMessage());
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
}
};
3、Handler接收Message通知
最後,我們創建一個Handler對象,用來接收Message通知。在收到Message通知後,完成刷新UI的操作即可。具體實現方法如下:
/*
*Function:實現handleMessage()方法,用於接收Message,刷新UI
*/
privateHandlermHandler=newHandler(){
publicvoidhandleMessage(Messagemsg){
super.handleMessage(msg);
refreshUI();
}
};
4、刷新UI
由以上的代碼可以看出,刷新UI的操作,我們是放在refreshUI()方法中來完成的。refreshUI()方法的實現也很簡單,調用HttpUtils工具類中的getInputStream()方法,獲得圖1所示Web工程的頁面內容輸入流,再將該輸入流轉化為字元串,放入TextView控制項中進行顯示即可。具體實現方法如下:
/*
*Function:刷新UI
*/
privatevoidrefreshUI(){
try{
InputStreaminputStream=HttpUtils.getInputStream();
StringresultData=HttpUtils.getResultData(inputStream);
mTextView.setText(resultData);
}catch(IOExceptione){
e.printStackTrace();
}
}
5. Android UI卡頓原因及解決辦法
渲染機制介紹
為了分析UI卡頓,我們有必要理解一下渲染機制,這套渲染機制適用於絕大部分的屏幕渲染,其中包括Android手機等眾多屏幕設備。
渲染的一些重要參數:
屏幕刷新理想的頻率(硬體的角度):60Hz
理想的一秒內繪制的幀數,幀率(屏幕刷新的角度):60fps
這兩個參數都是理想值,指代的都是同一個概念。實際情況中難免會比它們低。在60fps內,系統會得到發送的VSYNC(垂直刷新/繪制)信號去進行渲染,就會正常地繪制出我們需要的圖形界面。Android手機進行繪制的時候,GPU幫助我們將UI組件等計算成紋理Texture和三維圖形Polygons,同時會使用OpenGL---會將紋理和Polygons緩存在GPU內存裡面。
其中,VSYNC:有兩個概念
Refresh Rate:屏幕在一秒時間內刷新屏幕的次數----有硬體的參數決定,比如60HZ,即屏幕每秒刷新60次
Frame Rate:GPU在一秒內繪制操作的幀數,比如:60fps,
基本結論
要達到60fps,就要求:每一幀只能停留16ms。(大概就是1000ms/60 ~= 16ms刷新一次)
內存抖動是因為大量的對象被創建又在短時間內馬上被釋放。
瞬間產生大量的對象會嚴重佔用Young Generation的內存區域,當達到閥值,剩餘空間不夠的時候,也會觸發GC。即使每次分配的對象佔用了很少的內存,但是他們疊加在一起會增加Heap的壓力,從而觸發更多其他類型的GC。這個操作有可能會影響到幀率,並使得用戶感知到性能問題。
Android裡面是一個三級Generation的內存模型,最近分配的對象會存放在Young Generation區域,當這個對象在這個區域停留的時間達到一定程度,它會被移動到Old Generation,最後到Permanent Generation區域。
Android每個16ms就會繪制一次Activity,通過上述的結論我們知道,如果由於一些原因導致了我們的邏輯、CPU耗時、GPU耗時大於16ms( 應用卡頓的根源就在於16ms內不能完成繪制渲染合成過程,16ms需要完成視圖樹的所有測量、布局、繪制渲染及合成 ),UI就無法完成一次繪制,那麼就會造成卡頓。
比如說,在16ms內,發生了頻繁的GC:
在第一個16ms內,UI正常地完成了繪制,那麼屏幕不會卡頓。
在第二個16ms內,由於某些原因觸發了頻發的GC,UI無法在16ms內完成繪制,就會卡頓。
UI卡頓外部和內部常見原因
下面總結一些常見的UI卡頓原因:
1.內存抖動的問題
2.方法太耗時了(CPU佔用)
1) CPU計算時間,CPU的測量、布局時間
2)CPU將計算好的Polygons和Texture傳遞到GPU的時候也需要時間。OpenGL ES API允許數據上傳到GPU後可以對數據進行保存,緩存到display list。因此,我們平移等操作一個view是幾乎不怎麼耗時的 。
3) GPU進行格柵化
當我們的布局是用的FrameLayout的時候,我們可以把它改成merge,可以避免自己的幀布局和系統的ContentFrameLayout幀布局重疊造成重復計算(measure和layout)。
使用ViewStub:當載入的時候才會佔用。不載入的時候就是隱藏的,僅僅佔用位置。
CPU優化建議
針對CPU的優化,從減輕加工View對象成Polygons和Texture來下手:
View Hierarchy中包涵了太多的沒有用的view,這些view根本就不會顯示在屏幕上面,一旦觸發測量和布局操作,就會拖累應用的性能表現。那麼我們就需要利用工具進行分析。
如何找出裡面沒用的view呢?或者減少不必要的view嵌套。
我們利用工具:Hierarchy Viewer進行檢測,優化思想是:查看自己的布局,層次是否很深以及渲染比較耗時,然後想辦法能否減少層級以及優化每一個View的渲染時間。
我們打開APP,然後打開Android Device Monitor,然後切換到Hierarchy Viewer面板。除了看層次結構之外,還可以看到一些耗時的信息:
三個圓點分別代表:測量、布局、繪制三個階段的性能表現。
1)綠色:渲染的管道階段,這個視圖的渲染速度快於至少一半的其他的視圖。
2)黃色:渲染速度比較慢的50%。
3)紅色:渲染速度非常慢。
GPU優化建議就是一句話:盡量避免過度繪制(overdraw)
一、背景經常容易造成過度繪制。
手機開發者選項裡面找到工具:Debug GPU overdraw,其中,不同顏色代表了繪制了幾次:
6. Android UI線程
思考:
先必須了解下面2個問題
1.顧名思義 UI線程 就是刷新UI 所在線程
2.UI是單線程刷新
1.對Activity 來說 UI線程就是其主線程
2.對View來說 UI線程就是創建ViewRootImpl所在的線程
可以通過 WindowManager 內部會創建ViewRootImpl對象
好了,進入主題。我們來慢慢揭開面紗。
我們可以分別從幾個方面切入
我們可能都有使用過 runOnUiThread 現在來看看的源碼實現。
可以從上面的源碼 看到
不是UI線程 就用Handler切到Handler所在的線程中,如果是UI線程直接就調用run方法。
Activity的創建:
1.Activity創建:mInstrumentation.newActivity
2.創建Context :ContextImpl (r)
我們經常用這個方法乾的事情就是,要麼在onCreate中獲取View寬高的值。要麼就是在子線程中做一些耗時操作 ,然後post切到對應View所在的線程 來繪制UI操作。那麼這個對應的線程就是UI線程了。
那麼這個UI線程就一定是主線程嗎?
接來繼續來看。它的源碼View:post
mAttachInfo 在dispatchAttachedToWindow 中被賦值 ,也就是在ViewRootImpl創建的時候,所以是創建ViewRootImpl所在的線程。
attachInfo 上面時候為null 呢?在ViewRootImpl 還沒來得及創建的時候,ViewRootImpl 創建是在 「onResume" 之後。所以在 Activity 的 onCreate 去View.post 那麼AttachInfo 是為null 。
當 AttachInfo == null 那麼會調用 getRunQueue().post(action) 。
最終這個Runnable 被 緩存到 HandlerActionQueue 中。
直到ViewRootImpl 的 performTraversals 中 調用dispatchAttachedToWindow(mAttachInfo, 0);, 那麼才會去處理 RunQueue() 中的Runnable。
來張圖 便於理解這個流程
我們有時候去子線程操作UI的時候(如:requestLayout),會很經常見到下面的 報錯日誌:
Only the original thread that created a view hierarchy can touch its views
為什麼會報這個錯誤呢?
翻譯一下:只有創建視圖層次結構的原始線程才能接觸到它的視圖。
也就是操作UI的線程要和ViewRootImpl創建的線程是同一個線程才行,並不是只有主線程才能更新UI啊。
ViewRootImpl創建的線程?那麼 ViewRootImpl 在哪裡被創建的呢?
從上圖可以看到ViewRootImpl創建最開始是從 ActivityThread 的HandleResumeActivity中開始 一直 ViewRootImpl 創建,也就是說ViewRootImpl 對應的UI線程和 ActivityThread 在同一個線程 也就是主線程。
好了 通過上面的講解,上面的問題相信你可以自己回答啦~