1. 程式人生 > >深入理解Android之AOP

深入理解Android之AOP

格式更加精美的PDF版請到:http://vdisk.weibo.com/s/z68f8l0xTgCLK 下載
一、閒談AOP

大家都知道OOP,即ObjectOriented Programming,面向物件程式設計。而本文要介紹的是AOP。AOP是Aspect Oriented Programming的縮寫,中譯文為面向切向程式設計。OOP和AOP是什麼關係呢?首先:

    l OOP和AOP都是方法論。我記得在剛學習C++的時候,最難學的並不是C++的語法,而是C++所代表的那種看問題的方法,即OOP。同樣,今天在AOP中,我發現其難度並不在利用AOP幹活,而是從AOP的角度來看待問題,設計解決方法。這就是為什麼我特意強調AOP是一種方法論的原因!
    l 在OOP的世界中,問題或者功能都被劃分到一個一個的模組裡邊。每個模組專心幹自己的事情,模組之間通過設計好的介面互動。從圖示來看,OOP世界中,最常見的表示比如:


圖1  Android Framework中的模組

圖1中所示為AndroidFramework中的模組。OOP世界中,大家畫的模組圖基本上是這樣的,每個功能都放在一個模組裡。非常好理解,而且確實簡化了我們所處理問題的難度。

OOP的精髓是把功能或問題模組化,每個模組處理自己的家務事。但在現實世界中,並不是所有問題都能完美得劃分到模組中。舉個最簡單而又常見的例子:現在想為每個模組加上日誌功能,要求模組執行時候能輸出日誌。在不知道AOP的情況下,一般的處理都是:先設計一個日誌輸出模組,這個模組提供日誌輸出API,比如Android中的Log類。然後,其他模組需要輸出日誌的時候呼叫Log類的幾個函式,比如e(TAG,...),w(TAG,...),d(TAG,...),i(TAG,...)等。

在沒有接觸AOP之前,包括我在內,想到的解決方案就是上面這樣的。但是,從OOP角度看,除了日誌模組本身,其他模組的家務事絕大部分情況下應該都不會包含日誌輸出功能。什麼意思?以ActivityManagerService為例,你能說它的家務事裡包含日誌輸出嗎?顯然,ActivityManagerService的功能點中不包含輸出日誌這一項。但實際上,軟體中的眾多模組確實又需要列印日誌。這個日誌輸出功能,從整體來看,都是一個面上的。而這個面的範圍,就不侷限在單個模組裡了,而是橫跨多個模組。

    l 在沒有AOP之前,各個模組要列印日誌,就是自己處理。反正日誌模組的那幾個API都已經寫好了,你在其他模組的任何地方,任何時候都可以呼叫。功能是得到了滿足,但是好像沒有Oriented的感覺了。是的,隨意加日誌輸出功能,使得其他模組的程式碼和日誌模組耦合非常緊密。而且,將來要是日誌模組修改了API,則使用它們的地方都得改。這種搞法,一點也不酷。

AOP的目標就是解決上面提到的不cool的問題。在AOP中:

    l 第一,我們要認識到OOP世界中,有些功能是橫跨並嵌入眾多模組裡的,比如列印日誌,比如統計某個模組中某些函式的執行時間等。這些功能在各個模組裡分散得很厲害,可能到處都能見到。
    l 第二,AOP的目標是把這些功能集中起來,放到一個統一的地方來控制和管理。如果說,OOP如果是把問題劃分到單個模組的話,那麼AOP就是把涉及到眾多模組的某一類問題進行統一管理。比如我們可以設計兩個Aspects,一個是管理某個軟體中所有模組的日誌輸出的功能,另外一個是管理該軟體中一些特殊函式呼叫的許可權檢查。

講了這麼多,還是先來看個例子。在這個例子中,我們要:

    l Activity的生命週期的幾個函式執行時,要輸出日誌。
    l 幾個重要函式呼叫的時候,要檢查有沒有許可權。

注意,本文的例子程式碼在https://code.csdn.net/Innost/androidaopdemo上。
二、沒有AOP的例子

先來看沒有AOP的情況下,程式碼怎麼寫。主要程式碼都在AopDemoActivity中

[-->AopDemoActivity.java]

    public class AopDemoActivity extends Activity {
       private static final String TAG = "AopDemoActivity";
      onCreate,onStart,onRestart,onPause,onResume,onStop,onDestory返回前,都輸出一行日誌
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.layout_main);
           Log.e(TAG,"onCreate");
        }
       protected void onStart() {
           super.onStart();
            Log.e(TAG, "onStart");
        }
       protected void onRestart() {
           super.onRestart();
            Log.e(TAG, "onRestart");
        }
        protectedvoid onResume() {
           super.onResume();
            Log.e(TAG, "onResume");
       checkPhoneState會檢查app是否申明瞭android.permission.READ_PHONE_STATE許可權
            checkPhoneState();
        }
       protected void onPause() {
           super.onPause();
            Log.e(TAG, "onPause");
        }
       protected void onStop() {
           super.onStop();
            Log.e(TAG, "onStop");
        }
       protected void onDestroy() {
           super.onDestroy();
            Log.e(TAG, "onDestroy");
        }
       private void checkPhoneState(){
           if(checkPermission("android.permission.READ_PHONE_STATE")== false){
               Log.e(TAG,"have no permission to read phone state");
               return;
            }
           Log.e(TAG,"Read Phone State succeed");
           return;
        }
       private boolean checkPermission(String permissionName){
           try{
               PackageManager pm = getPackageManager();
              //呼叫PackageMangaer的checkPermission函式,檢查自己是否申明使用某許可權
               int nret = pm.checkPermission(permissionName,getPackageName());
               return nret == PackageManager.PERMISSION_GRANTED;
            }......
        }
    }

程式碼很簡單。但是從這個小例子中,你也會發現要是這個程式比較複雜的話,到處都加Log,或者在某些特殊函式加許可權檢查的程式碼,真的是一件挺繁瑣的事情。
三、AspectJ介紹
3.1  AspectJ極簡介

AOP雖然是方法論,但就好像OOP中的Java一樣,一些先行者也開發了一套語言來支援AOP。目前用得比較火的就是AspectJ了,它是一種幾乎和Java完全一樣的語言,而且完全相容Java(AspectJ應該就是一種擴充套件Java,但它不是像Groovy[1]那樣的拓展。)。當然,除了使用AspectJ特殊的語言外,AspectJ還支援原生的Java,只要加上對應的AspectJ註解就好。所以,使用AspectJ有兩種方法:

l 完全使用AspectJ的語言。這語言一點也不難,和Java幾乎一樣,也能在AspectJ中呼叫Java的任何類庫。AspectJ只是多了一些關鍵詞罷了。

l 或者使用純Java語言開發,然後使用AspectJ註解,簡稱@AspectJ。

