美团实习| 周记(四)

本周知识清单以下:

  • Android Lint工具
  • SimpleDateFormat
  • 线程池之Executors、ThreadPoolExecutor
  • 定时任务之Timer、ScheduledThreadPoolExecutor
  • OnTouchListener、OnClickListener的冲突
  • 一点小感悟

1.Android Lint工具html

项目一期需求开发告一段落,为了让代码更好,须要进一步去优化代码结构和质量。java

a.是Android Studio提供的代码扫描分析工具,在不运行程序或者写任何测试用例的状况下,帮助发现代码结构和质量问题,并提供一些解决方案。安全

b.工做流程:根据预先配置的检测标准检查项目的源文件,发现潜在bug和可优化代码,并将扫描结果显示在控制台或者Android Studio中的Event Log里,流程图以下:bash

其中,图中几个名词含义:多线程

  • App Source Files:包括Java代码、XML代码、icon及ProGuard配置文件等
  • lint.xml:Lint检测的执行标准配置文件
  • lint Tool:静态代码扫描工具,可运行在命令行或者Android Studio中
  • Correctness:正确性,如硬编码、使用过期API等
  • Usability :可用性,如不在文本字段上指定输入的类型等
  • Security: 安全性,如WebView中容许使用JavaScriptInterface等
  • Accessibility :可达性,如图片没有使用contentDescription等。
  • Performance: 性能,如静态引用、循环引用等
  • Internationalization:国际化,如直接使用汉字、没有使用资源引用等

c.使用方法:这里直接用Android Studio的GUI操做并发

step1:Lint使用路径为『工具栏 -> Analyze -> Inspect Code…app

step2:设定要检测的源文件ide

默认选项是Whole project(整个项目),还能够选择Custom scope自定义文件,有多个选项:Project Files(全部项目文件)、Project Production Files(项目的代码文件)、Project Test Files(项目的测试文件)、Open Files(当前打开的文件)、Module 'app'(主要的ap 模块)、Current File(当前文件)。函数

固然,还能选择特定的文件,选择右侧的'...'高并发

点击左上角'+'添加一个检查范围,可选项有Local(只能当前项目使用)和Shared(其余项目也可以使用)。

这里选择Shared,会提示起规则名。

根据右侧四个按钮的提示:Include(包括当前文件夹内的文件,但不包括其子文件夹)、Include Recursively(包括当前文件夹,及其子文件夹内全部的文件夹)、Exclude(移除当前文件夹,但不包括子文件夹)、Exclude Recursively(移除当前文件夹、及其全部子文件夹),来制定规则。

这里对app选择了Include Recursively,可看到该文件夹及其子文件夹全部文件都被选中了,而且变成了绿色,右上角提示总共有385个文件夹要扫描。

step3:确认完成,等待检测结果

结果显示在底部Inspection对话框,能够看到,除了以前说起的六个,还有Class structure(类结构,如全局变量可替换为局部变量等)、Code maturity issues(代码成熟度问题,如使用弃用的方法等)、Code style issues(代码风格问题,如不必的符号等)、Compiler issues(编译器问题,如集合缺乏泛型符号)、Control flow issues(控制流问题,如去掉不必的if/else语句)、Data flow issues(数据流问题,如无用的局部变量)、Declaration redundancy(声明冗余,如方法返回值可为void等)、Error handling(错误处理,如异常处理等)、Spelling(拼写)等等,只要打开相应的选项卡,就能够在右侧对话框看到具体描述和问题所在了。

推荐阅读Lint常见的问题及解决方案


2.SimpleDateFormat

a.SimpleDateFormat是一个用于格式化和分析数据的具体类。继承关系以下:

Java.lang.Object
   |
   +----java.text.Format
           |
           +----java.text.DateFormat
                   |
                   +----java.text.SimpleDateFormat
复制代码

b.经常使用方法:

先用其构造函数SimpleDateFormat(String str)构造一个格式化日期的格式,再经过它的format(Date date)将指定的日期对象格式化为指定格式的字符串。

假设格式化日期的形式为yyyyy-MM-dd hh:mm:ss a E,含义:

  • yyyyy:年,可匹配如 : 2018。Y和y都表示年。
  • MM:月,可匹配如 : 07。
  • dd:日,可匹配如13。
  • hh:时,可匹配如08。注意:大写H表示24进制计时,小写h表示12进制计时。
  • mm:分,可匹配如23。
  • ss:秒,可匹配如18。
  • a:上/下午,上午为AM、下午为PM。
  • E:星期,可匹配如Fri。

