史上最簡單易用的Android權限管理框架

前言

感覺已經很久沒有寫博客了,5月份之後一直在學習kotlin,邊學邊用,算是入門了吧;然後又突然對熱更新技術很有興趣,又去學習了一段時間,無奈畢竟我是凡人一個,只能膜拜那些大神啊;最近又在隨大流,開始好奇AI領域,國內資料太少,很多還收費,好不容易找到個國外免費的,可惜這英文能力太弱,實在是累極啊。

就當我正在渾渾噩噩之時,突和朋友討論起的Android權限申請的問題,最後我們得出這樣一個結論:目前各個比較流行的權限管理開源庫都有一個比較麻煩的使用步驟。

雖說有些開源庫依靠註解和APT技術來簡化了權限申請的流程,但我感覺還是不夠簡單,比如github上star比較多的一個開源庫

PermissionsDispatcher

star足有8000往上之多,可見Android權限申請的重要性與使用的頻繁性。可就這麼個要頻繁使用的東西,很多Android程序員都被其複雜的流程所困擾,爲了申請到權限,不得不在自己的代碼里加上一些看似毫無關聯的代碼邏輯,比如,Activity或Fragment必需重寫的一個方法

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

即使你使用了一些開源庫,比如PermissionsDispatcher,你也要重載這個方法,並且在裏面加上類似xxx.onRequestPermissionsResult這樣的代碼,告訴你引用的開源庫,某些權限回調了,你引用的開源庫才能繼續執行回調。

那麼,就沒有什麼辦法可以讓我們只是用註解,無需插入其他邏輯代碼即可實現權限申請了嗎?當然有,答案是肯定的,不然我今天也不會來寫這篇文章了(機智如我)?。並且我可以負責任的告訴大家,方法肯定不止我這一種,大家有興趣的話,可以順着我今天這篇文章的思路,去嘗試一下其他方式,說不定結果會出乎你的意料哦。

先給出項目的地址

SimplePermission

一、原理簡介

1.1 技術掃盲

今天給大家帶來的這個開源庫呢,也算是我最近研究熱更新技術得到啓發而實現的,原理和技術其實都非常簡單,只涉及到兩個方面:

  1. gradle transform api

  2. javassist

使用這兩個方面的技術說到底,就是爲了獲取到一些指定的class文件,並且在class文件被打包成dex文件之前,往class文件裏插入代碼。

關於javassist和gradle transform api的技術主要參考資料如下:

Java學習之javassist

自定義Gradle插件

Plugin Transform Javassist操作Class文件

Android中使用Javassist

尤其是第四個文章鏈接末尾有其demo的github地址,很多javassist的用法都可以在demo裏找到,大家需要慢慢研究。比如,我最開始想實現這個開源庫的時候,就遇到了無法插入涉及android api的代碼,當時感覺已經沒有希望了,隔了一兩天,我還是不想放棄,再次一陣google之後,總算找到了解決辦法,所以真的非常感謝這些大神們啊。

上面的參考不是必須的,大家可以先開始嘗試着跟着第二個鏈接裏內容去寫一個自己的gradle plugin插件,在實現的過程中遇到了什麼問題,在回來參考其他鏈接裏的知識,循序漸進,肯定能有所增益的。

前面說了,目前大多數權限庫都有一個不太順手的地方(不是缺點哈),就是要程序員手動添加一些代碼,感覺用起來不是那麼方便,那麼,如果有什麼辦法可以自動往我們需要權限的類或方法里加上申請權限的代碼,豈不美哉?剛纔已經說過了javassist和gradle transform api技術的作用,那麼我們就可以利用他們來完成往類或方法裏插入權限申請的代碼啦。

1.2問題&解決辦法

現在我們僅僅是知道了可以在編譯時修改class文件,往某些特定的class文件裏插入代碼,那麼接下來我們就至少要考慮以下幾個問題:

  1. 如何找出哪些class文件需要插入代碼?

  2. 如何找出哪些方法需要申請權限?需要申請哪些權限?

  3. 需要申請權限的方法申請權限成功後需要如何處理?

  4. permission權限申請庫採用的是回調的形式來通知權限申請結果,我們可否在該class文件任何地方插入權限申請的代碼?

