android 學習筆記2
1、new Handler(Looper.getMainLooper())
Handler一定要在主執行緒例項化嗎?new Handler()和new Handler(Looper.getMainLooper())的區別
如果你不帶引數的例項化:Handler handler = new Handler();那麼這個會預設用當前執行緒的looper
一般而言,如果你的Handler是要來重新整理操作UI的,那麼就需要在主執行緒下跑。
情況:
1.要重新整理UI,handler要用到主執行緒的looper。那麼在主執行緒 Handler handler = new Handler(); 如果在其他執行緒,也要滿足這個功能的話,要
Handler handler = new Handler(Looper.getMainLooper());
2.不用重新整理ui,只是處理訊息。 當前執行緒如果是主執行緒的話,Handler handler = new Handler(); 不是主執行緒的話,
Looper.prepare(); Handler handler = new Handler(); Looper.loop();
或者
Handler handler = new Handler (Looper.getMainLooper());
若是例項化的時候用 Looper.getMainLooper() 就表示放到主UI執行緒去處理。
如果不是的話,因為只有UI執行緒預設 Loop.prepare(); Loop.loop() 過,其他執行緒需要手動呼叫這兩個,否則會報錯。
2、LayoutParams
先來看兩個例子:
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); layoutParams.bottomMargin = 20; LinearLayout linearLayout = new LinearLayout(this); Button button = new Button(this); button.setText("What"); TextView tv = new TextView(this); tv.setText("What1"); //呼叫Activity的addContentView函式 linearLayout.addView(tv); linearLayout.addView(button); this.addContentView(linearLayout, layoutParams); // 第二個例子 setContentView(R.layout.activity_main); LinearLayout linearLayout = (LinearLayout) findViewById(R.id.ll); Button button = new Button(this); button.setText("What"); TextView tv = new TextView(this); tv.setText("What1"); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); linearLayout.addView(tv, layoutParams); linearLayout.addView(button, layoutParams);
LayoutParams 類是用於child view(子檢視) 向 parent view(父檢視)傳達自己的意願的一個東西(孩子想變成什麼樣向其父親說明)。其實子檢視父檢視可以簡單理解成
一個LinearLayout 和 這個 LinearLayout 裡邊一個 TextView 的關係 TextView 就算 LinearLayout 的子檢視 child view 。需要注意的是LayoutParams 只是 ViewGroup 的一個內部類這裡邊這個也就是ViewGroup裡邊這個 LayoutParams 類是 base class 基類實際上每個不同的ViewGroup 都有自己的 LayoutParams 子類。
因此我們可以看到都是將 LayoutParams 用到子 view 身上。
在繼承BaseAdapter的時候,用getView返回View的時候,用程式碼控制佈局,需要用到 View.setLayoutParams,但是報錯了,報的是型別轉換錯誤,經過研究,發現,這裡不能使用ViewGroup.LayoutParams而必須使用對應父View的LayoutParams型別。如:某 View 被 LinearLayout 包含,則該View 的 setLayoutParams 引數型別必須是 LinearLayout.LayoutParams。
原因在於 LinearLayout(或其他繼承自 ViewGroup 的layout,如:RelativeLayout )在進行遞迴佈局的時候,LinearLayout 會獲取子 View 的 LayoutParams,並強制轉換成 LinearLayout.LayoutParams,如
1LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();或者是如下定義:
1LayoutParams lp = (LayoutParams) child.getLayoutParams();以轉換成內部型別 LinearLayout.LayoutParams。
自己測試執行的時候報空指標,原因為 child.getLayoutParams(); 這裡沒有獲得到子控制元件所在的佈局,檢視程式碼發現 parent.addView(child); 應該寫在上面,之後 child 才能 getLayoutParams();
在看一個例子:
//第一個引數為寬的設定,第二個引數為高的設定。 (用的時候注意修改LinearLayout字首,其實很多時候可以不寫它的) LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT ); //設定居中顯示: lp.gravity = Gravity.CENTER; //設定它的上下左右的margin:4個引數按順序分別是左上右下 lp.setMargins(10,10,10,10); //還可以這樣新增規則: lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); lp.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); //給某個View設定LayoutParams引數: btn1.setLayoutParams(lp); //最後將新增View到Layout中:(如果是在某個自定義的Layout佈局中,字首也可以省略) mLayout.addView(textView, lp); //其實不用LayoutParams也不會死啦 mLayout.addView(textView, tvWidth, tvHeight);
在看一個自定義 view 的例子
public class Card extends FrameLayout { public Card(Context context) { super(context); LayoutParams lp = null; background = new View(getContext()); lp = new LayoutParams(-1, -1); lp.setMargins(10, 10, 0, 0); background.setBackgroundColor(0x33ffffff); addView(background, lp); label = new TextView(getContext()); label.setTextSize(28); label.setGravity(Gravity.CENTER); lp = new LayoutParams(-1, -1); lp.setMargins(10, 10, 0, 0); addView(label, lp); setNum(0); } }
3、paint 中獲取文字的寬度
在使用Canvas繪製文字時,需要得到字串的長度,Paint類內給了兩個方法,measureText(),getTextBound();
可是對於同於字串兩個方法得出來的值有些差別:
getTextBounds() 得到的寬度總是比 measureText() 得到的寬度要小一點。
其中紫色的是 measureText(); 紅色的標記是 getTextBounds。其實我們從方法名字也可以看出區別,一個是取寬度,一個是取字型邊界。
4、文字基準定義和寫字
-
基準點是baseline
-
ascent:是baseline之上至字元最高處的距離
-
descent:是baseline之下至字元最低處的距離
-
leading:是上一行字元的descent到下一行的ascent之間的距離,也就是相鄰行間的空白距離
-
top:是指的是最高字元到baseline的值,即ascent的最大值
-
bottom:是指最低字元到baseline的值,即descent的最大值
Paint的ascent和descent方法--算高度
這兩個是native方法//Return the distance above (negative) the baseline (ascent) based on the current typeface字型 and text size. public native float ascent(); //Return the distance below (positive) the baseline (descent) based on the current typeface and text size. public native float descent();可以通過 mPaint.ascent()+ mPaint.descent() 獲取文字高度
/** * text:要繪製的文字 * x:繪製原點x座標 * y:繪製原點y座標 * paint:用來做畫的畫筆 */ public void drawText(String text, float x, float y, Paint paint)
上面這個建構函式是最常用的drawText方法,傳進去一個String物件就能畫出對應的文字。
但這裡有兩個引數需要非常注意,表示原點座標的x和y.很多同學可能會認為,這裡傳進去的原點引數(x,y)是所在繪製文字所在矩形的左上角的點。但實際上並不是!比如,我們上面如果要畫"harvic's blog"這幾個字,這個原點座標應當是下圖中綠色小點的位置
在(x,y)中最讓人捉急的是y座標,一般而言,(x,y)所代表的位置是所畫圖形對應的矩形的左上角點。但在drawText中是非常例外的,y所代表的是基線的位置!
5、fresco 網上獲取圖片
/** * 獲取服務端的圖片 * 為了避免圖片的尺寸過大,我們要對其進行一定的大小控制, * 避免其過大撐破影響其他模板 *//* private void prefetchImageTag(String url, final ImageView imageView) { ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)).build(); Fresco.getImagePipeline().fetchDecodedImage(request, FeedRuntime.getAppContext()).subscribe( new BaseBitmapDataSubscriber() { @Override protected void onNewResultImpl(@Nullable Bitmap bitmap) { if (bitmap != null) { imageView.setImageBitmap(bitmap); } } @Override protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) { } }, UiThreadImmediateExecutorService.getInstance()); }*/
6、Looper.loop()為什麼不會阻塞主執行緒
Android是基於事件驅動的,即所有Activity的生命週期都是通過Handler事件驅動的。loop方法中會呼叫MessageQueue的next方法獲取下一個message,當沒有訊息時,基於Linux pipe/epoll機制會阻塞在loop的queue.next()中的nativePollOnce()方法裡,並不會消耗CPU。
7、IdleHandler
IdleHandler 是一個回撥介面,可以通過MessageQueue的addIdleHandler新增實現類。當MessageQueue中的任務暫時處理完了(沒有新任務或者下一個任務延時在之後),這個時候會回撥這個介面,返回false,那麼就會移除它,返回true就會在下次message處理完了的時候繼續回撥。
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { //此處新增處理任務 return false; } });
queueIdle()返回true表示可以反覆執行該方法,即執行後還可以再次執行;返回false表示執行完該方法後會移除該IdleHandler,即只執行一次。
注意,在主執行緒中使用時queueIdle中不能執行太耗時的任務。
getWidth()方法和getMeasureWidth()區別
首先getMeasureWidth()方法在measure()過程結束後就可以獲取到了,而getWidth()方法要在layout()過程結束後才能獲取到。另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設定的,而getWidth()方法中的值則是通過檢視右邊的座標減去左邊的座標計算出來的。
requestLayout,invalidate,postInvalidate區別與聯絡
-
相同點:三個方法都有重新整理介面的效果。
-
不同點:invalidate和postInvalidate只會呼叫onDraw()方法;requestLayout則會重新呼叫onMeasure、onLayout、onDraw。
呼叫了invalidate方法後,會為該View新增一個標記位,同時不斷向父容器請求重新整理,父容器通過計算得出自身需要重繪的區域,直到傳遞到ViewRootImpl中,最終觸發performTraversals方法,進行開始View樹重繪流程(只繪製需要重繪的檢視)。
呼叫requestLayout方法,會標記當前View及父容器,同時逐層向上提交,直到ViewRootImpl處理該事件,ViewRootImpl會呼叫三大流程,從measure開始,對於每一個含有標記位的view及其子View都會進行測量onMeasure、佈局onLayout、繪製onDraw。
Android View 深度分析requestLayout、invalidate與postInvalidate
Binder機制,共享記憶體實現原理
為什麼使用Binder?概念
程序隔離
程序空間劃分:使用者空間(User Space)/核心空間(Kernel Space)
系統呼叫:使用者態與核心態
原理
跨程序通訊是需要核心空間做支援的。傳統的 IPC 機制如管道、Socket 都是核心的一部分,因此通過核心支援來實現程序間通訊自然是沒問題的。但是 Binder 並不是 Linux 系統核心的一部分,那怎麼辦呢?這就得益於 Linux 的動態核心可載入模組(Loadable Kernel Module,LKM)的機制;模組是具有獨立功能的程式,它可以被單獨編譯,但是不能獨立執行。它在執行時被連結到核心作為核心的一部分執行。這樣,Android 系統就可以通過動態新增一個核心模組執行在核心空間,使用者程序之間通過這個核心模組作為橋樑來實現通訊。
在 Android 系統中,這個執行在核心空間,負責各個使用者程序通過 Binder 實現通訊的核心模組就叫 Binder 驅動(Binder Dirver)。
那麼在 Android 系統中使用者程序之間是如何通過這個核心模組(Binder 驅動)來實現通訊的呢?難道是和前面說的傳統 IPC 機制一樣,先將資料從傳送方程序拷貝到核心快取區,然後再將資料從核心快取區拷貝到接收方程序,通過兩次拷貝來實現嗎?顯然不是,否則也不會有開篇所說的 Binder 在效能方面的優勢了。
這就不得不通道 Linux 下的另一個概念:記憶體對映。
Binder IPC 機制中涉及到的記憶體對映通過 mmap() 來實現,mmap() 是作業系統中一種記憶體對映的方法。記憶體對映簡單的講就是將使用者空間的一塊記憶體區域對映到核心空間。對映關係建立後,使用者對這塊記憶體區域的修改可以直接反應到核心空間;反之核心空間對這段區域的修改也能直接反應到使用者空間。
一次完整的 Binder IPC 通訊過程通常是這樣:
-
首先 Binder 驅動在核心空間建立一個數據接收快取區;
-
接著在核心空間開闢一塊核心快取區,建立核心快取區和核心中資料接收快取區之間的對映關係,以及核心中資料接收快取區和接收程序使用者空間地址的對映關係;
-
傳送方程序通過系統呼叫 copyfromuser() 將資料 copy 到核心中的核心快取區,由於核心快取區和接收程序的使用者空間存在記憶體對映,因此也就相當於把資料傳送到了接收程序的使用者空間,這樣便完成了一次程序間的通訊。
Binder通訊模型
Binder是基於C/S架構的,其中定義了4個角色:Client、Server、Binder驅動和ServiceManager。
-
Binder驅動:類似網路通訊中的路由器,負責將Client的請求轉發到具體的Server中執行,並將Server返回的資料傳回給Client。
-
ServiceManager:類似網路通訊中的DNS伺服器,負責將Client請求的Binder描述符轉化為具體的Server地址,以便Binder驅動能夠轉發給具體的Server。Server如需提供Binder服務,需要向ServiceManager註冊。
具體的通訊過程 -
Server向ServiceManager註冊。Server通過Binder驅動向ServiceManager註冊,宣告可以對外提供服務。ServiceManager中會保留一份對映表。
-
Client向ServiceManager請求Server的Binder引用。Client想要請求Server的資料時,需要先通過Binder驅動向ServiceManager請求Server的Binder引用(代理物件)。
-
向具體的Server傳送請求。Client拿到這個Binder代理物件後,就可以通過Binder驅動和Server進行通訊了。
-
Server返回結果。Server響應請求後,需要再次通過Binder驅動將結果返回給Client。
ServiceManager是一個單獨的程序,那麼Server與ServiceManager通訊是靠什麼呢?
當Android系統啟動後,會建立一個名稱為servicemanager的程序,這個程序通過一個約定的命令BINDERSETCONTEXT_MGR向Binder驅動註冊,申請成為為ServiceManager,Binder驅動會自動為ServiceManager建立一個Binder實體。並且這個Binder實體的引用在所有的Client中都為0,也就說各個Client通過這個0號引用就可以和ServiceManager進行通訊。Server通過0號引用向ServiceManager進行註冊,Client通過0號引用就可以獲取到要通訊的Server的Binder引用。
寫給 Android 應用工程師的 Binder 原理剖析
一篇文章瞭解相見恨晚的 Android Binder 程序間通訊機制
序列化的方式
Serializable是Java提供的一個序列化介面,是一個空介面,用於標示物件是否可以支援序列化,通過ObjectOutputStrean及ObjectInputStream實現序列化和反序列化的過程。注意可以為需要序列化的物件設定一個serialVersionUID,在反序列化的時候系統會檢測檔案中的serialVersionUID是否與當前類的值一致,如果不一致則說明類發生了修改,反序列化失敗。因此對於可能會修改的類最好指定serialVersionUID的值。
Parcelable是Android特有的一個實現序列化的介面,在Parcel內部包裝了可序列化的資料,可以在Binder中自由傳輸。序列化的功能由writeToParcel方法來完成,最終通過Parcel的一系列write方法完成。反序列化功能由CREAOR來完成,其內部標明瞭如何建立序列化物件和陣列,並通過Parcel的一系列read方法來完成反序列化的過程。
Fragment的懶載入實現
Fragment可見狀態改變時會被呼叫setUserVisibleHint()方法,可以通過複寫該方法實現Fragment的懶載入,但需要注意該方法可能在onVIewCreated之前呼叫,需要確保介面已經初始化完成的情況下再去載入資料,避免空指標。
RecyclerView與ListView(快取原理,區別聯絡,優缺點)
快取區別:
層級不同:-
ListView有兩級快取,在螢幕與非螢幕內。
-
RecyclerView比ListView多兩級快取,支援多個離屏ItemView快取(匹配pos獲取目標位置的快取,如果匹配則無需再次bindView),支援開發者自定義快取處理邏輯,支援所有RecyclerView共用同一個RecyclerViewPool(快取池)。快取不同:
- ListView快取View。
- RecyclerView快取RecyclerView.ViewHolder,抽象可理解為:
- View + ViewHolder(避免每次createView時呼叫findViewById) + flag(標識狀態);
優點
RecylerView提供了局部重新整理的介面,通過區域性重新整理,就能避免呼叫許多無用的bindView。
RecyclerView的擴充套件性更強大(LayoutManager、ItemDecoration等)。
Android兩種虛擬機器區別與聯絡
Android中的Dalvik虛擬機器相較於Java虛擬機器針對手機的特點做了很多優化。
Dalvik基於暫存器,而JVM基於棧。在基於暫存器的虛擬機器裡,可以更為有效的減少冗餘指令的分發和減少記憶體的讀寫訪問。
Dalvik經過優化,允許在有限的記憶體中同時執行多個虛擬機器的例項,並且每一個 Dalvik應用作為一個獨立的Linux程序執行。
java虛擬機器執行的是java位元組碼。(java類會被編譯成一個或多個位元組碼.class檔案,打包到.jar檔案中,java虛擬機器從相應的.class檔案和.jar檔案中獲取相應的位元組碼)
Dalvik執行的是自定義的.dex位元組碼格式。(java類被編譯成.class檔案後,會通過一個dx工具將所有的.class檔案轉換成一個.dex檔案,然後dalvik虛擬機器會從其中讀取指令和資料)
Android開發之淺談java虛擬機器和Dalvik虛擬機器的區別
參考文章:
https://www.kancloud.cn/aslai/interview-guide/1126388