Anyway,不論哪種方法,最後都需要AspectJ的編譯工具ajc來編譯。由於AspectJ實際上脫胎於Java,所以ajc工具也能編譯java原始碼。

AspectJ現在託管於Eclipse專案中,官方網站是:

    l http://www.eclipse.org/aspectj/   <=AspectJ官方網站
    l http://www.eclipse.org/aspectj/doc/released/runtime-api/index.html  <=AspectJ類庫參考文件,內容非常少
    l http://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/index.html  <[email protected]文件,以後我們用Annotation的方式最多。

3.2  AspectJ語法

題外話:AspectJ東西比較多,但是AOP做為方法論,它的學習和體會是需要一步一步,並且一定要結合實際來的。如果一下子講太多,反而會疲倦。更可怕的是,有些膽肥的同學要是一股腦把所有高階玩法全弄上去,反而得不償失。這就是是方法論學習和其他知識學習不一樣的地方。請大家切記。
3.2.1  Join Points介紹

Join Points(以後簡稱JPoints)是AspectJ中最關鍵的一個概念。什麼是JPoints呢?JPoints就是程式執行時的一些執行點。那麼,一個程式中,哪些執行點是JPoints呢?比如:

    l 一個函式的呼叫可以是一個JPoint。比如Log.e()這個函式。e的執行可以是一個JPoint,而呼叫e的函式也可以認為是一個JPoint。
    l 設定一個變數,或者讀取一個變數,也可以是一個JPoint。比如Demo類中有一個debug的boolean變數。設定它的地方或者讀取它的地方都可以看做是JPoints。
    l for迴圈可以看做是JPoint。

理論上說,一個程式中很多地方都可以被看做是JPoint,但是AspectJ中,只有如表1所示的幾種執行點被認為是JPoints:

表1  AspectJ中的Join Point

Join Points

說明

示例

method call

函式呼叫

比如呼叫Log.e(),這是一處JPoint

method execution

函式執行

比如Log.e()的執行內部,是一處JPoint。注意它和method call的區別。method call是呼叫某個函式的地方。而execution是某個函式執行的內部。

constructor call

建構函式呼叫

和method call類似

constructor execution

建構函式執行

和method execution類似

field get

獲取某個變數

比如讀取DemoActivity.debug成員

field set

設定某個變數

比如設定DemoActivity.debug成員

pre-initialization

Object在建構函式中做得一些工作。

很少使用,詳情見下面的例子

initialization

Object在建構函式中做得工作

詳情見下面的例子

static initialization

類初始化

比如類的static{}

handler

異常處理

比如try catch(xxx)中,對應catch內的執行

advice execution

這個是AspectJ的內容,稍後再說

表1列出了AspectJ所認可的JoinPoints的型別。下面我們來看個例子以直觀體會一把。

圖2  示例程式碼

圖2是一個Java示例程式碼,下面我們將打印出其中所有的join points。圖3所示為打印出來的join points:

圖3  所有的join points


圖3中的輸出為從左到右,我們來解釋紅框中的內容。先來看左圖的第一個紅框:

    l staticinitialization(test.Test.<clinit>):表示當前是哪種型別的JPoint,括號中代表目標物件是誰(此處是指Test class的類初始化)。由於Test類沒有指定static block,所以後面的at:Test.java:0 表示程式碼在第0行(其實就是沒有找到原始碼的意思)。
    l Test類初始化完後,就該執行main函數了。所以,下一個JPoint就是execution(voidtest.Test.main(String[]))。括號中表示此JPoint對應的是test.Test.main函式。at:Test.java:30表示這個JPoint在原始碼的第30行。大家可以對比圖2的原始碼,很準確!
    l main函式裡首先是執行System.out.println。而這一行程式碼實際包括兩個JPoint。一個是get(PrintStream java.lang.System.out),get表示Field get,它表示從System中獲取out物件。另外一個是call(void java.io.PrintStream.println(String)),這是一個call型別的JPoint,表示執行out.println函式。

再來看左圖第二個紅框,它表示TestBase的類的初始化,由於原始碼中為TestBase定義了static塊,所以這個JPoint清晰指出了原始碼的位置是at:Test.java:5

接著看左圖第三個紅框,它和物件的初始化有關。在原始碼中,我們只是構造了一個TestDerived物件。它會先觸發TestDerived Preinitialization JPoint,然後觸發基類TestBase的PreInitialization JPoint。注意紅框中的before和after 。在TestDerived和TestBase所對應的PreInitialization before和after中都沒有包含其他JPoint。所以,Pre-Initialization應該是建構函式中一個比較基礎的Phase。這個階段不包括類中成員變數定義時就賦值的操作,也不包括建構函式中對某些成員變數進行的賦值操作。

而成員變數的初始化(包括成員變數定義時就賦值的操作,比如原始碼中的int base = 0,以及在建構函式中所做的賦值操作,比如原始碼中的this.derived = 1000)都被囊括到initialization階段。請讀者對應圖三第二個紅框到第三個紅框(包括第3個紅框的內容)看看是不是這樣的。

最後來看第5個紅框。它包括三個JPoint:

    l testMethod的call型別JPoint
    l testMethod的execution型別JPonint
    l 以及對異常捕獲的Handler型別JPoint

好了。JPoint的介紹就先到此。現在大家對JoinPoint應該有了一個很直觀的體會,簡單直白粗暴點說,JoinPoint就是一個程式中的關鍵函式(包括建構函式)和程式碼段(staticblock)。

為什麼AspectJ首先要定義好JoinPoint呢?大家仔細想想就能明白,以列印log的AopDemo為例,log在哪裡列印?自然是在一些關鍵點去列印。而誰是關鍵點?AspectJ定義的這些型別的JPoint就能滿足我們絕大部分需求。

注意,要是想在一個for迴圈中列印一些日誌,而AspectJ沒有這樣的JPoint,所以這個需求我們是無法利用AspectJ來實現了。另外,不同的軟體框架對錶1中的JPoint型別支援也不同。比如Spring中,不是所有AspectJ支援的JPoint都有。
(1) 自己動手試

圖2的示例程式碼我也放到了https://code.csdn.net/Innost/androidaopdemo上。請小夥伴們自己下載玩耍。具體的操作方法是:

    l 下載得到androidaopdemo中,有一個aspectj-test目錄。
    l aspectj-test目錄中有一個libs目錄,裡邊有一個檔案aspectj-1.8.7.jar檔案。執行這個檔案(java -jar aspectj-1.8.7.jar,安裝aspectj)。安裝完後,按照圖示要求將aspectj的安裝路徑加到PATH中,然後export。這樣,就可以在命令列中執行aspectj的命令了。比如編譯工具ajc。
    l 另外,libs下還有一個aspectjrt.jar,這個是aspectjt執行時的依賴庫。使用AspectJ的程式都要包含該jar包。
    l 執行create-jar.sh。它會編譯幾個檔案,然後生成test.jar。
    l 執行test.jar(java -jar test.jar),就會打印出圖3的log。

