提及這個ContextImpl.可能有些同窗不太熟悉,但提及Context,我想都認識它吧,上下文,也能夠說是表明一種所在的場景,因爲Context只是一個抽象類,而抽象類一定是有一個具體的實現類的,另外還有ContextThemeWrapper和ContextWrapper,不過這些都是Context的子類而已,他們是以裝飾模式而存在的一種關係,簡單說下裝飾模式,裝飾模式是經常使用的設計模式之一,通常狀況下若是須要動態地給一個對象添加一些額外的職責但又不想增長子類,那麼就能夠用到裝飾模式了,若是單純增長功能來講,Decorator模式相比生成子類更爲靈活,該模式以對客 戶端透明的方式擴展對象的功能,下面舉一個簡單的例子說明一下,首先一我的,有吃飯的功能接口android
代碼以下:數據庫
Component設計模式
public interface Person {
void eat();
}
複製代碼
ConcreteComponentbash
public class Man implements Person {
public void eat() {
System.out.println("男人在吃飯");
}
}
複製代碼
Decorator(這個類是關鍵,實現了接口而且有真實對象的引用)app
public abstract class Decorator implements Person {
protected Person person;
public Decorator(Person person){
this.person=person;
}
public void eat() {
person.eat();
}
}
複製代碼
下面的就是具體的添加額外功能的了具體子類,好比有些人吃飯前先洗手或者吃完飯後洗碗之類,固然具體什麼事情根據業務決定框架
public class ManDecoratorA extends Decorator {
public ManDecoratorA(Person person) {
super(person);
}
public void eat() {
wash();
super.eat();
}
private void wash() {
System.out.println("飯前先洗洗手");
}
}
public class ManDecoratorB extends Decorator {
public ManDecoratorB(Person person) {
super(person);
}
public void eat() {
super.eat();
washDishes();
}
private void washDishes(){
System.out.println("吃完飯後洗碗");
}
}
複製代碼
測試的結果爲:ide
public static void main(String[] args) {
Person person=new Man();
ManDecoratorA md1=new ManDecoratorA(person);
ManDecoratorB md2=new ManDecoratorB(person);
md1.eat();
System.out.println("===============");
md2.eat();
}
複製代碼
運行結果爲:oop
能夠看到在吃飯前和吃飯後作了一些事情,這就達到了不增長子類而又能夠添加一些額外功能的做用測試
回到ContextImpl和Context,其實也是同樣的,這個ContextImpl至關於代碼中的Man,而Context至關於Person,只是一個接口,一個抽象類,但本質都是同樣,而Decorator至關於ContextWrapper,那麼具體的抽象實現類爲何呢,在Android中Activity和Service就是相似代碼中的ManDecoratorA和ManDecoratorB了,只不過Activity有界面,天然就有Theme主題,所以對應的是ContextThemeWrapper,如今已經對裝飾模式理解的更深了吧。ui
如今回到ContextImpl自己來,ContextImpl做爲Context的抽象類,實現了全部的方法,咱們常見的getResources(),getAssets(),getApplication()等等的具體實現都是在ContextImpl的,下面是具體的一些代碼
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
@Override
public Looper getMainLooper() {
return mMainThread.getLooper();
}
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
複製代碼
能夠看到咱們日常開發所調用的方法的實現都是在這裏完成,做爲一名android開發者,咱們應該多去研究一些源碼之類的,這樣在出問題的時候能夠根據源碼找出問題的具體所在,ContextImpl在主線程ActivityThread經過傳入主線程對象建立了一個系統的ContextImpl,下面是代碼:
public ContextImpl getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
mSystemContext = ContextImpl.createSystemContext(this);
}
return mSystemContext;
}
}
複製代碼
這個是在ActivityThread裏面完成的,ActivityThread是Android應用程序的主線程環境,關於對ActivityThread的分析,能夠看個人另外一篇文章 關於Android主線程(ActivityThread)源代碼分析以及一些特殊問題的很是規方法
你們有空能夠去看看,實際上Activity和Service中的Context也是經過ContextImpl來的,你們有時間能夠去看看主線程的源碼,好了,咱們知道了ContextImpl裏面的方法了,下面說一個具體的不太常見的需求.曾經有個需求,要求用戶在卸載以後再從新安裝進入的時候可以讀取一些配置信息,當初服務端要求這個客戶端本身去實現,有同窗說了這個自己不難,很簡單啊,用SharePreference就能夠搞定,恩,是很簡單,但是需求說了再卸載以後從新進入的時候須要讀取出來原來的配置,卸載以後整個應用程序的目錄都不見了,全部數據都消失了,配置文件哪裏來呢?,咱們知道,SharePreference是保存在一個叫作shared_prefs目錄下面的,這個目錄隨着程序卸載也會被刪掉,也就是說卸載以後,保存在原來默認的存儲就會所有消失,那應該怎麼辦呢,其實只須要修改原來的路徑改成自定義的路徑就好,好比放在外部SD卡或者其餘地方,這樣程序卸載的時候就不會刪除這些自定義的目錄了,從而能夠在安裝再次進入的時候讀取出來,看起來這個方法能夠的
ContextImpl裏面有一個字段mPreferencesDir,這個文件目錄就是保存了SharePreference路徑的,咱們只須要修改這個爲咱們自定義的路徑就行了,因爲ContextImpl是一個隱藏類,咱們須要使用反射去實現,隨我走一波吧,下面是具體的代碼:
try {
Class<?> clazz=Class.forName("android.app.ContextImpl");
Method method=clazz.getDeclaredMethod("getImpl", Context.class);
method.setAccessible(true);
Object mContextImpl=method.invoke(null,this);
//獲取ContextImpl的實例
Log.d("[app]","mContextImpl="+mContextImpl);
Field mPreferencesDir=clazz.getDeclaredField("mPreferencesDir");
mPreferencesDir.setAccessible(true);
//咱們自定義的目錄假設在SD卡, 其餘目錄也是同樣的
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
File file=new File(Environment.getExternalStorageDirectory(),"new_shared_pres");
if (!file.exists()){
file.mkdirs();
}
mPreferencesDir.set(mContextImpl,file);
Log.d("[app]","修改sp路徑成功");
}
} catch (Exception e) {
e.printStackTrace();
}
下面是具體的執行結果:
12-08 17:28:42.811 12404-12404/com.example.hotfixdemo D/[app]: mContextImpl=android.app.ContextImpl@db6fc37
12-08 17:28:42.818 12404-12404/com.example.hotfixdemo D/[app]: 修改sp路徑成功
複製代碼
OK,已經成功修改成自定義的路徑了,這樣就達到目的了。
實際上,除了SP的路徑,ContextImpl裏面還有不少相似這樣的路徑,同樣是能夠經過相似的手段修改的,達到一些特定的目的,好比360的插件框架也是Hook了ContextImpl類的數據庫路徑,達到加載的目的,你們有空能夠去看看,Java層的Hook基本以反射和動態代理爲主,這兩方面的內容,有時間再寫,今天就寫到這裏,感謝你們閱讀。