美團實習| 週記(四)

本週知識清單如下:

  • Android Lint工具
  • SimpleDateFormat
  • 線程池之Executors、ThreadPoolExecutor
  • 定時任務之Timer、ScheduledThreadPoolExecutor
  • OnTouchListener、OnClickListener的衝突
  • 一點小感悟

1.Android Lint工具

項目一期需求開發告一段落,爲了讓代碼更好,需要進一步去優化代碼結構和質量。

a.是Android Studio提供的代碼掃描分析工具,在不運行程序或者寫任何測試用例的情況下,幫助發現代碼結構和質量問題,並提供一些解決方案。

b.工作流程:根據預先配置的檢測標準檢查項目的源文件,發現潛在bug和可優化代碼,並將掃描結果顯示在控制檯或者Android Studio中的Event Log裏,流程圖如下:

其中,圖中幾個名詞含義:

  • 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…

step2:設定要檢測的源文件

默認選項是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.一點小感悟

這周的北京莫名的涼快,從上週末開始就斷斷續續的下了好幾場雨,相比於烈日,還是喜歡清爽的雨吧。工作的節奏也似乎跟着淅瀝瀝的雨慢了下來,後臺也纔跟着上線,距離下一期的開發還有些時日,也就空出一段難得不被人打擾的日子,做了些對上一期代碼質量和性能評價總結等收尾的事情,在這種時候發現到的問題反倒讓自己學的更多、成長的更快。

分享一波開發流程,自己也參與到了大部分的過程,雖然沒有寫很厲害的代碼,但是能體驗一波完整的開發流程也是收穫頗多呢!以及不得不知的互聯網職位縮寫含義

開發流程

轉眼也來北京一個多月了,除了頭幾天不太適應北京夏日的高溫、以及從未見過如此擁擠的地鐵之外,好像就沒有特別的感概,時而恍惚以爲自己仍在那個美麗的濱海城市大連了,大連在我的眼裏也是如此的繁華和熱鬧。

不過很奇怪的是,貌似不少人還以爲春秋招只和應屆生有關係,聽聞我未畢業就實習都一副喫驚的樣子。事實上,現在一年一度的春招,爲了能在秋招前提前鎖定一批優秀人才,企業的重心反而是在面向大三/研二學生的暑期實習上,這就是爲什麼暑期實習的招聘流程和秋招幾乎一致,要求也是較高的。

可能得益於學校有意識的培養,身邊有很多同學都在北京各大互聯網公司實習,還有一些老同學在北京讀書,又遇上那麼多的可愛的小夥伴,所以只不過換了個地方繼續生活,有何談孤單呢!

窗外一景

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章