我已經編譯並提交了一個test.jar到git上,小夥伴們可以執行一把玩玩!
3.2.2  Pointcuts介紹

pointcuts這個單詞不好翻譯,此處直接用英文好了。那麼,Pointcuts是什麼呢?前面介紹的內容可知,一個程式會有很多的JPoints,即使是同一個函式(比如testMethod這個函式),還分為call型別和execution型別的JPoint。顯然,不是所有的JPoint,也不是所有型別的JPoint都是我們關注的。再次以AopDemo為例,我們只要求在Activity的幾個生命週期函式中列印日誌,只有這幾個生命週期函式才是我們業務需要的JPoint,而其他的什麼JPoint我不需要關注。

怎麼從一堆一堆的JPoints中選擇自己想要的JPoints呢?恩,這就是Pointcuts的功能。一句話,Pointcuts的目標是提供一種方法使得開發者能夠選擇自己感興趣的JoinPoints。

在圖2的例子中,怎麼把Test.java中所有的Joinpoint選擇出來呢?用到的pointcut格式為:

pointcuttestAll():within(Test)。

AspectJ中,pointcut有一套標準語法,涉及的東西很多,還有一些比較高階的玩法。我自己琢磨了半天,需不需要把這些內容一股腦都搬到此文呢?回想我自己學習AOP的經歷,好像看了幾本書,記得比較清楚的都是簡單的case,而那些複雜的case則是到實踐中,確實有需求了,才回過頭來,重新查閱文件來實施的。恩,那就一步一步來吧。
(1) 一個Pointcuts例子

直接來看一個例子,現在我想把圖2中的示例程式碼中,那些呼叫println的地方找到,該怎麼弄?程式碼該這麼寫:

public pointcut  testAll(): call(public  *  *.println(..)) && !within(TestAspect) ;

注意,aspectj的語法和Java一樣,只不過多了一些關鍵詞

我們來看看上述程式碼

  第一個public:表示這個pointcut是public訪問。這主要和aspect的繼承關係有關,屬於AspectJ的高階玩法,本文不考慮。

  pointcut:關鍵詞,表示這裡定義的是一個pointcut。pointcut定義有點像函式定義。總之,在AspectJ中,你得定義一個pointcut。

  testAll():pointcut的名字。在AspectJ中,定義Pointcut可分為有名和匿名兩種辦法。個人建議使用named方法。因為在後面,我們要使用一個pointcut的話,就可以直接使用它的名字就好。

  testAll後面有一個冒號,這是pointcut定義名字後,必須加上。冒號後面是這個pointcut怎麼選擇Joinpoint的條件。

  本例中,call(public  *  *.println(..))是一種選擇條件。call表示我們選擇的Joinpoint型別為call型別。

  public  **.println(..):這小行程式碼使用了萬用字元。由於我們這裡選擇的JoinPoint型別為call型別,它對應的目標JPoint一定是某個函式。所以我們要找到這個/些函式。public  表示目標JPoint的訪問型別(public/private/protect)。第一個*表示返回值的型別是任意型別。第二個*用來指明包名。此處不限定包名。緊接其後的println是函式名。這表明我們選擇的函式是任何包中定義的名字叫println的函式。當然,唯一確定一個函式除了包名外,還有它的引數。在(..)中,就指明瞭目標函式的引數應該是什麼樣子的。比如這裡使用了萬用字元..,代表任意個數的引數,任意型別的引數。

  再來看call後面的&&:AspectJ可以把幾個條件組合起來,目前支援 &&,||,以及!這三個條件。這三個條件的意思不用我說了吧?和Java中的是一樣的。

  來看最後一個!within(TestAspectJ):前面的!表示不滿足某個條件。within是另外一種型別選擇方法,特別注意,這種型別和前面講到的joinpoint的那幾種類型不同。within的型別是資料型別,而joinpoint的型別更像是動態的,執行時的型別。

上例中的pointcut合起來就是:

    l 選擇那些呼叫println(而且不考慮println函式的引數是什麼)的Joinpoint。
    l 另外,呼叫者的型別不要是TestAspect的。

圖4展示了執行結果:


圖4  新pointcut執行結果

我在圖2所示的原始碼中,為Test類定義了一個public static void println()函式,所以圖4的執行結果就把這個println給匹配上了。

看完例子,我們來講點理論。
(2) 直接針對JoinPoint的選擇

pointcuts中最常用的選擇條件和Joinpoint的型別密切相關,比如圖5:


圖5  不同型別的JPoint對應的pointcuts查詢方法

以圖5為例,如果我們想選擇型別為methodexecution的JPoint,那麼pointcuts的寫法就得包括execution(XXX)來限定。

除了指定JPoint型別外,我們還要更進一步選擇目標函式。選擇的根據就是圖5中列出的什麼MethodSignature,ConstructorSignature,TypeSinature,FieldSignature等。名字聽起來陌生得很,其實就是指定JPoint對應的函式(包括建構函式),Static block的資訊。比如圖4中的那個println例子,首先它的JPoint型別是call,所以它的查詢條件是根據MethodSignature來表達。一個Method Signature的完整表示式為:

    @註解 訪問許可權 返回值的型別 包名.函式名(引數)
      @註解和訪問許可權(public/private/protect,以及static/final)屬於可選項。如果不設定它們,則預設都會選擇。以訪問許可權為例,如果沒有設定訪問許可權作為條件,那麼public,private,protect及static、final的函式都會進行搜尋。
      返回值型別就是普通的函式的返回值型別。如果不限定型別的話,就用*萬用字元表示
      包名.函式名用於查詢匹配的函式。可以使用萬用字元,包括*和..以及+號。其中*號用於匹配除.號之外的任意字元,而..則表示任意子package,+號表示子類。
         比如:
         java.*.Date:可以表示java.sql.Date,也可以表示java.util.Date
         Test*:可以表示TestBase,也可以表示TestDervied
         java..*:表示java任意子類
         java..*Model+:表示Java任意package中名字以Model結尾的子類,比如TabelModel,TreeModel
         等
      最後來看函式的引數。引數匹配比較簡單,主要是引數型別,比如:
         (int, char):表示引數只有兩個,並且第一個引數型別是int,第二個引數型別是char
         (String, ..):表示至少有一個引數。並且第一個引數型別是String,後面引數型別不限。在引數匹配中,
         ..代表任意引數個數和型別
         (Object ...):表示不定個數的引數,且型別都是Object,這裡的...不是萬用字元,而是Java中代表不定引數的意思

