android 异步回调中操作UI线程,UI同步、卡死阻塞等性能问题
android開發(fā)中,回調(diào)無(wú)處不在,整個(gè)android開發(fā)的框架就是以回調(diào)機(jī)制建立起來(lái)的。如:activity,service,broadcast,fragment,view事件監(jiān)聽,baseadapter適配器等等,生命周期或者具體每一步的操作都是以回調(diào)的形式拋給開發(fā)者實(shí)現(xiàn)。
先看UI同步問(wèn)題:
編碼過(guò)程中,“Android異步回調(diào)UI同步性問(wèn)題”經(jīng)常存在,有時(shí)候稍不注意會(huì)產(chǎn)生一些看起來(lái)難以理解的bug,并由于異步特性的存在,此類bug還具有一定的隨機(jī)性。有時(shí)候由于一些需求的復(fù)雜性,此類bug隱蔽性很強(qiáng),也容易被忽略。
ListView Item View中有ImageView,通過(guò)Android-Universal-Image-Loader去加載顯示,圖片加載完成后需要做一些邏輯處理(如隱藏圖片加載進(jìn)度條等...),通常代碼如下:
初看上去,代碼邏輯好像也沒什么問(wèn)題,網(wǎng)上大部分人也是這么寫的。當(dāng)較慢滑動(dòng)ListView時(shí),或在平時(shí)正常使用時(shí),也沒有什么問(wèn)題。但是此處的代碼邏輯真的嚴(yán)密嗎?
ListView的getView復(fù)用特性,大家也都熟知。對(duì)于之前遇到的“圖片錯(cuò)位/先顯示之前的圖片后再被正確的圖片覆蓋掉”,此類現(xiàn)象也都知道如何解決(在getView邏輯開始處理處將ImageView設(shè)置成最先的默認(rèn)圖片,其他UI元素類似處理),基本上也不會(huì)再有“圖片錯(cuò)位/先顯示之前的圖片后再被正確的圖片覆蓋掉”這類現(xiàn)象了。實(shí)際上,當(dāng)網(wǎng)速條件一般,且loadImage大致與上述代碼所示,在ListView中快速滑動(dòng)列表,幾屏后,不出意外,會(huì)發(fā)現(xiàn)“圖片錯(cuò)位/先顯示之前的圖片后再被正確的圖片覆蓋掉”此問(wèn)題依然存在。
此時(shí)問(wèn)題出現(xiàn)的原因不在于getView本身,因?yàn)間etView邏輯開始時(shí)已經(jīng)將ImageView重置為默認(rèn)圖片,而在于“Android異步回調(diào)UI同步性問(wèn)題”。由于ViewHolder的不斷復(fù)用,網(wǎng)速一般時(shí)快速滑動(dòng)幾屏后,onLoadingComplete的異步回調(diào)執(zhí)行時(shí)與當(dāng)前UI元素已經(jīng)存在不一致,簡(jiǎn)單點(diǎn)理解,ImageView被復(fù)用了ImageView position 0,ImageView position 11, ImageView position 21,此時(shí)滑動(dòng)停止,onLoadingComplete的異步回調(diào)執(zhí)行時(shí)ImageView已經(jīng)是最后一次的ImageView position 21,而onLoadingComplete的異步回調(diào)可能被執(zhí)行數(shù)次(ImageView position 0,ImageView position 11, ImageView position 21,且順序還取決于異步中的具體處理和網(wǎng)絡(luò)環(huán)境等),于是問(wèn)題發(fā)生了。
解決方案:
抓住”UI元素中的某一特性的表征量“,在異步回調(diào)中通過(guò)比較“異步回調(diào)生成點(diǎn)”和“異步回調(diào)執(zhí)行點(diǎn)”此特征變量的值直接作出邏輯上的處理。
1 public class HardRefSimpleImageLoadingListener implements ImageLoadingListener { 2 3 public int identifier; 4 5 public HardRefSimpleImageLoadingListener() { 6 } 7 8 public HardRefSimpleImageLoadingListener(int identifier) { 9 this.identifier = identifier; 10 } 11 12 @Override 13 public void onLoadingCancelled(String arg0, View arg1) { 14 15 } 16 17 @Override 18 public void onLoadingComplete(String arg0, View arg1, Bitmap arg2) { 19 20 } 21 22 @Override 23 public void onLoadingFailed(String arg0, View arg1, FailReason arg2) { 24 25 } 26 27 @Override 28 public void onLoadingStarted(String arg0, View view) { 29 30 } 31 } 32 33 ImageLoader.getInstance().loadImage(imageUrl, new HardRefSimpleImageLoadingListener(did) { 34 @Override 35 public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { 36 if (loadedImage != null) { 37 if (identifier != did) { 38 return; 39 } 40 imageView.setImageBitmap(loadedImage); 41 // 其他業(yè)務(wù)邏輯處理.. 42 } 43 } 44 });
總之,凡此類“Android異步回調(diào)UI同步性問(wèn)題”,最好都通過(guò)比較“異步回調(diào)生成點(diǎn)”和“異步回調(diào)執(zhí)行點(diǎn)”特征變量的值去針對(duì)性的做邏輯處理,以免出現(xiàn)不必要的Bug,是非常必要且有效的手段.
異步操作UI問(wèn)題:
如果子線程操作UI元素,或者執(zhí)行UI方面的操作(不是簡(jiǎn)單的調(diào)用UI線程中的數(shù)據(jù));那么一定需要以handler方式去操作,否則可能出現(xiàn)UI線程阻塞,異常等。
下面是UI阻塞問(wèn)題:(現(xiàn)象:UI頁(yè)面停止,操作無(wú)響應(yīng),但是系統(tǒng)不爆出無(wú)響應(yīng)彈出框)
public void showAd() {if (isRequesting){return; }//發(fā)起請(qǐng)求 LogEx.d(LOG_TAG, "begin to request start ad"); //getAdLoader.setNeedDoNetWorkCheckFlag(false); mNewAdRequestHelper.requestBannerAdbyPositionId(mNewAdRequestHelper.Banner_Advertise_Adplaceid_Start); isRequesting = true; //啟動(dòng)超時(shí)定時(shí)器 mGetAdOverTimeSessionId = TimerMgr.getInstance().start(getStartUpAdDelayTime * 1000, mGetAdOverTimeLister); LogEx.d(LOG_TAG, "timer begin, time:"+getStartUpAdDelayTime +"s"); }/** * 初始化數(shù)據(jù) * 初始化 定時(shí)器 10s 超時(shí) * @param context */ private void initData(Context context) {mPreferenceHelper = new PreferenceHelper(context, "startUpAd"); //創(chuàng)建超時(shí)監(jiān)聽 mGetAdOverTimeLister = new ITimerMgr(){@Override public void onTimer(String arg0){if (arg0.equals(mGetAdOverTimeSessionId)){//超時(shí)監(jiān)聽器時(shí)間到if (isRequesting){//請(qǐng)求還沒返回,則丟棄 LogEx.d(LOG_TAG, "Time out, use local img for start ad"); showAdToUser(); return; }LogEx.d(LOG_TAG, "timer end,do not over time"); }}}; readAdFromLocal(); mNewAdRequestHelper = new NewAdRequestHelper(new NewAdRequestHelper.VodAdReturnListener() {@Override public void BannerReturn(AdVodBannerRspXMLParser.BannerPic bannerPic) {mstrBannerStartUrl = bannerPic.getUrl(); LogEx.d(LOG_TAG, "bannerPic.getDuration()=" + bannerPic.getDuration()); if(TextUtils.isEmpty(bannerPic.getDuration())){return; }miDurationStartAd = Integer.parseInt(bannerPic.getDuration()); LogEx.d(LOG_TAG, "miDurationStartAd=" + miDurationStartAd); if (!isRequesting) {LogEx.d(LOG_TAG, "get ad url, but not requesting ,return"); return; }if (!TextUtils.isEmpty(mstrBannerStartUrl)) {try {mstrBannerStartUrl = URLDecoder.decode(mstrBannerStartUrl, "UTF-8"); } catch (Exception e) {e.printStackTrace(); showAdToUser(); return; }}else {showAdToUser(); return; }LogEx.d(LOG_TAG, "get ad url, will download the new img ,wait"); //下載新圖片 imgDownloader = new ImgFileUtil(mContext, new ImgDownloadListener() {@Override public void onImgDownloaded(String filename) {LogEx.d(LOG_TAG, "download ad img success"); // mPreferenceHelper.putString(AD_START_URL_KEY, mstrBannerStartUrl); mStartUpAdLocalUrl = filename; writeAdToLocal(); if (!isRequesting) {LogEx.d(LOG_TAG, "overtime, return"); return; }showAdToUser(); }@Override public void onImgDownloadFail() {LogEx.i(LOG_TAG, "download ad img failed, use local img"); if (!isRequesting) {LogEx.w(LOG_TAG, "overtime, return"); return; }showAdToUser(); }}); //下載 圖片的 請(qǐng)求 imgDownloader.stratDownloadImg(mstrBannerStartUrl); }@Override public void VodPlayAdReturn(AdVodPlayRspXMLParser parser) {}}); }
/** * 展示 廣告圖片 */ private void showAdToUser(){TimerMgr.getInstance().stop(mGetAdOverTimeSessionId); LogEx.d(LOG_TAG, "enter showAdToUser"); isRequesting = false; if (StringUtil.isEmptyString(mStartUpAdLocalUrl)){LogEx.w(LOG_TAG, "at showAdToUser,but mStartUpAdLocalUrl is empty, return"); return; }//創(chuàng)建PopupWindow View popView = ((LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.start_ad_layout, null); mPopAD = new PopupWindow(popView, FrameLayout.LayoutParams.FILL_PARENT, FrameLayout.LayoutParams.FILL_PARENT,true); //展示廣告 mHandler.post(new Runnable(){@Override public void run(){mPopAD.showAtLocation(mViewRoot, Gravity.CENTER, 0, getStatusBarHeight()); }}); if (mStartUpAdLocalUrl.endsWith("gif")){imgGIF = (GifImageView) popView.findViewById(R.id.start_ad_gif); InputStream in = ImgFileUtil.readDataFile(mContext, mStartUpAdLocalUrl); imgGIF.setImageStream(in); WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); int width = display.getWidth(); int height = display.getHeight(); imgGIF.setWidthAndHeight(width,height); imgGIF.setVisibility(View.VISIBLE); }else {imgNormal = (ImageView) popView.findViewById(R.id.start_ad_img); Bitmap imgSrc = ImgFileUtil.File2Bitmap(mContext,mStartUpAdLocalUrl); imgNormal.setImageBitmap(imgSrc); imgNormal.setVisibility(View.VISIBLE); }LogEx.d(LOG_TAG, "start ad is showing, show time = "+miDurationStartAd+"s"); //啟動(dòng)定時(shí)器 mHandler.postDelayed(new Runnable(){@Override public void run(){LogEx.d(LOG_TAG, "start ad show time end, dismiss"); mPopAD.dismiss(); if (null != imgGIF){imgGIF.destroyDrawingCache(); }}},miDurationStartAd * 1000); }
定時(shí)器定時(shí)到時(shí),會(huì)觸發(fā)回調(diào),回調(diào)是在子線程中處理。那么當(dāng)圖片下載完成,調(diào)用showAdToUser的時(shí)候,恰好定時(shí)器到時(shí),調(diào)用回調(diào),發(fā)現(xiàn)標(biāo)志位已經(jīng)是FALSE,那么什么也不執(zhí)行。那么此時(shí)的UI直接是阻塞的。 --- 定時(shí)器到時(shí)強(qiáng)制掛起其他線程資源,執(zhí)行定時(shí)器線程,那么極容易出現(xiàn)阻塞問(wèn)題。
如果沒有下載完成標(biāo)志位是TRUE,那么定時(shí)器到時(shí)回調(diào)執(zhí)行showAdToUser,那么在showAdToUser中,判斷url是空的,那么直接返回。那么return,會(huì)讓線程執(zhí)行結(jié)束。這樣反而不會(huì)讓UI一致阻塞。
總之,子線程操作UI,尤其是定時(shí)器的使用,一定盡量使用handler!!
在網(wǎng)絡(luò)請(qǐng)求回調(diào)的時(shí)候我們都會(huì)使用handler上拋UI回調(diào):
public class SDKNetHTTPRequest {private static final String LOG_TAG = "SDKNetHTTPRequest"; private Map<String, String> headerMap = null; private SDKNetHTTPRequest.IHTTPRequestReturnListener mListener = null; private String mStrTag = ""; private String mContent = ""; private Handler mhandlerInUIHandler = new Handler(Looper.getMainLooper()) {public void handleMessage(Message msg) {if(msg.what == 0) {if(null != SDKNetHTTPRequest.this.mListener) {SDKNetHTTPRequest.this.mListener.onDataReturn(SDKNetHTTPRequest.this.mStrTag, SDKNetHTTPRequest.this.mContent); }} else if(null != SDKNetHTTPRequest.this.mListener) {SDKNetHTTPRequest.this.mListener.onFailReturn(SDKNetHTTPRequest.this.mStrTag, msg.what, (String)msg.obj); }}}; public SDKNetHTTPRequest() {this.headerMap = new HashMap(); }public void startRequest(String url, String method, String body, String tag, SDKNetHTTPRequest.IHTTPRequestReturnListener listener) {LogEx.d("SDKNetHTTPRequest", "start request"); this.mListener = listener; this.mStrTag = tag; String requestMethod = this.getRealMethod(method); HttpAttribute httpAttr = new HttpAttribute(); if(null == requestMethod) {requestMethod = "Get"; } else {requestMethod = requestMethod.toLowerCase(); if(-1 != requestMethod.indexOf("post")) {requestMethod = "Post"; } else if(-1 != requestMethod.indexOf("get")) {requestMethod = "Get"; }}LogEx.i("SDKNetHTTPRequest", "url=" + url); HttpRequest req = new HttpRequest(requestMethod, url, body); req.setHeaderMap(this.headerMap); HttpRequestParams httpRequestParams = new HttpRequestParams((DataAttribute)null, httpAttr, req, new IHttpDownloadListener() {public void onError(Exception e) {LogEx.exception(e); Message msg = SDKNetHTTPRequest.this.mhandlerInUIHandler.obtainMessage(); msg.obj = "exception"; msg.what = 1720000103; SDKNetHTTPRequest.this.mhandlerInUIHandler.sendMessage(msg); }public void onData(HttpRequest datareq, HttpResponse datarsp) {Message msg; if(null == datareq) {LogEx.e("SDKNetHTTPRequest", "HttpRequest is null"); msg = SDKNetHTTPRequest.this.mhandlerInUIHandler.obtainMessage();msg.obj = "param is null";msg.what = 1720000103;SDKNetHTTPRequest.this.mhandlerInUIHandler.sendMessage(msg); } else {LogEx.d("SDKNetHTTPRequest", datareq.getUrl() + " back"); LogEx.d("SDKNetHTTPRequest", "start request1"); if(datareq.isCanceled()) {LogEx.d("SDKNetHTTPRequest", "HttpRequest canceled:" + datareq); } else {if(null == datarsp) {LogEx.d("SDKNetHTTPRequest", "HomePage " + datareq.getUrl() + " not response!"); msg = SDKNetHTTPRequest.this.mhandlerInUIHandler.obtainMessage(); msg.obj = "get data failed"; msg.what = 1720000103; SDKNetHTTPRequest.this.mhandlerInUIHandler.sendMessage(msg); } else if(200 == datarsp.getStatusCode()) {Map msg2 = datarsp.getHeaderMap(); if(null != msg2) {String msg1 = (String)msg2.get("Date"); ServerDate.setEpgTimeOffset(msg1); }SDKNetHTTPRequest.this.mContent = datarsp.getBody().trim(); LogEx.d("SDKNetHTTPRequest", "content is: " + SDKNetHTTPRequest.this.mContent); Message msg3 = SDKNetHTTPRequest.this.mhandlerInUIHandler.obtainMessage(); msg3.obj = "success"; msg3.what = 0; SDKNetHTTPRequest.this.mhandlerInUIHandler.sendMessage(msg3); } else {LogEx.d("SDKNetHTTPRequest", "response: status code = " + datarsp.getStatusCode()); msg = SDKNetHTTPRequest.this.mhandlerInUIHandler.obtainMessage(); msg.obj = "get data failed : response statuscode =" + datarsp.getStatusCode(); msg.what = 1720000103; SDKNetHTTPRequest.this.mhandlerInUIHandler.sendMessage(msg); }}}}public void onCancel(HttpRequest datareq, HttpResponse datarsp) {}}); DataDownload.getInstance().sendHttpRequest(httpRequestParams); }private String getRealMethod(String method) {if(StringUtil.isEmptyString(method)) {method = "Get"; } else {method = method.toLowerCase(); if(-1 != method.indexOf("post")) {method = "Post"; } else if(-1 != method.indexOf("get")) {method = "Get"; }}return method; }public void setHeader(String key, String value) {if(null != key && !"".equals(key.trim())) {if(null != value && !"".equals(value.trim())) {key = key.toLowerCase(); this.headerMap.put(key.trim(), value.trim()); LogEx.d("SDKNetHTTPRequest", "headerMap put : key = " + key + ",value=" + value); }}}public void cancelRequest() {if(this.mListener != null) {this.mListener = null; }}public interface IHTTPRequestReturnListener {void onDataReturn(String var1, String var2); void onFailReturn(String var1, int var2, String var3); } }
總結(jié)
以上是生活随笔為你收集整理的android 异步回调中操作UI线程,UI同步、卡死阻塞等性能问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 旅行箱包广告词178个
- 下一篇: 蒋氏将才是什么意思