c.使用注意:

在阿里开发手册中一段关于SimpleDateFormat的规范:

【强制】SimpleDateFormat是线程不安全的类,通常不要定义为static变量,若是定义为static,必须加锁,或者使用DateUtils工具类。

缘由分析:在SimpleDateFormat内部持有一个Calendar对象的引用,若是把SimpleDateFormat定义为static,可能存在多个Thread同时共享它,即共享对这个Calendar的引用。在高并发状况下,易出现幻读成员变量的现象。

//解决方案一:定义为局部变量
private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
    public String getFormat(Date date){
        SimpleDateFormat sdf = new SimpleDateFormat(FORMAT);
        return sdf.format(date);
复制代码
//解决方案二:加锁
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public void getFormat(){
        synchronized (sdf){
        sdf.format(new Date());
        ….;
    }
复制代码
//解决方案三:使用ThreadLocal使得每一个线程都有本身的SimpleDateFormat对象
private static final ThreadLocal<DateFormat> df = newThreadLocal<DateFormat>() {     
       @Override      
       protected DateFormatinitialValue() {
        return newSimpleDateFormat("yyyy-MM-dd");     
      } 
 }; 
复制代码

推荐阅读SimpleDateFormat线程不安全及解决办法


3.线程池之Executors、ThreadPoolExecutor

a.Executors的类型:

//建立一个单一线程池:线程以队列顺序来执行。
ExecutorService threadPool = Executors.newSingleThreadExecutor();
复制代码
//建立一个定长线程池,超出的线程会在队列中等待。
ExecutorService threadPool = Executors.newFixedThreadPool(2);
复制代码
//建立一个定长线程池,支持定时及周期性任务执行。 
ExecutorService threadPool = Executors.newScheduledThreadPool(3);
复制代码
//建立一个无界线程池:可进行线程自动回收,可存放线程数量最大值为Integer.MAX_VALUE
ExecutorService threadPool = Executors.newCachedThreadPool();
复制代码

b.在阿里开发手册中说明了Executors各个方法的弊端

  • newFixedThreadPoolnewSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费很是大的内存,甚至OOM。
  • newCachedThreadPoolnewScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会建立数量很是多的线程,甚至OOM。

c.解决方式:改用ThreadPoolExecutor建立线程池,便于明确线程池的运行规则,规避资源耗尽的风险。

其中,ExecutorThreadPoolExecutorScheduledExecutorServiceScheduledThreadPoolExecutor关系以下:

java.util.concurrent.Executor : 负责线程的使用与调度的根接口 
  |–ExecutorService:Executor的子接口,线程池的主要接口 
      |–ThreadPoolExecutor:ExecutorService的实现类 
      |–ScheduledExecutorService:ExecutorService的子接口,负责线程的调度 
          |–ScheduledThreadPoolExecutor:继承了ThreadPoolExecutor实现了ScheduledExecutorService
复制代码

d.ThreadPoolExecutor的构造函数:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
复制代码

其中,各个参数的具体含义详见要点提炼|开发艺术之线程,以及有关拒绝策略

而后根据具体需求来建立ThreadPoolExecutor对象,并提供一系列参数来配置线程池便可。而不是直接用有默认配置的Executors建立,事实上看过源码也知道Executors底层也是经过ThreadPoolExecutor去实现的。

推荐阅读JDK 源码解析--Executors、ExecutorService、ThreadPoolExecutor 线程池


4.定时任务之Timer、ScheduledThreadPoolExecutor

a.Timer常见使用:

//延迟2s后执行timer定时器内的任务
Timer  timer = new Timer();
timer.schedule(new TimerTask() {
      @Override
      public void run() {
          //do something

       }
}, 2000);
复制代码
//延迟2s后执行timer定时器内的任务,以后每隔1s执行一次
Timer  timer = new Timer();
timer.schedule(new TimerTask() {
      @Override
      public void run() {
          //do something

       }
}, 2000,1000);
复制代码

b.Timer管理延时任务的缺陷:

  • Timer内部只建立了一个线程,所以若是存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会影响其余任务的执行。
  • Timer建立的线程没有处理异常,所以当多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行。

解决办法:JDK5.0后推荐使用ScheduledThreadPoolExecutor代替Timer,顾名思义,ScheduledThreadPoolExecutor内部重用线程池,使用了多线程,使得单个任务的执行不会影响其余线程。

c.ScheduledThreadPoolExecutor的常见使用:

//延迟2s后执行timer定时器内的任务,以后每隔1s执行一次,单位为毫秒
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);//建立大小为2的线程池
executor.scheduleAtFixedRate(new Runnable() {
       @Override
       public void run() {
           //do something
       }
}, 2000, 1000, TimeUnit.MILLISECONDS);
复制代码

其中,间隔单位的参数有:

  • TimeUnit.MILLISECONDS :毫秒
  • TimeUnit.SECONDS :秒
  • TimeUnit.MINUTES :分钟
  • TimeUnit.HOURS :小时
  • TimeUnit.DAYS:天

推荐阅读深刻理解Java线程池:ScheduledThreadPoolExecutor


5.OnTouchListener、OnClickListener的冲突

a.优先度onTouch()>onTouchEvent()>onClick()

//setOnTouchListener须要重写onTouch()
bt.setOnTouchListener(new View.OnTouchListener() {
       @Override
       public boolean onTouch(View v, MotionEvent event) {
            //do something
       return false;
       }
});
复制代码
//setOnClickListener须要重写onClick()
bt.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
            //do something
       }
});
复制代码
@Override
public boolean onTouchEvent(MotionEvent event) {
        //do something
        return super.onTouchEvent(event);
}
复制代码

