說起這個(gè)ContextImpl.可能有些同學(xué)不太熟悉手蝎,但說起Context,我想都認(rèn)識(shí)它吧俐芯,上下文棵介,也可以說是代表一種所在的場(chǎng)景,由于Context只是一個(gè)抽象類吧史,而抽象類必定是有一個(gè)具體的實(shí)現(xiàn)類的邮辽,另外還有ContextThemeWrapper和ContextWrapper,不過這些都是Context的子類而已,他們是以裝飾模式而存在的一種關(guān)系,簡(jiǎn)單說下裝飾模式吨述,裝飾模式是常用的設(shè)計(jì)模式之一岩睁,一般情況下如果需要?jiǎng)討B(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)但又不想增加子類,那么就可以用到裝飾模式了揣云,如果單純?cè)黾庸δ軄碚f笙僚,Decorator模式相比生成子類更為靈活,該模式以對(duì)客 戶端透明的方式擴(kuò)展對(duì)象的功能,下面舉一個(gè)簡(jiǎn)單的例子說明一下,首先一個(gè)人灵再,有吃飯的功能接口
代碼如下:
Component
public interface Person {
void eat();
}
ConcreteComponent
public class Man implements Person {
public void eat() {
System.out.println("男人在吃飯");
}
}
Decorator(這個(gè)類是關(guān)鍵肋层,實(shí)現(xiàn)了接口并且有真實(shí)對(duì)象的引用)
public abstract class Decorator implements Person {
protected Person person;
public Decorator(Person person){
this.person=person;
}
public void eat() {
person.eat();
}
}
下面的就是具體的添加額外功能的了具體子類,比如有些人吃飯前先洗手或者吃完飯后洗碗之類翎迁,當(dāng)然具體什么事情根據(jù)業(yè)務(wù)決定
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("吃完飯后洗碗");
}
}
測(cè)試的結(jié)果為:
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();
}
運(yùn)行結(jié)果為:
可以看到在吃飯前和吃飯后做了一些事情栋猖,這就達(dá)到了不增加子類而又可以添加一些額外功能的作用
回到ContextImpl和Context,其實(shí)也是一樣的汪榔,這個(gè)ContextImpl相當(dāng)于代碼中的Man,而Context相當(dāng)于Person,只是一個(gè)接口蒲拉,一個(gè)抽象類,但本質(zhì)都是一樣痴腌,而Decorator相當(dāng)于ContextWrapper雌团,那么具體的抽象實(shí)現(xiàn)類為什么呢,在Android中Activity和Service就是類似代碼中的ManDecoratorA和ManDecoratorB了士聪,只不過Activity有界面锦援,自然就有Theme主題,因此對(duì)應(yīng)的是ContextThemeWrapper剥悟,現(xiàn)在已經(jīng)對(duì)裝飾模式理解的更深了吧灵寺。
現(xiàn)在回到ContextImpl本身來,ContextImpl作為Context的抽象類区岗,實(shí)現(xiàn)了所有的方法略板,我們常見的getResources(),getAssets(),getApplication()等等的具體實(shí)現(xiàn)都是在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();
}
可以看到我們平常開發(fā)所調(diào)用的方法的實(shí)現(xiàn)都是在這里完成慈缔,作為一名android開發(fā)者叮称,我們應(yīng)該多去研究一些源碼之類的,這樣在出問題的時(shí)候可以根據(jù)源碼找出問題的具體所在藐鹤,ContextImpl在主線程ActivityThread通過傳入主線程對(duì)象創(chuàng)建了一個(gè)系統(tǒng)的ContextImpl,下面是代碼:
public ContextImpl getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
mSystemContext = ContextImpl.createSystemContext(this);
}
return mSystemContext;
}
}
這個(gè)是在ActivityThread里面完成的瓤檐,ActivityThread是Android應(yīng)用程序的主線程環(huán)境,關(guān)于對(duì)ActivityThread的分析教藻,可以看我的另一篇文章 關(guān)于Android主線程(ActivityThread)源代碼分析以及一些特殊問題的非常規(guī)方法
大家有空可以去看看,實(shí)際上Activity和Service中的Context也是通過ContextImpl來的,大家有時(shí)間可以去看看主線程的源碼距帅,好了,我們知道了ContextImpl里面的方法了括堤,下面說一個(gè)具體的不太常見的需求.曾經(jīng)有個(gè)需求碌秸,要求用戶在卸載之后再重新安裝進(jìn)入的時(shí)候能夠讀取一些配置信息绍移,當(dāng)初服務(wù)端要求這個(gè)客戶端自己去實(shí)現(xiàn),有同學(xué)說了這個(gè)本身不難讥电,很簡(jiǎn)單啊蹂窖,用SharePreference就可以搞定,恩恩敌,是很簡(jiǎn)單瞬测,可是需求說了再卸載之后重新進(jìn)入的時(shí)候需要讀取出來原來的配置,卸載之后整個(gè)應(yīng)用程序的目錄都不見了纠炮,所有數(shù)據(jù)都消失了月趟,配置文件哪里來呢?,我們知道恢口,SharePreference是保存在一個(gè)叫做shared_prefs目錄下面的孝宗,這個(gè)目錄隨著程序卸載也會(huì)被刪掉,也就是說卸載之后耕肩,保存在原來默認(rèn)的存儲(chǔ)就會(huì)全部消失因妇,那應(yīng)該怎么辦呢,其實(shí)只需要修改原來的路徑改為自定義的路徑就好猿诸,比如放在外部SD卡或者其他地方婚被,這樣程序卸載的時(shí)候就不會(huì)刪除這些自定義的目錄了,從而可以在安裝再次進(jìn)入的時(shí)候讀取出來梳虽,看起來這個(gè)方法可以的
ContextImpl里面有一個(gè)字段mPreferencesDir址芯,這個(gè)文件目錄就是保存了SharePreference路徑的,我們只需要修改這個(gè)為我們自定義的路徑就好了怖辆,由于ContextImpl是一個(gè)隱藏類是复,我們需要使用反射去實(shí)現(xiàn)删顶,隨我走一波吧竖螃,下面是具體的代碼:
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的實(shí)例
Log.d("[app]","mContextImpl="+mContextImpl);
Field mPreferencesDir=clazz.getDeclaredField("mPreferencesDir");
mPreferencesDir.setAccessible(true);
//我們自定義的目錄假設(shè)在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();
}
下面是具體的執(zhí)行結(jié)果:
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,已經(jīng)成功修改為自定義的路徑了,這樣就達(dá)到目的了逗余。
實(shí)際上特咆,除了SP的路徑,ContextImpl里面還有很多類似這樣的路徑录粱,一樣是可以通過類似的手段修改的腻格,達(dá)到一些特定的目的,比如360的插件框架也是Hook了ContextImpl類的數(shù)據(jù)庫路徑啥繁,達(dá)到加載的目的菜职,大家有空可以去看看,Java層的Hook基本以反射和動(dòng)態(tài)代理為主旗闽,這兩方面的內(nèi)容酬核,有時(shí)間再寫蜜另,今天就寫到這里,感謝大家閱讀嫡意。