對於第1個和第2個問題,我們可以用註解很好的去解決,編譯時註解即可,大家不要在意我的simplepermission_ano裏的註解是運行時的,習慣了,沒改過來?。

至於第3個問題,這個是需要琢磨一下的,因爲大部分情況下,我們的某個方法要申請權限總是意味着我們的方法裏面的一些操作需要依賴於某些權限,並且有些是必須要先得到權限後纔可以執行的操作,那麼我們就不能單純的在這方法裏插入申請權限的代碼,還要干預這個方法的邏輯,但是我們的權限申請流程並不是阻塞式的,而是異步回調式的,這樣我們就沒辦法在這個方法內部去幹預其邏輯了。那麼是否就無計可施了呢?其實不然,我們可以把權限申請的代碼插入到該方法的最前部分,根據方法上的註解裏的一個值來判斷該方法是否需要等到權限回調成功了在執行後續邏輯,這樣我們就可以在權限申請回調成功的地方插入調用剛纔申請權限的那個方法的代碼,重新執行其內部邏輯(因爲我們重新調用了那個方法)不就可以了嗎?不過這又牽涉到另一個問題,如果該方法是一個無參無返回值的方法,那就非常簡單啦,但是如果該方法有參數該怎麼辦?我們在權限申請回調成功的地方再次調用該方法就無法繼續寫啦,因爲我們沒有參數啊!於是我又想到了一個辦法,在申請權限的類里加入一個全局變量(不帶泛型的Map),當某個申請權限的方法被調用時,這個方法其實已經被我們插入了判斷應用程序是否擁有其註解裏包含的權限的代碼了,如果應用程序已經擁有了那些權限,那我們便不再幹預其後續邏輯;如果應用程序還未擁有那些權限,則用一個不帶泛型的List把該方法的參數挨個存儲起來,然後把這個List放到全局的那個不帶泛型的Map裏,當然,放到map裏時的那個key,就是方法的註解裏的一個唯一值。然後,和剛纔的邏輯一樣,在權限申請回調成功的地方,根據回調裏的那個唯一值作爲key,從map裏取出次方法的參數,插入再次調用此方法的代碼,問題不就解決了嗎?。不過,對於帶有返回值的方法,目前我也沒有想到有什麼好的方法去解決,但是我相信,一定是可以解決的,我會好好琢磨琢磨的。

金無赤足,人無完人,對於最後一個問題,由於本人能力有限和javassist的一些限制,目前本庫無法在內部類裏使用,這是一個非常大的遺憾,希望隨着時間的推移,javassist能夠支持插入內部類吧。

二、沒有代碼的代碼實現

其實代碼實現部分真沒什麼好說的,項目我都放到了github,大家可以先直接去看本開源庫的README,先了解permission權限申請庫的基本用法,在去看simplepermissionplugin的源碼,就會很快明白是怎麼實現的啦。至於simplepermission_ano庫,你可以直接忽略,裏面就兩個註解而已。simplepermissionplugin源碼我註釋的非常清楚,裏面所有涉及到的技術,前面給的那幾篇參考文章裏都只會多不會少。

在這裏呢,我也不會貼很多代碼啦,我只是想通過一點代碼對比,讓大家看看,正常使用permission權限庫和用gradle插件&註解方式使用permission權限庫的區別。

正常情況下使用permission權限庫的代碼如下

public class MainActivity extends AppCompatActivity implements View.OnClickListener
	private TextView mTextMessage;
	protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextMessage = (TextView) findViewById(R.id.message);

        mTextMessage.setOnClickListener(this);
	}
	 /**
	  * 此方法需要一個 Manifest.permission.READ_CONTACTS權限
 	  *
 	  * @param text
 	  */
    private void setText(final String text) {
        PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(10010, this,
        new String[]{Manifest.permission.READ_CONTACTS}, new PermissionsRequestCallback() {
            @Override
            public void onGranted(int requestCode, String permission) {
                
            }

            @Override
            public void onDenied(int requestCode, String permission) {

            }

            @Override
            public void onDeniedForever(int requestCode, String permission) {

            }

            @Override
            public void onFailure(int requestCode, String[] deniedPermissions) {

            }

            @Override
            public void onSuccess(int requestCode) {
                mTextMessage.setText(text);
            }
        });
    }

    @Override
    public void onClick(View v) {
        setText("哈哈哈哈哈哈");
    }
    
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        PermissionsManager.getInstance().notifyPermissionsChange(permissions,grantResults);
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