b.Warning:当对一个控件使用setOnTouchListener() 或对自定义控件重写onTouchEvent()会出现警告:

If a View that overrides onTouchEvent or uses an OnTouchListener does not also implement performClick and call it when clicks are detected, the View may not handle accessibility actions properly. Logic handling the click actions should ideally be placed in View#performClick as some accessibility services invoke performClick when a click action should occur.

大意是:若是重写了view的onTouchEvent方法或者设置了OnTouchListener,但没有实现performClick并在检测到点击时调用它,view可能没法正确处理辅助操做。理想状况下,处理点击操做的逻辑应放在View#performClick中,由于某些辅助功能服务会在发生单击操做时调用performClick。

c.缘由onClick()会经过performClick()完成点击事件的,而在onTouch()onTouchEvent()ACTION_UP过程当中会启用一个新的线程来调用performClick(),于是可能会屏蔽掉onClick()中设置的事件。

d.解决办法

  • 若是是使用setOnTouchListener(),那么就在重写onTouch()ACTION_UP状况下调用performClick()
bt.setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
           switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        break;
                    case MotionEvent.ACTION_MOVE:
                        break;
                    case MotionEvent.ACTION_UP:
                        //如下为添加内容
                        button.performClick();
                        break;
            }
            return false;//若是返回true,因为优先级较高,后面onTouchEvent、onClick将不会被触发
          }
     });
复制代码
  • 若是是重写onTouchEvent(),那么就在ACTION_UP状况下调用performClick()
@Override
public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_CANCEL:
                    break;
                //如下为添加内容
                case MotionEvent.ACTION_UP:
                    performClick();
                    break;
            }
        return true;
    }
复制代码

6.一点小感悟

这周的北京莫名的凉快,从上周末开始就断断续续的下了好几场雨,相比于烈日,仍是喜欢清爽的雨吧。工做的节奏也彷佛跟着淅沥沥的雨慢了下来,后台也才跟着上线,距离下一期的开发还有些时日,也就空出一段可贵不被人打扰的日子,作了些对上一期代码质量和性能评价总结等收尾的事情,在这种时候发现到的问题反倒让本身学的更多、成长的更快。

分享一波开发流程,本身也参与到了大部分的过程,虽然没有写很厉害的代码,可是能体验一波完整的开发流程也是收获颇多呢!以及不得不知的互联网职位缩写含义

开发流程图

转眼也来北京一个多月了,除了头几天不太适应北京夏日的高温、以及从未见过如此拥挤的地铁以外,好像就没有特别的感概,时而恍惚觉得本身仍在那个美丽的滨海城市大连了,大连在个人眼里也是如此的繁华和热闹。

不过很奇怪的是,貌似很多人还觉得春秋招只和应届生有关系,听闻我未毕业就实习都一副吃惊的样子。事实上,如今一年一度的春招,为了能在秋招前提早锁定一批优秀人才,企业的重心反而是在面向大三/研二学生的暑期实习上,这就是为何暑期实习的招聘流程和秋招几乎一致,要求也是较高的。

可能得益于学校有意识的培养,身边有不少同窗都在北京各大互联网公司实习,还有一些老同窗在北京读书,又赶上那么多的可爱的小伙伴,因此只不过换了个地方继续生活,有何谈孤单呢!

窗外一景
相关文章
相关标签/搜索