概述
android开发中,回调无处不在,整个android开发的框架就是以回调机制建立起来的。如:activity,service,broadcast,fragment,view事件监听,baseadapter适配器等等,生命周期或者具体每一步的操作都是以回调的形式抛给开发者实现。
先看UI同步问题:
编码过程中,“Android异步回调UI同步性问题”经常存在,有时候稍不注意会产生一些看起来难以理解的bug,并由于异步特性的存在,此类bug还具有一定的随机性。有时候由于一些需求的复杂性,此类bug隐蔽性很强,也容易被忽略。
ListView Item View中有ImageView,通过Android-Universal-Image-Loader去加载显示,图片加载完成后需要做一些逻辑处理(如隐藏图片加载进度条等...),通常代码如下:
1 ImageLoader.getInstance().loadImage(imageUrl, new ImageLoadingListener() {
2
3 @Override
4 public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
5 if (loadedImage != null) {
6 imageView.setImageBitmap(loadedImage);
7 // 其他业务逻辑处理..
8 }
9 }
10
11 @Override
12 public void onLoadingStarted(String imageUri, View view) {
13
14 }
15
16 @Override
17 public void onLoadingCancelled(String arg0, View arg1) {
18
19 }
20
21 @Override
22 public void onLoadingFailed(String arg0, View arg1, FailReason arg2) {
23
24 }
25 });
初看上去,代码逻辑好像也没什么问题,网上大部分人也是这么写的。当较慢滑动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同步、卡死阻塞等性能问题的全部内容,希望文章能够帮你解决android 异步回调中操作UI线程,UI同步、卡死阻塞等性能问题所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复