用gradle插件&註解方式使用permission權限庫的代碼如下

@PermissionNotify
public class MainActivity extends AppCompatActivity implements View.OnClickListener
	private TextView mTextMessage;
	protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextMessage = (TextView) findViewById(R.id.message);

        mTextMessage.setOnClickListener(this);
	}
	
	 /**
     * 此方法需要一個 Manifest.permission.READ_CONTACTS權限
     *
     * @param text
     */
    @PermissionRequest(
            requestCode = 10010,
            requestPermissions =
             {Manifest.permission.READ_CONTACTS}
            , needReCall = true
    )
    private void setText(final String text) {
        mTextMessage.setText(text);
    }

    @Override
    public void onClick(View v) {
        setText("哈哈哈哈哈哈");
    }
}

最後在app module的build/intermediates/transforms/SimplePermissionTransform/debug(或release)/24(可能是其他數字)/包名 文件夾下可以看到加上了註解的類的實際編譯結果,類似如下

@PermissionNotify
public class MainActivity extends AppCompatActivity implements OnClickListener, PermissionsRequestCallback {
    private TextView mTextMessage;
   	 private final Map requestPermissionMethodParams = new HashMap();

    public MainActivity() {
    }

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(2131361818);
       
        this.mTextMessage.setOnClickListener(this);
    }

    @PermissionRequest(
        requestCode = 10010,
        requestPermissions = { Manifest.permission.READ_CONTACTS },
        needReCall = true
    )
    private void setText(final String text) {
        String[] var2 = new String[]{ "android.permission.READ_CONTACTS"};
        boolean var3 = PermissionsManager.getInstance().hasAllPermissions(this, var2);
        if (!var3) {
            ArrayList var4 = new ArrayList();
            var4.add(text);
            this.requestPermissionMethodParams.put(10010, var4);
            PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(10010, this, var2, this);
        } else {
            this.mTextMessage.setText(text);
        }
    }

    public void onClick(View v) {
        this.setText("哈哈哈哈哈哈");
    }

    public void onGranted(int var1, String var2) {
    }

    public void onDenied(int var1, String var2) {
    }

    public void onDeniedForever(int var1, String var2) {
    }

    public void onFailure(int var1, String[] var2) {
    }

    public void onSuccess(int var1) {
        Object var2 = this.requestPermissionMethodParams.get(var1);
        if (var1 == 10010) {
            this.setText((String)((List)var2).get(0));
        }
    }

    public void onRequestPermissionsResult(int var1, String[] var2, int[] var3) {
        PermissionsManager.getInstance().notifyPermissionsChange(var2, var3);
        super.onRequestPermissionsResult(var1, var2, var3);
    }
}

我想,通過這個對比,大家肯定能看出來區別在哪裏吧。簡化了很多邏輯代碼,當然也增加了一些代碼,不過,兩種方式孰優孰劣,大家一眼便知。

三、結語

本庫看上去用起來似乎很簡單,但是具體的細節實現上卻並不是那麼容易,由於個人能力和一些技術的限制,我還面臨很多問題。比如本開源庫只支持在Activity或Fragment裏使用;又比如這個開源庫不能放到內部類裏等等等等,具體的可查看本開源庫README的第4條,這些都是目前我無法解決的問題,希望能有大神提點一二,感激不盡。

這個庫其實更深一點的意義,就是在這裏拋磚引玉,希望大家能夠挖掘出更好的Android權限管理開源庫,造福千千萬萬的Android程序員啊。爲什麼這麼說呢?其實很簡單,這個庫權限申請的部分是simplepermission裏的代碼實現的,我所開發的插件就必須受到simplepermission這個庫的一些限制,但是思路卻可以不受限制,說不定大家手裏有非常好的開源圈子看不到的Adroid權限申請庫,如果大家把手裏的Adroid權限申請庫順着我這個思路也寫個gradle插件,讓Android權限申請的使用更加的簡便,功能更加豐富,豈不是造福廣大的Android程序猿啊,想想還有點小激動呢_

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