是不是很簡單呢?

    Constructorsignature和Method Signature類似,只不過建構函式沒有返回值,而且函式名必須叫new。比如:
    public *..TestDerived.new(..):
      public:選擇public訪問許可權
      *..代表任意包名
      TestDerived.new:代表TestDerived的建構函式
      (..):代表引數個數和型別都是任意
    再來看Field Signature和Type Signature,用它們的地方見圖5。下面直接上幾個例子:
    Field Signature標準格式:
    @註解 訪問許可權 型別 類名.成員變數名
      其中,@註解和訪問許可權是可選的
      型別:成員變數型別,*代表任意型別
      類名.成員變數名:成員變數名可以是*,代表任意成員變數
    比如,
    set(inttest..TestBase.base):表示設定TestBase.base變數時的JPoint
    Type Signature:直接上例子
    staticinitialization(test..TestBase):表示TestBase類的static block
    handler(NullPointerException):表示catch到NullPointerException的JPoint。注意,圖2的原始碼第23行截獲的其實是Exception,其真實型別是NullPointerException。但是由於JPointer的查詢匹配是靜態的,即編譯過程中進行的匹配,所以handler(NullPointerException)在執行時並不能真正被截獲。只有改成handler(Exception),或者把原始碼第23行改成NullPointerException才行。

以上例子,讀者都可以在aspectj-test例子中自己都試試。
(3) 間接針對JPoint的選擇

除了根據前面提到的Signature資訊來匹配JPoint外,AspectJ還提供其他一些選擇方法來選擇JPoint。比如某個類中的所有JPoint,每一個函式執行流程中所包含的JPoint。

特別強調,不論什麼選擇方法,最終都是為了找到目標的JPoint。

表2列出了一些常用的非JPoint選擇方法:

表2  其它常用選擇方法

關鍵詞

說明

示例

within(TypePattern)

TypePattern標示package或者類。TypePatter可以使用萬用字元

表示某個Package或者類中的所有JPoint。比如

within(Test):Test類中(包括內部類)所有JPoint。圖2所示的例子就是用這個方法。

withincode(Constructor Signature|Method Signature)

表示某個建構函式或其他函式執行過程中涉及到的JPoint

比如

withinCode(* TestDerived.testMethod(..))

表示testMethod涉及的JPoint

withinCode( *.Test.new(..))

表示Test建構函式涉及的JPoint

cflow(pointcuts)

cflow是call flow的意思

cflow的條件是一個pointcut

比如

cflow(call TestDerived.testMethod):表示呼叫TestDerived.testMethod函式時所包含的JPoint,包括testMethod的call這個JPoint本身

cflowbelow(pointcuts)

cflow是call flow的意思。

比如

cflowblow(call TestDerived.testMethod):表示呼叫TestDerived.testMethod函式時所包含的JPoint,包括testMethod的call這個JPoint本身

this(Type)

JPoint的this物件是Type型別。

(其實就是判斷Type是不是某種型別,即是否滿足instanceof Type的條件)

JPoint是程式碼段(不論是函式,異常處理,static block),從語法上說,它都屬於一個類。如果這個類的型別是Type標示的型別,則和它相關的JPoint將全部被選中。

圖2示例的testMethod是TestDerived類。所以

this(TestDerived)將會選中這個testMethod JPoint

target(Type)

JPoint的target物件是Type型別

和this相對的是target。不過target一般用在call的情況。call一個函式,這個函式可能定義在其他類。比如testMethod是TestDerived類定義的。那麼

target(TestDerived)就會搜尋到呼叫testMethod的地方。但是不包括testMethod的execution JPoint

args(TypeSignature)

用來對JPoint的引數進行條件搜尋的

比如args(int,..),表示第一個引數是int,後面引數個數和型別不限的JPoint。

上面這些東西,建議讀者:

    l 進入androidaopdemo/aspectj-test目錄。
    l 修改test/TestAspect.aj檔案。主要是其中的pointcuts:testAll()這一行。按照圖2中的解釋說明,隨便改改試試。
    l 執行./create-jar.sh,得到一個test.jar包,然後java -jar test.jar得到執行結果

注意:this()和target()匹配的時候不能使用萬用字元。

圖6給出了修改示例和輸出:


圖6  示例程式碼和輸出結果

注意,不是所有的AOP實現都支援本節所說的查詢條件。比如Spring就不支援withincode查詢條件。
3.2.3  advice和aspect介紹

恭喜,看到這個地方來,AspectJ的核心部分就掌握一大部分了。現在,我們知道如何通過pointcuts來選擇合適的JPoint。那麼,下一步工作就很明確了,選擇這些JPoint後,我們肯定是需要幹一些事情的。比如前面例子中的輸出都有before,after之類的。這其實JPoint在執行前,執行後,都執行了一些我們設定的程式碼。在AspectJ中,這段程式碼叫advice。簡單點說,advice就是一種Hook。

ASpectJ中有好幾個Hook,主要是根據JPoint執行時機的不同而不同,比如下面的:

    before():testAll(){
       System.out.println("before calling: " + thisJoinPoint);//列印這個JPoint的資訊
      System.out.println("      at:" + thisJoinPoint.getSourceLocation());//列印這個JPoint對應的原始碼位置
    }

testAll()是前面定義的pointcuts,而before()定義了在這個pointcuts選中的JPoint執行前我們要乾的事情。

表3列出了AspectJ所支援的Advice的型別:

表3  advice的型別

關鍵詞

說明

示例

before()

before advice

表示在JPoint執行之前,需要乾的事情

after()

after advice

表示JPoint自己執行完了後,需要乾的事情。

after():returning(返回值型別)

after():throwing(異常型別)

returning和throwing後面都可以指定具體的型別,如果不指定的話則匹配的時候不限定型別

假設JPoint是一個函式呼叫的話,那麼函式呼叫執行完有兩種方式退出,一個是正常的return,另外一個是拋異常。

注意,after()預設包括returning和throwing兩種情況

返回值型別 around()

before和around是指JPoint執行前或執行後備觸發,而around就替代了原JPoint

around是替代了原JPoint,如果要執行原JPoint的話,需要呼叫proceed

注意,after和before沒有返回值,但是around的目標是替代原JPoint的,所以它一般會有返回值,而且返回值的型別需要匹配被選中的JPoint。我們來看個例子,見圖7。


圖7  advice示例和結果

圖7中:

    l 第一個紅框是修改後的testMethod,在這個testMethod中,肯定會丟擲一個空指標異常。
    l 第二個紅框是我們配置的advice,除了before以外,還加了一個around。我們重點來看around,它的返回值是Object。雖然匹配的JPoint是testMethod,其定義的返回值是void。但是AspectJ考慮的很周到。在around裡,可以設定返回值型別為Object來表示返回任意型別的返回值。AspectJ在真正返回引數的時候,會自動進行轉換。比如,假設inttestMethod定義了int作為返回值型別,我們在around裡可以返回一個Integer,AspectJ會自動轉換成int作為返回值。
    l 再看around中的//proceed()這句話。這代表呼叫真正的JPoint函式,即testMethod。由於這裡我們遮蔽了proceed,所以testMethod真正的內容並未執行,故執行的時候空指標異常就不會丟擲來。也就是說,我們完全截獲了testMethod的執行,甚至可以任意修改它,讓它執行別的函式都沒有問題。。

