金沙国际官网_金沙国际平台登录

因为这个金沙国际官网_金沙国际平台登录网站与很多的大型澳门赌场都有合作,金沙国际官网_金沙国际平台登录尽职尽责,高效执行,保持好奇心,不断学习,追求卓越,点击进入金沙国际官网_金沙国际平台登录马上体验吧,所以现在也正式地开始了营业。

您的位置:金沙国际官网 > 编程 > Android代码规范参考指南,Android中的ANR用法详解

Android代码规范参考指南,Android中的ANR用法详解

发布时间:2020-01-05 04:46编辑:编程浏览(114)

    代码规范对于一个软件项目来说非常重要,当然Android项目也不例外,一个优秀的Android项目不仅需要严谨的业务逻辑和架构设计,更需要一套统一优雅的代码规范标准,才可以让整个项目团队更加高效。

    有过Android开发经历的人都不会对ANR陌生,它和崩溃一样是程序设计的问题。本文将以较为深入的视角来介绍什么是ANR,出现场景,如何避免以及如何定位分析ANR,希望可以帮助大家在编写程序时有所帮助。

    前言

    在Android ImageLoader框架之基本架构中我们对SimpleImageLoader框架进行了基本的介绍,今天我们就从源码的角度来剖析ImageLoader的设计与实现。
    在我们使用ImageLoader前都会通过一个配置类来设置一些基本的东西,比如加载中的图片、加载失败的图片、缓存策略等等,SimpleImageLoader的设计也是如此。配置类这个比较简单,我们直接看源码吧。

    包命名规范

    什么是ANR

    ANR全称Application Not Responding,意思就是程序未响应。如果一个应用无法响应用户的输入,系统就会弹出一个ANR对话框,如下图所示,用户可以自行选择继续等待亦或者是停止当前程序。

    图片 1

    ImageLoaderConfig配置

    /**
     * ImageLoader配置类,
     * 
     * @author mrsimple
     */
    public class ImageLoaderConfig {
    
        /**
         * 图片缓存配置对象
         */
        public BitmapCache bitmapCache = new MemoryCache();
    
        /**
         * 加载图片时的loading和加载失败的图片配置对象
         */
        public DisplayConfig displayConfig = new DisplayConfig();
        /**
         * 加载策略
         */
        public LoadPolicy loadPolicy = new SerialPolicy();
    
        /**
         * 
         */
        public int threadCount = Runtime.getRuntime().availableProcessors() + 1;
    
        /**
         * @param count
         * @return
         */
        public ImageLoaderConfig setThreadCount(int count) {
            threadCount = Math.max(1, count);
            return this;
        }
    
        public ImageLoaderConfig setCache(BitmapCache cache) {
            bitmapCache = cache;
            return this;
        }
    
        public ImageLoaderConfig setLoadingPlaceholder(int resId) {
            displayConfig.loadingResId = resId;
            return this;
        }
    
        public ImageLoaderConfig setNotFoundPlaceholder(int resId) {
            displayConfig.failedResId = resId;
            return this;
        }
    
        public ImageLoaderConfig setLoadPolicy(LoadPolicy policy) {
            if (policy != null) {
                loadPolicy = policy;
            }
            return this;
        }
    }
    

    都是很简单的setter函数,但是不太一样的是这些setter都是返回一个ImageLoaderConfig对象的,在这里也就是返回了自身。这个设计是类似Builder模式的,便于用户的链式调用,例如:

     private void initImageLoader() {
            ImageLoaderConfig config = new ImageLoaderConfig()
                    .setLoadingPlaceholder(R.drawable.loading)
                    .setNotFoundPlaceholder(R.drawable.not_found)
                    .setCache(new DoubleCache(this))
                    .setThreadCount(4)
                    .setLoadPolicy(new ReversePolicy());
            // 初始化
            SimpleImageLoader.getInstance().init(config);
        }
    

    对于Builder模式不太了解的同学可以参考 Android源码分析之Builder模式。构建好配置对象之后我们就可以通过这个配置对象来初始化SimpleImageLoader了。SimpleImageLoader会根据配置对象来初始化一些内部策略,例如缓存策略、线程数量等。调用init方法后整个ImageLoader就正式启动了。

    包(packages):采用反域名命名规则,全部使用小写字母。一级包名为com,二级包名为xxx(可以是公司域名或者个人命名),三级包名根据应用进行命名,四级包名为模块名或层级名。

    出现场景

    • 主线程被IO操作(从4.0之后网络IO不允许在主线程中)阻塞。
    • 主线程中存在耗时的计算
    • 主线程中错误的操作,比如Thread.wait或者Thread.sleep等

    Android系统会监控程序的响应状况,一旦出现下面两种情况,则弹出ANR对话框

    • 应用在 5秒 内未响应用户的输入事件(如按键或者触摸)
    • BroadcastReceiver未在 10秒 内完成相关的处理

    SimpleImageLoader的实现

    SimpleImageLoader这个类的职责只是作为用户入口,它的工作其实并没有那么多,只是一个门童罢了。我们看看它的源码吧。

    /**
     * 图片加载类,支持url和本地图片的uri形式加载.根据图片路径格式来判断是网络图片还是本地图片,如果是网络图片则交给SimpleNet框架来加载,
     * 如果是本地图片那么则交给mExecutorService从sd卡中加载
     * .加载之后直接更新UI,无需用户干预.如果用户设置了缓存策略,那么会将加载到的图片缓存起来.用户也可以设置加载策略,例如顺序加载{@see
     * SerialPolicy}和逆向加载{@see ReversePolicy}.
     * 
     * @author mrsimple
     */
    public final class SimpleImageLoader {
        /** SimpleImageLoader实例 */
        private static SimpleImageLoader sInstance;
    
        /** 网络请求队列  */
        private RequestQueue mImageQueue;
        /** 缓存 */
        private volatile BitmapCache mCache = new MemoryCache();
    
        /** 图片加载配置对象 */
        private ImageLoaderConfig mConfig;
    
        private SimpleImageLoader() {
        }
    
        /**
         * 获取ImageLoader单例
         * 
         * @return
         */
        public static SimpleImageLoader getInstance() {
            if (sInstance == null) {
                synchronized (SimpleImageLoader.class) {
                    if (sInstance == null) {
                        sInstance = new SimpleImageLoader();
                    }
                }
            }
            return sInstance;
        }
    
        /**
         * 初始化ImageLoader,启动请求队列
         * @param config 配置对象
         */
        public void init(ImageLoaderConfig config) {
            mConfig = config;
            mCache = mConfig.bitmapCache;
            checkConfig();
            mImageQueue = new RequestQueue(mConfig.threadCount);
            mImageQueue.start();
        }
    
        private void checkConfig() {
            if (mConfig == null) {
                throw new RuntimeException(
                        "The config of SimpleImageLoader is Null, please call the init(ImageLoaderConfig config) method to initialize");
            }
    
            if (mConfig.loadPolicy == null) {
                mConfig.loadPolicy = new SerialPolicy();
            }
            if (mCache == null) {
                mCache = new NoCache();
            }
    
        }
    
        public void displayImage(ImageView imageView, String uri) {
            displayImage(imageView, uri, null, null);
        }
    
        public void displayImage(final ImageView imageView, final String uri,
                final DisplayConfig config, final ImageListener listener) {
            BitmapRequest request = new BitmapRequest(imageView, uri, config, listener);
            // 加载的配置对象,如果没有设置则使用ImageLoader的配置
            request.displayConfig = request.displayConfig != null ? request.displayConfig
                    : mConfig.displayConfig;
            // 添加对队列中
            mImageQueue.addRequest(request);
        }
    
          // 代码省略...
    
        /**
         * 图片加载Listener
         * 
         * @author mrsimple
         */
        public static interface ImageListener {
            public void onComplete(ImageView imageView, Bitmap bitmap, String uri);
        }
    }
    

    从上述代码中我们可以看到SimpleImageLoader的工作比较少,也比较简单。它就是根据用户传递进来的配置来初始化ImageLoader,并且作为图片加载入口,用户调用displayImage之后它会将这个调用封装成一个BitmapRequest请求,然后将该请求添加到请求队列中。

    包名 说明
    com.xxx.应用名称缩写.activities 页面用到的Activity类(activities层级用户界面)
    com.xxx.应用名称缩写.fragment 页面用到的Fragment类
    com.xxx.应用名称缩写.base 页面中每个Activity类共享的可以写成一个BaseActivity类(基础共享的类)
    com.xxx.应用名称缩写.adapter 页面用到的Adapter类(适配器的类)
    com.xxx.应用名称缩写.utils 此包中包含:公共工具方法类(包含日期、网络、存储、日志等工具类)
    com.xxx.应用名称缩写.bean

    (model/domain均可,个人喜好)

    实体类
    com.xxx.应用名称缩写.db 数据库操作
    com.xxx.应用名称缩写.view(或者.ui) 自定义的View类等
    com.xxx.应用名称缩写.service Service服务
    com.xxx.应用名称缩写.broadcast Broadcast服务

    如何避免

    基本的思路就是将IO操作在工作线程来处理,减少其他耗时操作和错误操作

    • 使用AsyncTask处理耗时IO操作。
    • 使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。
    • 使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。
    • Activity的onCreate和onResume回调中尽量避免耗时的代码
    • BroadcastReceiver中onReceive代码也要尽量减少耗时,建议使用IntentService处理。

    BitmapRequest图片加载请求

    BitmapRequest只是一个存储了ImageView、图片uri、DisplayConfig以及ImageListener的一个对象,封装这个对象的目的在加载图片时减少参数的个数,***在BitmapRequest的构造函数中我们会将图片uri设置为ImageView的tag,这样做的目的是防止图片错位显示。***BitmapRequest类实现了Compare接口,请求队列会根据它的序列号进行排序,排序策略用户也可以通过配置类来设置,具体细节在加载策略的章节我们再聊吧。

     public BitmapRequest(ImageView imageView, String uri, DisplayConfig config,
                ImageListener listener) {
            mImageViewRef = new WeakReference<ImageView>(imageView);
            displayConfig = config;
            imageListener = listener;
            imageUri = uri;
            // 设置ImageView的tag为图片的uri
            imageView.setTag(uri);
            imageUriMd5 = Md5Helper.toMD5(imageUri);
        }
    

    类命名规范

    画龙点睛

    通常100到200毫秒就会让人察觉程序反应慢,为了更加提升响应,可以使用下面的几种方法

    • 如果程序正在后台处理用户的输入,建议使用让用户得知进度,比如使用ProgressBar控件。
    • 程序启动时可以选择加上欢迎界面,避免让用户察觉卡顿。
    • 使用Systrace和TraceView找出影响响应的问题。

    RequestQueue图片请求队列

    请求队列我们采用了SImpleNet中一样的模式,通过封装一个优先级队列来维持图片加载队列,mSerialNumGenerator会给每一个请求分配一个序列号,PriorityBlockingQueue会根据BitmapRequest的compare策略来决定BitmapRequest的顺序。RequestQueue内部会启动用户指定数量的线程来从请求队列中读取请求,分发线程不断地从队列中读取请求,然后进行图片加载处理,这样ImageLoader就happy起来了。

    /**
     * 请求队列, 使用优先队列,使得请求可以按照优先级进行处理. [ Thread Safe ]
     * 
     * @author mrsimple
     */
    public final class RequestQueue {
        /**
         * 请求队列 [ Thread-safe ]
         */
        private BlockingQueue<BitmapRequest> mRequestQueue = new PriorityBlockingQueue<BitmapRequest>();
        /**
         * 请求的序列化生成器
         */
        private AtomicInteger mSerialNumGenerator = new AtomicInteger(0);
    
        /**
         * 默认的核心数
         */
        public static int DEFAULT_CORE_NUMS = Runtime.getRuntime().availableProcessors() + 1;
        /**
         * CPU核心数 + 1个分发线程数
         */
        private int mDispatcherNums = DEFAULT_CORE_NUMS;
        /**
         * NetworkExecutor,执行网络请求的线程
         */
        private RequestDispatcher[] mDispatchers = null;
    
        /**
         * 
         */
        protected RequestQueue() {
            this(DEFAULT_CORE_NUMS);
        }
    
        /**
         * @param coreNums 线程核心数
         * @param httpStack http执行器
         */
        protected RequestQueue(int coreNums) {
            mDispatcherNums = coreNums;
        }
    
        /**
         * 启动RequestDispatcher
         */
        private final void startDispatchers() {
            mDispatchers = new RequestDispatcher[mDispatcherNums];
            for (int i = 0; i < mDispatcherNums; i++) {
                mDispatchers[i] = new RequestDispatcher(mRequestQueue);
                mDispatchers[i].start();
            }
        }
    
        public void start() {
            stop();
            startDispatchers();
        }
    
        /**
         * 停止RequestDispatcher
         */
        public void stop() {
            if (mDispatchers != null && mDispatchers.length > 0) {
                for (int i = 0; i < mDispatchers.length; i++) {
                    mDispatchers[i].interrupt();
                }
            }
        }
    
        /**
         * 不能重复添加请求
         * @param request
         */
        public void addRequest(BitmapRequest request) {
            if (!mRequestQueue.contains(request)) {
                request.serialNum = this.generateSerialNumber();
                mRequestQueue.add(request);
            } else {
                Log.d("", "### 请求队列中已经含有");
            }
        }
    
        private int generateSerialNumber() {
            return mSerialNumGenerator.incrementAndGet();
        }
    }
    

    类(classes):名词,采用大驼峰命名法,尽量避免缩写,除非该缩写是众所周知的,比如HTML,URL,如果类名称包含单词缩写,则单词缩写的每个字母均应大写。

    如何定位

    如果开发机器上出现问题,我们可以通过查看/data/anr/traces.txt即可,最新的ANR信息在最开始部分。我们从stacktrace中即可找到出问题的具体行数。本例中问题出现在MainActivity.java 27行,因为这里调用了Thread.sleep方法。

     root@htc_m8tl:/ # cat /data/anr/traces.txt | more
    
    ----- pid 30307 at 2015-05-30 14:51:14 -----
    Cmd line: com.example.androidyue.bitmapdemo
    
    JNI: CheckJNI is off; workarounds are off; pins=0; globals=272
    
    DALVIK THREADS:
    (mutexes: tll=0 tsl=0 tscl=0 ghl=0)
    
    "main" prio=5 tid=1 TIMED_WAIT
      | group="main" sCount=1 dsCount=0 obj=0x416eaf18 self=0x416d8650
      | sysTid=30307 nice=0 sched=0/0 cgrp=apps handle=1074565528
      | state=S schedstat=( 0 0 0 ) utm=5 stm=4 core=3
      at java.lang.VMThread.sleep(Native Method)
      at java.lang.Thread.sleep(Thread.java:1044)
      at java.lang.Thread.sleep(Thread.java:1026)
      at com.example.androidyue.bitmapdemo.MainActivity$1.run(MainActivity.java:27)
      at android.app.Activity.runOnUiThread(Activity.java:4794)
      at com.example.androidyue.bitmapdemo.MainActivity.onResume(MainActivity.java:33)
      at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1282)
      at android.app.Activity.performResume(Activity.java:5405)
    

    如果是线上版本引起的,Google Play后台有相关的数据可以帮助查看分析并解决问题。

    RequestDispatcher请求分发

    请求Dispatcher,继承自Thread,从网络请求队列中循环读取请求并且执行,也比较简单,直接上源码吧。

    final class RequestDispatcher extends Thread {
    
        /**
         * 网络请求队列
         */
        private BlockingQueue<BitmapRequest> mRequestQueue;
    
        /**
         * @param queue 图片加载请求队列
         */
        public RequestDispatcher(BlockingQueue<BitmapRequest> queue) {
            mRequestQueue = queue;
        }
    
        @Override
        public void run() {
            try {
                while (!this.isInterrupted()) {
                    final BitmapRequest request = mRequestQueue.take();
                    if (request.isCancel) {
                        continue;
                    }
                    // 解析图片schema
                    final String schema = parseSchema(request.imageUri);
                    // 根据schema获取对应的Loader
                    Loader imageLoader = LoaderManager.getInstance().getLoader(schema);
                    // 加载图片
                    imageLoader.loadImage(request);
                }
            } catch (InterruptedException e) {
                Log.i("", "### 请求分发器退出");
            }
        }
    
        /**
         * 这里是解析图片uri的格式,uri格式为: schema:// + 图片路径。
         */
        private String parseSchema(String uri) {
            if (uri.contains("://")) {
                return uri.split("://")[0];
            } else {
                Log.e(getName(), "### wrong scheme, image uri is : " + uri);
            }
    
            return "";
        }
    
    }
    

    第一个重点就是run函数了,不断地从队列中获取请求,然后解析到图片uri的schema,从schema的格式就可以知道它是存储在哪里的图片。例如网络图片对象的schema是http或者https,sd卡存储的图片对应的schema为file。根据schema我们从LoaderManager中获取对应的Loader来加载图片,这个设计保证了SimpleImageLoader可加载图片类型的可扩展性,这就是为什么会增加loader这个包的原因。用户只需要根据uri的格式来构造图片uri,并且实现自己的Loader类,然后将Loader对象注入到LoaderManager即可,后续我们会再详细说明。

    这里的另一个重点是parseSchema函数,它的职责是解析图片uri的格式,uri格式为: schema:// + 图片路径,例如网络图片的格式为

    描述 例如
    Application类 Application为后缀标识 XXXApplication
    Activity类 Activity为后缀标识 闪屏页面类

    SplashActivity

    解析类 Handler为后缀标识
    公共方法类 Utils或Manager为后缀标识 线程池管理类:ThreadPoolManager

    日志工具类:LogUtils

    数据库类 以DBHelper后缀标识 MySQLiteDBHelper
    Service类 以Service为后缀标识 播放服务:PlayService
    BroadcastReceiver类 以Broadcast为后缀标识 时间通知:

    TimeBroadcast

    ContentProvider类 以Provider为后缀标识 单词内容提供者:DictProvider
    直接写的共享基础类 以Base为前缀 BaseActivity,

    BaseFragment

    细致分析

    提问: BroadcastReceiver过了60秒居然没有ANR? 现场代码如下

     public class NetworkReceiver extends BroadcastReceiver{
        private static final String LOGTAG = "NetworkReceiver";
    
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(LOGTAG, "onReceive intent=" + intent);
            try {
                Thread.sleep(60000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.i(LOGTAG, "onReceive end");
        }
    }
    

    回答:实际上已经发生了ANR,只是没有进行对话框弹出而已。这种ANR就是background ANR,即后台程序的ANR,我们可以通过过滤日志验证

     adb logcat | grep "NetworkReceiver|ActivityManager|WindowManager"
    I/NetworkReceiver( 4109): onReceive intent=Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x8000010 cmp=com.example.androidyue.bitmapdemo/.NetworkReceiver (has extras) }
    I/ActivityManager(  462): No longer want com.android.exchange (pid 1054): empty #17
    I/NetworkReceiver( 4109): onReceive end
    W/BroadcastQueue(  462): Receiver during timeout: ResolveInfo{5342dde4 com.example.androidyue.bitmapdemo.NetworkReceiver p=0 o=0 m=0x108000}
    E/ActivityManager(  462): ANR in com.example.androidyue.bitmapdemo
    E/ActivityManager(  462): Reason: Broadcast of Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x8000010 cmp=com.example.androidyue.bitmapdemo/.NetworkReceiver (has extras) }
    E/ActivityManager(  462): Load: 0.37 / 0.2 / 0.14
    E/ActivityManager(  462): CPU usage from 26047ms to 0ms ago:
    E/ActivityManager(  462):   0.4% 58/adbd: 0% user + 0.4% kernel / faults: 1501 minor
    E/ActivityManager(  462):   0.3% 462/system_server: 0.1% user + 0.1% kernel
    E/ActivityManager(  462):   0% 4109/com.example.androidyue.bitmapdemo: 0% user + 0% kernel / faults: 6 minor
    E/ActivityManager(  462): 1.5% TOTAL: 0.5% user + 0.9% kernel + 0% softirq
    E/ActivityManager(  462): CPU usage from 87ms to 589ms later:
    E/ActivityManager(  462):   1.8% 58/adbd: 0% user + 1.8% kernel / faults: 30 minor
    E/ActivityManager(  462):     1.8% 58/adbd: 0% user + 1.8% kernel
    E/ActivityManager(  462): 4% TOTAL: 0% user + 4% kernel
    W/ActivityManager(  462): Killing ProcessRecord{5326d418 4109:com.example.androidyue.bitmapdemo/u0a10063}: background ANR
    I/ActivityManager(  462): Process com.example.androidyue.bitmapdemo (pid 4109) has died.
    

    除了日志,我们还可以根据前面提到的查看traces.txt文件。

    提问:可以更容易了解background ANR么?

    回答:当然可以,在Android开发者选项—>高级—>显示所有”应用程序无响应“勾选即可对后台ANR也进行弹窗显示,方便查看了解程序运行情况。

    本章总结

    最后我们来整理一下这个过程吧,SimpleImageLoader根据用户的配置来配置、启动请求队列,请求队列又会根据用户配置的线程数量来启动几个分发线程。这几个线程不断地从请求队列(线程安全)中读取图片加载请求,并且执行图片加载请求。这些请求是用户通过调用SimpleImageLoader的displayImage函数而产生的,内部会把这个调用封装成一个BitmapRequest对象,并且将该对象添加到请求队列中。这样就有了生产者(用户)和消费者(分发线程),整个SimpleImageLoader就随着CPU跳动而热血沸腾起来了!

    变量命名规范

    Github仓库链接

    SimpleImageLoader仓库地址

    变量(variables)采用小驼峰命名法。类中控件名称必须与xml布局id保持一致。

    公开的常量:定义为静态final,名称全部大写。eg: public staticfinal String ACTION_MAIN=”android.intent.action.MAIN”;

    静态变量:名称以s开头 eg:private static long sInstanceCount = 0;

    非静态的私有变量、protected的变量:以m开头,eg:private Intent mItent;

    接口命名规范

    接口(interface):命名规则与类一样采用大驼峰命名法,多以able或ible结尾,eg:interface Runable; interface Accessible;

    方法命名规范

    方法(methods):动词或动名词,采用小驼峰命名法,eg:onCreate(),run();

    本文由金沙国际官网发布于编程,转载请注明出处:Android代码规范参考指南,Android中的ANR用法详解

    关键词: