android 异步回调中操作UI线程,UI同步、卡死阻塞等性能问题
android開發中,回調無處不在,整個android開發的框架就是以回調機制建立起來的。如:activity,service,broadcast,fragment,view事件監聽,baseadapter適配器等等,生命周期或者具體每一步的操作都是以回調的形式拋給開發者實現。
先看UI同步問題:
編碼過程中,“Android異步回調UI同步性問題”經常存在,有時候稍不注意會產生一些看起來難以理解的bug,并由于異步特性的存在,此類bug還具有一定的隨機性。有時候由于一些需求的復雜性,此類bug隱蔽性很強,也容易被忽略。
ListView Item View中有ImageView,通過Android-Universal-Image-Loader去加載顯示,圖片加載完成后需要做一些邏輯處理(如隱藏圖片加載進度條等...),通常代碼如下:
初看上去,代碼邏輯好像也沒什么問題,網上大部分人也是這么寫的。當較慢滑動ListView時,或在平時正常使用時,也沒有什么問題。但是此處的代碼邏輯真的嚴密嗎?
ListView的getView復用特性,大家也都熟知。對于之前遇到的“圖片錯位/先顯示之前的圖片后再被正確的圖片覆蓋掉”,此類現象也都知道如何解決(在getView邏輯開始處理處將ImageView設置成最先的默認圖片,其他UI元素類似處理),基本上也不會再有“圖片錯位/先顯示之前的圖片后再被正確的圖片覆蓋掉”這類現象了。實際上,當網速條件一般,且loadImage大致與上述代碼所示,在ListView中快速滑動列表,幾屏后,不出意外,會發現“圖片錯位/先顯示之前的圖片后再被正確的圖片覆蓋掉”此問題依然存在。
此時問題出現的原因不在于getView本身,因為getView邏輯開始時已經將ImageView重置為默認圖片,而在于“Android異步回調UI同步性問題”。由于ViewHolder的不斷復用,網速一般時快速滑動幾屏后,onLoadingComplete的異步回調執行時與當前UI元素已經存在不一致,簡單點理解,ImageView被復用了ImageView position 0,ImageView position 11, ImageView position 21,此時滑動停止,onLoadingComplete的異步回調執行時ImageView已經是最后一次的ImageView position 21,而onLoadingComplete的異步回調可能被執行數次(ImageView position 0,ImageView position 11, ImageView position 21,且順序還取決于異步中的具體處理和網絡環境等),于是問題發生了。
解決方案:
抓住”UI元素中的某一特性的表征量“,在異步回調中通過比較“異步回調生成點”和“異步回調執行點”此特征變量的值直接作出邏輯上的處理。
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 // 其他業務邏輯處理.. 42 } 43 } 44 });
總之,凡此類“Android異步回調UI同步性問題”,最好都通過比較“異步回調生成點”和“異步回調執行點”特征變量的值去針對性的做邏輯處理,以免出現不必要的Bug,是非常必要且有效的手段.
異步操作UI問題:
如果子線程操作UI元素,或者執行UI方面的操作(不是簡單的調用UI線程中的數據);那么一定需要以handler方式去操作,否則可能出現UI線程阻塞,異常等。
下面是UI阻塞問題:(現象:UI頁面停止,操作無響應,但是系統不爆出無響應彈出框)
public void showAd() {if (isRequesting){return; }//發起請求 LogEx.d(LOG_TAG, "begin to request start ad"); //getAdLoader.setNeedDoNetWorkCheckFlag(false); mNewAdRequestHelper.requestBannerAdbyPositionId(mNewAdRequestHelper.Banner_Advertise_Adplaceid_Start); isRequesting = true; //啟動超時定時器 mGetAdOverTimeSessionId = TimerMgr.getInstance().start(getStartUpAdDelayTime * 1000, mGetAdOverTimeLister); LogEx.d(LOG_TAG, "timer begin, time:"+getStartUpAdDelayTime +"s"); }/** * 初始化數據 * 初始化 定時器 10s 超時 * @param context */ private void initData(Context context) {mPreferenceHelper = new PreferenceHelper(context, "startUpAd"); //創建超時監聽 mGetAdOverTimeLister = new ITimerMgr(){@Override public void onTimer(String arg0){if (arg0.equals(mGetAdOverTimeSessionId)){//超時監聽器時間到if (isRequesting){//請求還沒返回,則丟棄 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(); }}); //下載 圖片的 請求 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; }//創建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"); //啟動定時器 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); }
定時器定時到時,會觸發回調,回調是在子線程中處理。那么當圖片下載完成,調用showAdToUser的時候,恰好定時器到時,調用回調,發現標志位已經是FALSE,那么什么也不執行。那么此時的UI直接是阻塞的。 --- 定時器到時強制掛起其他線程資源,執行定時器線程,那么極容易出現阻塞問題。
如果沒有下載完成標志位是TRUE,那么定時器到時回調執行showAdToUser,那么在showAdToUser中,判斷url是空的,那么直接返回。那么return,會讓線程執行結束。這樣反而不會讓UI一致阻塞。
總之,子線程操作UI,尤其是定時器的使用,一定盡量使用handler!!
在網絡請求回調的時候我們都會使用handler上拋UI回調:
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); } }
總結
以上是生活随笔為你收集整理的android 异步回调中操作UI线程,UI同步、卡死阻塞等性能问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 旅行箱包广告词178个
- 下一篇: 安装 android studio创建工