注意:從技術上說,around是完全可以替代before和after的。圖7中第二個紅框還把after給註釋掉了。如果不註釋掉,編譯時候報錯,[error]circular advice precedence: can't determine precedence between two or morepieces of advice that apply to the same join point: method-execution(voidtest.Test$TestDerived.testMethod())(大家可以自己試試)。我猜測其中的原因是around和after衝突了。around本質上代表了目標JPoint,比如此處的testMethod。而after是testMethod之後執行。那麼這個testMethod到底是around還是原testMethod呢?真是傻傻分不清楚!

(我覺得再加一些限制條件給after是可以避免這個問題的,但是沒搞成功...)

advice講完了。現在回顧下3.2節從開始到現在我們學到了哪些內容:

    l AspectJ中各種型別的JoinPoint,JPoint是一個程式的關鍵執行點,也是我們關注的重點。
    l pointcuts:提供了一種方法來選擇目標JPoint。程式有很多JPoint,但是需要一種方法來讓我們選擇我們關注的JPoint。這個方法就是利用pointcuts來完成的。
    l 通過pointcuts選擇了目標JPoint後,我們總得乾點什麼吧?這就用上了advice。advice包括好幾種類型,一般情況下都夠我們用了。

上面這些東西都有點像函式定義,在Java中,這些東西都是要放到一個class裡的。在AspectJ中,也有類似的資料結構,叫aspect。

    public aspect 名字 {//aspect關鍵字和class的功能一樣,檔名以.aj結尾
     pointcuts定義...
     advice定義...
    }

你看,通過這種方式,定義一個aspect類,就把相關的JPoint和advice包含起來,是不是形成了一個“關注面”?比如:

    l 我們定義一個LogAspect,在LogAspect中,我們在關鍵JPoint上設定advice,這些advice就是列印日誌
    l 再定義一個SecurityCheckAspect,在這個Aspect中,我們在關鍵JPoint上設定advice,這些advice將檢查呼叫app是否有許可權。

通過這種方式,我們在原來的JPoint中,就不需要寫log列印的程式碼,也不需要寫許可權檢查的程式碼了。所有這些關注點都挪到對應的Aspectj檔案中來控制。恩,這就是AOP的精髓。

注意,讀者在把玩程式碼時候,一定會碰到AspectJ語法不熟悉的問題。所以請讀者記得隨時參考官網的文件。這裡有一個官方的語法大全:

http://www.eclipse.org/aspectj/doc/released/quick5.pdf 或者官方的另外一個文件也可以:

http://www.eclipse.org/aspectj/doc/released/progguide/semantics.html

 
3.2.4  引數傳遞和JPoint資訊
(1) 引數傳遞

到此,AspectJ最基本的東西其實講差不多了,但是在實際使用AspectJ的時候,你會發現前面的內容還欠缺一點,尤其是advice的地方:

l 前面介紹的advice都是沒有引數資訊的,而JPoint肯定是或多或少有引數的。而且advice既然是對JPoint的截獲或者hook也好,肯定需要利用傳入給JPoint的引數乾點什麼事情。比方所around advice,我可以對傳入的引數進行檢查,如果引數不合法,我就直接返回,根本就不需要呼叫proceed做處理。

往advice傳引數比較簡單,就是利用前面提到的this(),target(),args()等方法。另外,整個pointcuts和advice編寫的語法也有一些區別。具體方法如下:

  先在pointcuts定義時候指定引數型別和名字

    pointcut testAll(Test.TestDerived derived,int x):call(*Test.TestDerived.testMethod(..))
                 && target(derived)&& args(x)

 注意上述pointcuts的寫法,首先在testAll中定義引數型別和引數名。這一點和定義一個函式完全一樣

  接著看target和args。此處的target和args括號中用得是引數名。而引數名則是在前面pointcuts中定義好的。這屬於target和args的另外一種用法。

  注意,增加引數並不會影響pointcuts對JPoint的匹配,上面的pointcuts選擇和

pointcut testAll():call(*Test.TestDerived.testMethod(..)) && target(Test.TestDerived) &&args(int)是一樣的

只不過我們需要把引數傳入advice,才需要改造

接下來是修改advice:

    Object around(Test.TestDerived derived,int x):testAll(derived,x){
         System.out.println("     arg1=" + derived);
         System.out.println("     arg2=" + x);
          return proceed(derived,x); //注意,proceed就必須把所有引數傳進去。
    }

  advice的定義現在也和函式定義一樣,把引數型別和引數名傳進來。

  接著把引數名傳給pointcuts,此處是testAll。注意,advice必須和使用的pointcuts在引數型別和名字上保持一致。

  然後在advice的程式碼中,你就可以引用引數了,比如derived和x,都可以打印出來。

總結,引數傳遞其實並不複雜,關鍵是得記住語法:

    l pointcuts修改:像定義函式一樣定義pointcuts,然後在this,target或args中繫結引數名(注意,不再是引數型別,而是引數名)。
    l advice修改:也像定義函式一樣定義advice,然後在冒號後面的pointcuts中繫結引數名(注意是引數名)
    l 在advice的程式碼中使用引數名。

(2) JoinPoint資訊收集

我們前面示例中都打印出了JPoint的資訊,比如當前呼叫的是哪個函式,JPoint位於哪一行程式碼。這些都屬於JPoint的資訊。AspectJ為我們提供如下資訊:

    l thisJoinpoint物件:在advice程式碼中可直接使用。代表JPoint每次被觸發時的一些動態資訊,比如引數啊之類的、
    l thisJoinpointStatic物件:在advice程式碼中可直接使用,代表JPoint中那些不變的東西。比如這個JPoint的型別,JPoint所處的程式碼位置等。
    l thisEnclosingJoinPointStaticPart物件:在advice程式碼中可直接使用。也代表JPoint中不可變的部分,但是它包含的東西和JPoint的型別有關,比如對一個call型別JPoint而言,thisEnclosingJoinPointStaticPart代表包含呼叫這個JPoint的函式的資訊。對一個handler型別的JPoint而言,它代表包含這個try/catch的函式的資訊。

關於thisJoinpoint,建議大家直接檢視API文件,非常簡單。其地址位於http://www.eclipse.org/aspectj/doc/released/runtime-api/index.html。

 
四、使用AOP的例子

現在正式回到我們的AndroidAopDemo這個例子來。我們的目標是為AopDemoActivity的幾個Activity生命週期函式加上log,另外為checkPhoneState加上許可權檢查。一切都用AOP來集中控制。

