我是靠谱客的博主 震动羽毛,最近开发中收集的这篇文章主要介绍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去加载显示,图片加载完成后需要做一些逻辑处理(如隐藏图片加载进度条等...),通常代码如下:

复制代码
 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同步、卡死阻塞等性能问题所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部