前面提到說AspectJ需要編寫aj檔案,然後把AOP程式碼放到aj檔案中。但是在Android開發中,我建議不要使用aj檔案。因為aj檔案只有AspectJ編譯器才認識,而Android編譯器不認識這種檔案。所以當更新了aj檔案後,編譯器認為原始碼沒有發生變化,所以不會編譯它。

當然,這種問題在其他不認識aj檔案的java編譯環境中也存在。所以,AspectJ提供了一種基於註解的方法來把AOP實現到一個普通的Java檔案中。這樣我們就把AOP當做一個普通的Java檔案來編寫、編譯就好。
4.1  列印Log

馬上來看AopDemoActivity對應的DemoAspect.java檔案吧。先看輸出日誌第一版本:

[-->第一版本]

    package com.androidaop.demo;
    import android.util.Log;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.JoinPoint;
     
    @Aspect   //必須使用@AspectJ標註,這樣class DemoAspect就等同於 aspect DemoAspect了
    public class DemoAspect {
        staticfinal String TAG = "DemoAspect";
    /*
    @Pointcut:pointcut也變成了一個註解,這個註解是針對一個函式的,比如此處的logForActivity()
    其實它代表了這個pointcut的名字。如果是帶引數的pointcut,則把引數型別和名字放到
    代表pointcut名字的logForActivity中,然後在@Pointcut註解中使用引數名。
    基本和以前一樣,只是寫起來比較奇特一點。後面我們會介紹帶引數的例子
    */
    @Pointcut("execution(* com.androidaop.demo.AopDemoActivity.onCreate(..)) ||"
            +"execution(* com.androidaop.demo.AopDemoActivity.onStart(..))")
    public void logForActivity(){};  //注意,這個函式必須要有實現,否則Java編譯器會報錯
     
    /*
    @Before:這就是Before的advice,對於after,after -returning,和after-throwing。對於的註解格式為
    @After,@AfterReturning,@AfterThrowing。Before後面跟的是pointcut名字,然後其程式碼塊由一個函式來實現。比如此處的log。
    */
        @Before("logForActivity()")
        public void log(JoinPoint joinPoint){
           //對於使用Annotation的AspectJ而言,JoinPoint就不能直接在程式碼裡得到多了,而需要通過
          //引數傳遞進來。
           Log.e(TAG, joinPoint.toShortString());
        }
    }

提示:如果開發者已經切到AndroidStudio的話,AspectJ註解是可以被識別並能自動補齊。

上面的例子僅僅是列出了onCreate和onStart兩個函式的日誌,如果想在所有的onXXX這樣的函式里加上log,該怎麼改呢?

    @Pointcut("execution(* *..AopDemoActivity.on*(..))")
    public void logForActivity(){};

圖8給出這個例子的執行結果:


圖8  AopDemoActivity執行結果

4.2  檢查許可權
4.2.1  使用註解

檢查許可權這個功能的實現也可以採用剛才列印log那樣,但是這樣就沒有太多意思了。我們玩點高階的。不過這個高階的玩法也是來源於現實需求:

    l 許可權檢查一般是針對API的,比如呼叫者是否有許可權呼叫某個函式。
    l API往往是通過SDK釋出的。一般而言,我們會在這個函式的註釋裡說明需要呼叫者宣告哪些許可權。
    l 然後我們在API檢查呼叫者是不是申明瞭文件中列出的許可權。

如果我有10個API,10個不同的許可權,那麼在10個函式的註釋裡都要寫,太麻煩了。怎麼辦?這個時候我想到了註解。註解的本質是原始碼的描述。許可權宣告,從語義上來說,其實是屬於API定義的一部分,二者是一個統一體,而不是分離的。

Java提供了一些預設的註解,不過此處我們要使用自己定義的註解:

    package com.androidaop.demo;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
     
    //第一個@Target表示這個註解只能給函式使用
    //第二個@Retention表示註解內容需要包含的Class位元組碼裡,屬於執行時需要的。
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SecurityCheckAnnotation {//@interface用於定義一個註解。
        publicString declaredPermission();  //declarePermssion是一個函式,其實代表了註解裡的引數
    }
    怎麼使用註解呢?接著看程式碼:
    //為checkPhoneState使用SecurityCheckAnnotation註解,並指明呼叫該函式的人需要宣告的許可權
       @SecurityCheckAnnotation(declaredPermission="android.permission.READ_PHONE_STATE")
       private void checkPhoneState(){
            //如果不使用AOP,就得自己來檢查許可權
           if(checkPermission("android.permission.READ_PHONE_STATE") ==false){
               Log.e(TAG,"have no permission to read phone state");
               return;
            }
           Log.e(TAG,"Read Phone State succeed");
           return;
        }

4.2.2  檢查許可權

下面,我們來看看如何在AspectJ中,充分利用這注解資訊來幫助我們檢查許可權。

    /*
    來看這個Pointcut,首先,它在選擇Jpoint的時候,把@SecurityCheckAnnotation使用上了,這表明所有那些public的,並且攜帶有這個註解的API都是目標JPoint
    接著,由於我們希望在函式中獲取註解的資訊,所有這裡的poincut函式有一個引數,引數型別是
    SecurityCheckAnnotation,引數名為ann
    這個引數我們需要在後面的advice裡用上,所以pointcut還使用了@annotation(ann)這種方法來告訴
    AspectJ,這個ann是一個註解
    */
      @Pointcut("execution(@SecurityCheckAnnotation public * *..*.*(..)) && @annotation(ann)")
      publicvoid checkPermssion(SecurityCheckAnnotationann){};
     
    /*
    接下來是advice,advice的真正功能由check函式來實現,這個check函式第二個引數就是我們想要
    的註解。在實際執行過程中,AspectJ會把這個資訊從JPoint中提出出來並傳遞給check函式。
    */
       @Before("checkPermssion(securityCheckAnnotation)")
        publicvoid check(JoinPoint joinPoint,SecurityCheckAnnotationsecurityCheckAnnotation){
            //從註解資訊中獲取宣告的許可權。
           String neededPermission = securityCheckAnnotation.declaredPermission();
           Log.e(TAG, joinPoint.toShortString());
           Log.e(TAG, "\tneeded permission is " + neededPermission);
           return;
        }

如此這般,我們在API原始碼中使用的註解資訊,現在就可以在AspectJ中使用了。這樣,我們在原始碼中定義註釋,然後利用AspectJ來檢查。圖9展示了執行的結果


圖9  許可權檢查的例子

4.2.3  和其他模組互動

事情這樣就完了?很明顯沒有。為什麼?剛才許可權檢查只是簡單得打出了日誌,但是並沒有真正去做許可權檢查。如何處理?這就涉及到AOP如何與一個程式中其他模組互動的問題了。初看起來容易,其實有難度。

比如,DemoAspect雖然是一個類,但是沒有建構函式。而且,我們也沒有在程式碼中主動去構造它。根據AsepctJ的說明,DemoAspect不需要我們自己去構造,AspectJ在編譯的時候會把建構函式給你自動加上。具體在程式什麼位置加上,其實是有規律的,但是我們並不知道,也不要去知道。

這樣的話,DemoAspect豈不是除了打打log就沒什麼作用了?非也!以此例的許可權檢查為例,我們需要:

    l 把真正進行許可權檢查的地方封裝到一個模組裡,比如SecurityCheck中。
    l SecurityCheck往往在一個程式中只會有一個例項。所以可以為它提供一個函式,比如getInstance以獲取SecurityCheck例項物件。
    l 我們就可以在DemoAspect中獲取這個物件,然後呼叫它的check函式,把最終的工作由SecurityCheck來檢查了。

恩,這其實是Aspect的真正作用,它負責收集Jpoint,設定advice。一些簡單的功能可在Aspect中來完成,而一些複雜的功能,則只是有Aspect來統一收集資訊,並交給專業模組來處理。

最終程式碼:


      @Before("checkPermssion(securityCheckAnnotation)")
        publicvoid check(JoinPoint joinPoint,SecurityCheckAnnotation securityCheckAnnotation){
           String neededPermission = securityCheckAnnotation.declaredPermission();
           Log.e(TAG, "\tneeded permission is " + neededPermission);
            SecurityCheckManager manager =SecurityCheckManager.getInstanc();
           if(manager.checkPermission(neededPermission) == false){
               throw new SecurityException("Need to declare permission:" + neededPermission);
            }
           return;
        }


圖10所示為最終的執行結果。


圖10  執行真正的許可權檢查

注意,

    1 所有程式碼都放到https://code.csdn.net/Innost/androidaopdemo上了....
    2  編譯:請在ubuntu下使用gradle assemble。編譯結果放在out/apps/目錄下。關於gradle的使用,請大家參考我的另外一篇重磅文章http://blog.csdn.net/innost/article/details/48228651

五、其他、總結和參考文獻

最後我們來講講其他一些內容。首先是AspectJ的編譯。
5.1  AspectJ編譯

    l AspectJ比較強大,除了支援對source檔案(即aj檔案、或@AspectJ註解的Java檔案,或普通java檔案)直接進行編譯外,
    l 還能對Java位元組碼(即對class檔案)進行處理。有感興趣的同學可以對aspectj-test小例子的class檔案進行反編譯,你會發現AspectJ無非是在被選中的JPoint的地方加一些hook函式。當然Before就是在呼叫JPoint之前加,After就是在JPoint返回之前加。
    l 更高階的做法是當class檔案被載入到虛擬機器後,由虛擬機器根據AOP的規則進行hook。

在Android裡邊,我們用得是第二種方法,即對class檔案進行處理。來看看程式碼:

    //AndroidAopDemo.build.gradle
    //此處是編譯一個App,所以使用的applicationVariants變數,否則使用libraryVariants變數
    //這是由Android外掛引入的。所以,需要import com.android.build.gradle.AppPlugin;
    android.applicationVariants.all { variant ->
       /*
         這段程式碼之意是:
         當app編譯個每個variant之後,在javaCompile任務的最後新增一個action。此action
         呼叫ajc函式,對上一步生成的class檔案進行aspectj處理。
       */
        AppPluginplugin = project.plugins.getPlugin(AppPlugin)
       JavaCompile javaCompile = variant.javaCompile
       javaCompile.doLast{
           String bootclasspath =plugin.project.android.bootClasspath.join(File.pathSeparator)
           //ajc是一個函式,位於utils.gradle中
            ajc(bootclasspath,javaCompile)
        }
    }

ajc函式其實和我們手動試玩aspectj-test目標一樣,只是我們沒有直接呼叫ajc命令,而是利用AspectJ提供的API做了和ajc命令一樣的事情。

    import org.aspectj.bridge.IMessage
    import org.aspectj.bridge.MessageHandler
    import org.aspectj.tools.ajc.Main
     
    def ajc(String androidbootClassFiles,JavaCompile javaCompile){
       String[] args = ["-showWeaveInfo",
                         "-1.8", //1.8是為了相容java 8。請根據自己java的版本合理設定它
                         "-inpath",javaCompile.destinationDir.toString(),
                         "-aspectpath",javaCompile.classpath.asPath,
                         "-d",javaCompile.destinationDir.toString(),
                         "-classpath",javaCompile.classpath.asPath,
                        "-bootclasspath", androidbootClassFiles]
        MessageHandlerhandler = new MessageHandler(true);
        new Main().run(args,handler)
     
        deflog = project.logger
        for(IMessage message : handler.getMessages(null, true)) {
         switch (message.getKind()) {
           case IMessage.ABORT:
           case IMessage.ERROR:
           case IMessage.FAIL:
             log.error message.message, message.thrown
             throw message.thrown
             break;
           case IMessage.WARNING:
           case IMessage.INFO:
             log.info message.message, message.thrown
             break;
           case IMessage.DEBUG:
             log.debug message.message, message.thrown
             break;
          }
        }
      }

主要利用了https://eclipse.org/aspectj/doc/released/devguide/ajc-ref.html中TheAspectJ compiler API一節的內容。由於程式碼已經在csdn git上,大家下載過來直接用即可。
5.2  總結

除了hook之外,AspectJ還可以為目標類新增變數。另外,AspectJ也有抽象,繼承等各種更高階的玩法。根據本文前面的介紹,這些高階玩法一定要靠需求來驅動。AspectJ肯定對原程式是有影響的,如若貿然使用高階用法,則可能帶來一些未知的後果。關於這些內容,讀者根據情況自行閱讀文後所列的參考文獻。

最後再來看一個圖。


圖11 未使用AOP的情況

圖11中,左邊是一個程式的三個基於OOP而劃分的模組(也就是concern)。安全、業務邏輯、交易管理。這三個模組在設計圖上一定是互相獨立,互不干擾的。

但是在右圖實現的時候,這三個模組就攪在一起了。這和我們在AndroidAopDemo中檢查許可權的例子中完全一樣。在業務邏輯的時候,需要顯示呼叫安全檢查模組。

自從有了AOP,我們就可以去掉業務邏輯中顯示呼叫安全檢查的內容,使得程式碼歸於乾淨,各個模組又能各司其職。而這之中千絲萬縷的聯絡,都由AOP來連線和管理,豈不美哉?!
5.3  參考文獻

[1]  Manning.AspectJ.in.Action第二版

看書還是要挑簡單易懂的,AOP概念並不複雜,而AspectJ也有很多書,但是真正寫得通俗易懂的就是這本,雖然它本意是介紹Spring中的AOP,但對AspectJ的解釋真得是非常到位,而且還有對@AspectJ註解的介紹。本文除第一個圖外,其他參考用圖全是來自於此書。

[2]  http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/

Android中如何使用AspectJ,最重要的是它教會我們怎麼使用aspectj編譯工具API。
---------------------
作者:阿拉神農
來源:CSDN
原文:https://blog.csdn.net/innost/article/details/49387395
版權宣告:本文為博主原創文章,轉載請附上博文連結!

相關推薦

深入理解AndroidAOP

格式更加精美的PDF版請到:http://vdisk.weibo.com/s/z68f8l0xTgCLK 下載 一、閒談AOP 大家都知道OOP,即ObjectOriented Programming,面向物件程式設計。而本文要介紹的是AOP。AOP是Aspect Or

深入理解AndroidView的繪製流程

本篇文章會從原始碼(基於Android 6.0)角度分析Android中View的繪製流程,側重於對整體流程的分析,對一些難以理解的點加以重點闡述,目的是把View繪製的整個流程把握好,而對於特定實現細節則可以日後再對相應原始碼進行研讀。 在進行實際的分析之前,我們先來看下面

深入理解AndroidXposed詳解

一、背景Xposed,大名鼎鼎得Xposed,是Android平臺上最負盛名的一個框架。在這個框架下,我們可以載入很多外掛App,這些外掛App可以直接或間接操縱系統層面的東西,比如操縱一些本來只對系統廠商才open的功能(實際上是因為Android系統很多API是不公開的,

深入理解AndroidJava虛擬機器Dalvik

一、背景這個選題很大,但並不是一開始就有這麼高大上的追求。最初之時,只是源於對Xposed的好奇。Xposed幾乎是定製ROM的神器軟體技術架構或者說方法了。它到底是怎麼實現呢?我本意就是想搞明白Xposed的實現原理,但隨著程式碼研究的深入,我發現如果不瞭解虛擬機器的實現,

深入理解AndroidJava Security第一部分

深入理解Android之Java Security(第一部分)從事Android工作4年以來,只有前1年不到的時間是用C++在開發東西(主要是開發DLNA元件,目前我已將它們全部開源,參考http://

深入理解AndroidJava Security第二部分(Final)

深入理解Android之Java Security(第二部分,最後)程式碼路徑:Security.java:libcore/lunl/src/main/java/java/security/TrustedCertificateStore.java:libcore /crypt

Android開發深入理解Android 7.0系統許可權更改相關文件

摘要: Android 6.0之後的版本增加了執行時許可權,應用程式在執行每個需要系統許可權的功能時,需要新增許可權請求程式碼(預設許可權禁止),否則應用程式無法響應;Android 7.0在Android 6.0的基礎上,對系統許可權進一步更改,這次的許可權更改包括三個方

深入理解Android L新特性 頁面內容&共享元素過渡動畫

今天我們來聊聊Android L(5.0)引入的新特性:頁面內容過渡動畫和頁面共享動畫,這兩個特性都是基於我們前面已經說過的Transition動畫,如果你對Transition動畫不太屬性,請先看我前面的兩篇文章。在5.0之前,我們從一個Activity A進入到另外一個A

深入理解Android Telephony RILD機制分析

RILD負責modem和RILJ端的通訊,資訊分兩種:unsolicited和solicited,前者是由modem主動上報的,諸如時區更新、通話狀態、網路狀態等訊息,後者是RILJ端發請求並需要modem反饋的資訊。RILJ與RILD之間的通訊由主執行緒s_t

深入理解javascript設計模式

rip 是我 解決問題 不想 接受 button move center 常識 設計模式 設計模式是命名、抽象和識別對可重用的面向對象設計實用的的通用設計結構。設計模式確定類和他們的實體、他們的角色和協作、還有他們的責任分配。 每個設計模式都聚焦於一個面向對象的設計難題

深入理解javascript原型

undefine tor ace 對象實例 高級 code turn 三方 true 理解原型 原型是一個對象。其它對象能夠通過它實現屬性繼承。不論什麽一個對象都能夠成為繼承,全部對象在默認的情況下都有一個原型。由於原型本身也是對象,所以每一個原型自身又有一個原型

深入理解Java線程池

我們 先進先出 tor cor null 討論 等於 影響 log 重要連接:http://www.cnblogs.com/dolphin0520/p/3958019.html 出處:http://www.cnblogs.com/dolphin0520/     本文歸作者

深入理解JVMJVM內存區域與內存分配

錯誤 銷毀 構造方法 初學 不存在 data 空閑 table fin 深入理解JVM之JVM內存區域與內存分配   在學習jvm的內存分配的時候,看到的這篇博客,該博客對jvm的內存分配總結的很好,同時也利用jvm的內存模型解釋了java程序中有關參數傳遞的問題。  

深入理解SpringCloudEureka註冊過程分析

.net then media inject seq tar view inf cas   eureka是一種去中心化的服務治理應用,其顯著特點是既可以作為服務端又可以作為服務向自己配置的地址進行註冊。那麽這篇文章就來探討一下eureka的註冊流程。 一、Eureka的服

深入理解系列 float

子元素 out oat 理解 oom 20px 布局 空格 ble float的設計初衷: 僅僅是為了實現文字環繞效果 float的感性認知: 包裹性: 收縮:元素應用了float後,寬度收縮,緊緊地包裹住內容(即元素的寬度收縮到元素內的內容的寬度大小 堅挺:原來沒有高

深入理解python二——python列表和元組

n) 數據 兩種 性能 執行 效率 動態 單元 這一 從一開始學習python的時候,很多人就聽到的是元組和列表差不多,區別就是元組不可以改變,列表可以改變。 從數據結構來說,這兩者都應當屬於數組,元組屬於靜態的數組,而列表屬於動態數組。稍後再內存的分配上也會體現這一點。對

深入理解分散式抉擇分散式鎖

  引言 為什麼寫這篇文章? 目前網上大部分的基於zookeeper,和redis的分散式鎖的文章都不夠全面。要麼就是特意避開叢集的情況,要麼就是考慮不全,讀者看著還是一臉迷茫。坦白說,這種老題材,很難寫出新創意,博主內心戰戰兢兢,如履薄冰,文中有什麼不嚴謹之處,歡迎批評

深入理解 Android Https

前言 大家都知道https相比http增加的是安全性。 怎麼增加安全性呢? 就是加密和解密步驟。 下面來詳細談談對https的理解和在Android中的使用. 兩種加密 加密方式分兩種,對稱加密和非對稱加密。這兩種方式都有自己的優劣勢, https中這兩種方式都採用了。 我們約定S是服務端,C是客戶端,

深入理解JavaScriptthis全面解析

    在之前的章節裡我們知道,this  是在函式執行時繫結的,它只與函式在哪被呼叫有關係   1.1  呼叫位置    在理解  this  的繫結之前,我們先理解  this 

深入理解JavaScriptthis的四種繫結

  之前對this的四種繫結不太理解,好在瀏覽了https://www.cnblogs.com/xiaohuochai/p/5735901.html這篇博文,才得以清晰思路,接下來我再次總結this的四種繫結機制。 1  this的四種繫結機制   在JavaS