A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 就业部_安卓组 于 2016-11-25 16:05 编辑

有很多人包括我自己有种习惯,就是先存书签以后再看。为了方便各位随时随地,有网没网都可以查看,所以我在文末上传了本文完整附件下载,有需要的同学可以下载到电脑里,方便以后阅览。(快给我个好评)


不同 Context 的分析

Application 中的 Context
ActivityThread 源码

[Java] 纯文本查看 复制代码
public static void main(String[] args) {
        ...
        //初始化Looper
        Looper.prepareMainLooper();
        //创建一个APP主线程ActivityThread对象
        ActivityThread thread = newActivityThread();
        //初始化App应用信息
        thread.attach(false);
        //获得主线程也就是UI线程的handler对象
        if (sMainThreadHandler == null) {
            sMainThreadHandler =thread.getHandler();
        }
        //Android4.1版本之后添加了这么一个方法,目的就是为了能让AsyncTask能在子线程创建,
        //在之前的版本是不能在子线程中创建初始化AsyncTask的。
        AsyncTask.init();
        //启动Looper循环,进入消息循环。
        Looper.loop();
    }

        main 方法主要创建程序主线程ActivityThread 并初始化,并调用 attach 来初始化

[Java] 纯文本查看 复制代码
private void attach(boolean system) {
    //整个应用的Application对象
    Application mInitialApplication;
    //整个应用的后台管家
    Instrumentation mInstrumentation;
        ...
            try {
                mInstrumentation = newInstrumentation();
                ContextImpl context =ContextImpl.createAppContext(
                        this,getSystemContext().mPackageInfo);
                //利用ContextImpl创建整个应用的Application对象
                mInitialApplication =context.mPackageInfo.makeApplication(true, null);
                //调用Application对象的onCreate方法
               mInitialApplication.onCreate();
            } 
       ...
    }

        context.mPackageInfo.makeApplication(true,null); 创建了应用的 Application 对象。跟进去看看

[Java] 纯文本查看 复制代码
public Application makeApplication(booleanforceDefaultAppClass,
            Instrumentationinstrumentation) {
        //第一次进来mApplication==null条件不满足,之后创建Activity的时候条件满足直接返回当前Application对象
        if (mApplication != null) {
            return mApplication;
        }

        Application app = null;

        try {

            //为Appliaction创建ContextImpl对象
            ContextImpl appContext =ContextImpl.createAppContext(mActivityThread, this);
            //调用Instrumentation类中的newApplication方法创建Application
            app =mActivityThread.mInstrumentation.newApplication(
                    cl, appClass,appContext);
            //给ContextImpl设置外部引用
           appContext.setOuterContext(app);
        } 
      ...
        return app;
    }

        通过 Instrumentation 的 newApplication 方法来创建 Application 对象。
        查看 newApplication 方法
[Java] 纯文本查看 复制代码
public Application newApplication(ClassLoadercl, String className, Context context)
            throwsInstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        returnnewApplication(cl.loadClass(className), context);
    }

    static public Application newApplication(Class<?>clazz, Context context)
            throwsInstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app =(Application)clazz.newInstance();
        app.attach(context);
        return app;
    }

        通过类加载器来创建 Application ,并且调用 attach 来初始化。进入 Application 查看 attach

final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk =ContextImpl.getImpl(context).mPackageInfo;
    }  

        Application 类是继承自 ContextWrapper 类,在 attach 方法中调ContextWrapper 中的 attachBaseContext 方法来对 ContextWrapper 的成员变量 mBase 赋值,而 mBase 就是 ContextImpl,通过委托机制让 ContextImpl 真正实现了 Context 类中的所有抽象方法。

Activity 中的Context

        当 Application 创建完成后,AMS 会通过 Binder 机制通知 ActivityThread 去创建需要的 Activity。最终转到 performLaunchActivity 方法来创建 Activity

[Java] 纯文本查看 复制代码
private Activity performLaunchActivity(ActivityClientRecordr, Intent customIntent) {
        ...

        Activity activity = null;
        try {
        //通过Instrumentation类的newActivity方法来创建一个Activity对象
            java.lang.ClassLoader cl =r.packageInfo.getClassLoader();
            activity =mInstrumentation.newActivity(
                    cl,component.getClassName(), r.intent);
           ...
        }
        try {
            //获取当前应用的Application对象,该对象的唯一作用就是作为参数传递到Activity里,
            然后在Activity类中可以获得调用getApplication方法来获取Application对象
            Application app =r.packageInfo.makeApplication(false, mInstrumentation);

          ...
            if (activity != null) {
                //为Activity创建ContextImpl对象
                Context appContext =createBaseContextForActivity(r, activity);
                //为Activity赋值初始化
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app,r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID,r.lastNonConfigurationInstances, config,
                       r.voiceInteractor);
               ...
                //获取当前应用的主题资源
                int theme =r.activityInfo.getThemeResource();
                if (theme != 0) {
                //设置主题
                   activity.setTheme(theme);
                }

                activity.mCalled = false;
                if (r.isPersistable()) {
                //辗转到Activity,调用Activity的生命周期onCreate方法
                   mInstrumentation.callActivityOnCreate(activity, r.state,r.persistentState);
                } else {
                   mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                ...
                r.activity = activity;
                r.stopped = true;
                if(!r.activity.mFinished) {
                //调用Activity的生命周期onStart方法
                    activity.performStart();
                    r.stopped = false;
                }
                ...

        return activity;
    }

        由上可见,是通过 createBaseContextForActivity 来创建 ContextImpl 对象,而又将该对象传递到attach 方法中来对 Activity 进行初始化操作。

[Java] 纯文本查看 复制代码
private Context createBaseContextForActivity(ActivityClientRecordr,
            final Activity activity) {
        ContextImpl appContext =ContextImpl.createActivityContext(this, r.packageInfo, r.token);
        // 让 ContextImpl 持有 Activity 对象的引用
        appContext.setOuterContext(activity);
        Context baseContext = appContext;
   ...
   return baseContext;
}

        appContext.setOuterContext(activity);是为了让 ContextImpl 持有 Activity 的引用,目的是在 ContextImpl 类中注册一些服务,设置主题等。而做这些都需要 Activity 对象的引用。
        再来看看 Activity 的 attach 方法

[Java] 纯文本查看 复制代码
public final void attach(Context context,ActivityThread aThread,
            Instrumentation instr,IBinder token, int ident,
            Application application,Intent intent, ActivityInfo info,
            CharSequence title, Activityparent, String id,
            NonConfigurationInstanceslastNonConfigurationInstances,
            Configuration config,IVoiceInteractor voiceInteractor) {
        //调用父类方法对mBase变量赋值
        attachBaseContext(context);
        //创建一个Activity的窗口
        mWindow =PolicyManager.makeNewWindow(this);
        //给Window窗口设置回调事件
        mWindow.setCallback(this);
       mWindow.setOnWindowDismissedCallback(this);
       mWindow.getLayoutInflater().setPrivateFactory(this);
        //设置键盘弹出状态
        if (info.softInputMode !=WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
           mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
           mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread =Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        //此处注意,将整个应用的Application对象赋值给Activity的mApplication成员变量。
        //目的是为了能在Activity中通过getApplication方法来直接获取Application对象
        mApplication = application;
       ...
    }
    //在Activity中返回当前应用的Application对象
/** Return the application that owns this activity. */
    public final Application getApplication(){
        return mApplication;
    }
}

        attach 方法进来就调用了父类的attachBaseContext 方法。
        attachBaseContext 里面的代码以及剩下的要做的事在前面的环节中已经分析过了,此处就不再分析了。
        结论与上述一样,之所以 Activity 可以调用 Context 里面的任何方法,是因为 mBase ,而 mBase 就是一个 ComtextImpl 对象,车轱辘话我就不来回说了。

Service 中的Context

        与 Activity 类似,Service 也是由 AMS 通过 Binder 机制通知 ActivityThread 去创建一个 Service ,最后转到 handleCreateService 方法。
[Java] 纯文本查看 复制代码
private void handleCreateService(CreateServiceDatadata) {

        LoadedApk packageInfo =getPackageInfoNoCheck(
                data.info.applicationInfo,data.compatInfo);
        Service service = null;
        try {
            //通过类加载器创建Service服务
            java.lang.ClassLoader cl =packageInfo.getClassLoader();
            service = (Service)cl.loadClass(data.info.name).newInstance();
        } 
    ...
        try {
          ...
          //此处为Service创建一个ContextImpl对象
            ContextImpl context =ContextImpl.createAppContext(this, packageInfo);
            //同样为ContextImpl类设置外部对象,目的还是让ContextImpl持有外部类的引用
            //在ContextImpl类中的许多方法需要使用到外部Context对象引用
           context.setOuterContext(service);
    ...
            //获得当前应用的Applicaton对象,该对象在整个应用中只有一份,是共享的。
            Application app =packageInfo.makeApplication(false, mInstrumentation);
            //将ContextImpl对象和Application对象作为attach方法参数来初始化Service
            service.attach(context, this,data.info.name, data.token, app,
                   ActivityManagerNative.getDefault());
            //Service初始化完成之后系统自动调用onCreate生命周期方法
            service.onCreate();
     ...
    }

        与上述 Application 和 Activity 类似,也是通过类加载器来创建 Service 对象,然后再创建 ContextImpl 对象,而且也是为 ContextImpl 设置 Service 对象 Context 的引用,其目的也是 ContextImpl 类中的许多方法需要使用外部 Context 引用。
        接下来的事情都类似了,跟 Activity 差不多,看一下 attach 方法

[Java] 纯文本查看 复制代码
public final void attach(
            Context context,
            ActivityThread thread, String className,IBinder token,
            Application application,Object activityManager) {
        //调用父类方法去注册ContextImpl对象
        attachBaseContext(context);
        mThread = thread;           // NOTE:  unused - remove?
        mClassName = className;
        mToken = token;
        //将整个应用的Applicaton对象赋值给Service类的成员变量mApplication
        mApplication = application;
        mActivityManager =(IActivityManager)activityManager;

    }

        不做累述,分析与前文相似。

创建 Context 的总结

        上述分析了这么多,看了这么多源码,其实有关创建 Context 对象的地方其实都是在 ActivityThread 类中,而 ActivityThread 就是整个应用的入口,也是整个应用的主线程。
        每个应用首先会创建一个 Application 对象,且只有一个,然后会根据你的代码和需求创建相应的 Activity 和 Service。并且在创建 Activity 或者 Service 的时候都会持有当前应用的 Application 对象,通过 getApplication 方法就可以拿到。
        Context 定义了 N 多方法,都是抽象的。需要子类 Context 的子类去实现,Activity 、Application 、Service 创建的时候都会去创建一个 ContextImpl 对象,最终他们都会通过调用 ContextWrapper 里面的 attachBaseContext 方法给 mBase 赋值,也就是 ContextImpl 对象,所以真正的实现体是 ContextImpl。

getApplication 与getApplicationContext 的区别

        有时候会发现 getApplicationContext 可以做的事 getApplication 也能做,他们两个有什么区别呢?
        查看 getApplicationContext 的源代码

[Java] 纯文本查看 复制代码
public class ContextWrapper extends Context{
    ...
    @Override
    public Context getApplicationContext(){
        returnmBase.getApplicationContext();
    }
    ...
}

        发现是调用了 mBase 的 getApplicationContext 方法,也就是在 ContextImpl 里面实现,追过去看一下

[Java] 纯文本查看 复制代码
class ContextImpl extends Context {
    ...
    @Override
    public Context getApplicationContext(){
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication(): mMainThread.getApplication();
    }
    ...
}

        返回值为mPackageInfo.getApplication() 或者mMainThread.getApplication();
        类型是 Context 。
        再来看看 getApplication

[Java] 纯文本查看 复制代码
public final Application getApplication() {
        return mApplication;
}

        返回的是当前应用的 Application 对象
        所以:
        getApplication 和 getApplicationContext 方法返回的对象都是指向当前应用的Application对象,是同一个Application 对象,仅仅是返回值类型不同而已。
        getApplication 方法是在 Activity,Service 类中实现的;
        getApplicationContext 方法是在 ContextWrapper 类中实现的。
        也就是 getApplication 方法是在 ContextWrapper 子类中实现的,而 getApplicationContext 是在父类中实现的,所以两个方法的使用范围是不一样的。比如说你可以
context.getApplicationContext();
        却不能
context.getApplication();
        除非你这个 Context 是 Activity 或者 Service。

Context 的作用域

        该段参考: https://possiblemobile.com/2013/06/context/
        虽然Context神通广大,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。


        上图中的 No 上面有些数字1,2,意思如下:
                数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
                数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
                数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)

        注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个 context 用于使用。

Context 引起的问题

使用 Application 的问题

        虽然每个项目里面都有 Application ,而且用法虽然简单,但是常在河边走哪有不湿鞋?有些地方需要注意一下,比如说,我们都知道 Context 定义了很多方法,诸如getString(),getPackageName(),startActivity()等等等等,既然 Application 是 Context 的子类,那我就可以调用getPackageName(),快速测试一下

[Java] 纯文本查看 复制代码
package com.xy.bbs;

import android.app.Application;

/**
* Created by mesmerize on 2016/11/1.
*/

public class TestApplication extends Application {

    public TestApplication() {

        String packageName = this.getPackageName();

        System.out.println("ConstructorpackageName = " + packageName);
    }

        很简单的一段代码,就是在在构造函数中打印包名,然而运行之后,就崩溃了,log 如下:

[Java] 纯文本查看 复制代码
Caused by: java.lang.NullPointerException:Attempt to invoke virtual method 'java.lang.Stringandroid.content.Context.getPackageName()' on a null object reference
                                                             at android.content.ContextWrapper.getPackageName(ContextWrapper.java:132)
                                                             at com.xy.bbs.TestApplication.<init>(TestApplication.java:13)
                                                             at java.lang.reflect.Constructor.newInstance0(Native Method) 
                                                             at java.lang.reflect.Constructor.newInstance(Constructor.java:430) 
                                                             atcom.android.tools.fd.runtime.BootstrapApplication.createRealApplication(BootstrapApplication.java:215)
                                                             atcom.android.tools.fd.runtime.BootstrapApplication.attachBaseContext(BootstrapApplication.java:239)
                                                             at android.app.Application.attach(Application.java:189) 
                                                              ...

        NullPointerException,哎!?怎么还空指针了?
        这点稍后说,先把代码改成这样再试试:

[Java] 纯文本查看 复制代码
package com.xy.bbs;

import android.app.Application;

/**
* Created by mesmerize on 2016/11/1.
*/
public class TestApplication extends Application {

    @Override
    public void onCreate() {

        String packageName = this.getPackageName();

        System.out.println("onCreate()packageName = " + packageName);
    }
}

        然后一切正常,正确打印包名。
        这是为什么呢?
        之前在谈 ContextWrapper 的时候讲过给 Context 赋值是在 attachBaseContext() 这个方法中赋值,赋值给 mBase ,并且里面所定义的所有方法全是通过 mBase 这个对象来调用的,试想一下,如果 mBase 为 null,调用其中定义的方法时会怎样?肯定就空指针了啊!
        所以,这也就引出了 Applicatioin 方法的执行顺序
        先走 constructor => 然后 attachBaseContext() => 最后 onCreate()
        因此我们在 onCreate() 里面调用 ContextWrapper 定义的方法就不会有问题,而在构造函数中调用就会空指针。
        这是一个问题。

        当然你也可以重写 attachBaseContext() 这个方法,但是要注意要在 super.attachBaseContext(base); 之后调用,不能在之前。
        还有一种空指针的情况,虽说 Google 不建议我们自定义 Application,更推崇使用单例,但是身边的人几乎都是在用自定义 Application,不谈这个。
        有些人写单例习惯了,有时候可能写出这样的代码:

[Java] 纯文本查看 复制代码
public class TestApplication extends Application{  

    private static TestApplicationapplicatioin;  

    public static TestApplication getInstance(){  
        if (applicatioin == null) {  
            applicatioin = newTestApplication();  
        } 
        return applicatioin;  
    } 

}

        有问题吗?
        肯定是有问题的!
        Application 是系统的组件,系统组件就要让系统自己去创建,这样new出来,就只是一个普通的 java 对象,并不具备 Context 的能力,拿这样一个对象去调用 Context 的方法,还是空指针。
        正确写法如下:

[Java] 纯文本查看 复制代码
public class TestApplication extends Application{  

    private static TestApplicationapplication;  

    public static TestApplication getInstance(){  
        return application;  
    } 

    @Override  
    public void onCreate() {  
        super.onCreate();  
        application = this;  
    } 

}  

        浅显易懂,我就不多做解释了。

内存泄露的问题

        该段部分代码参考:https://possiblemobile.com/2013/06/context/
        首先看一段代码:

[Java] 纯文本查看 复制代码
public class CustomManager {
    private static CustomManagersInstance;

    public static CustomManager getInstance(Contextcontext) {
        if (sInstance == null) {
            sInstance = newCustomManager(context);
        }

        return sInstance;
    }

    private Context mContext;

    private CustomManager(Contextcontext) {
        mContext = context;
    }
}

        这就是一个很普通的单例,我们在项目中经常会编写一些工具类等,可能会写成单例的模式,比如上述代码。这就可能造成一些问题。
        可以看到上述代码中需要一个 Context,由于工具类大多需要访问资源,所以需要 Context ,这无可厚非,但是有一种可能,就是 Context 的类型。如果是在 Activity 中或者是 Service 中呢?
        sInstance 可是一个静态的强引用的啊!而我们都知道static 的生命周期是伴随整个项目的生命周期而存活的,静态变量在整个应用的内存里只保存一份,一旦创建就不会释放该变量的内存,直到整个应用都销毁才会释放static静态变量的内存。如果你用 Activity 作为 Context传了进来。那也就是说,此 Activity 会一直活下去,直到项目退出。否则的话,这个 Activity 是没办法被回收的。而 Activity 生命周期肯定没这么长,所以就造成了内存泄露了!
        所以,遇到这种问题的时候,尽可能改成跟整个应用的生命周期一样的 Context,比如 ApplicationContext。
        改动代码如下:

[Java] 纯文本查看 复制代码
public class CustomManager {
    private static CustomManagersInstance;

    public static CustomManager getInstance(Contextcontext) {
        if (sInstance == null) {
            //Always pass in theApplication Context
            sInstance = newCustomManager(context.getApplicationContext());
        }

        return sInstance;
    }

    private Context mContext;

    private CustomManager(Contextcontext) {
        mContext = context;
    }
}

        即可。
        再来看看另一种 View 持有引用导致的内存泄露

[Java] 纯文本查看 复制代码
public class CustomResource {
    //静态变量drawable
    private static Drawable mDrawable;
    private View view;

    public CustomResource(Contextcontext) {
        Resources resources =context.getResources();
        mDrawable =resources.getDrawable(R.drawable.ic_launcher);
        view = new View(context);
       view.setBackgroundDrawable(mDrawable);
    }
}

        这段代码也会内存泄露,查阅 setBackgroundDrawable() 的源码可以看到:

[Java] 纯文本查看 复制代码
publicvoid setBackgroundDrawable(Drawable background) {
         ...
         /**此处的this就是当前View对象*/
         // Set callback last, since theview may still be initializing.
         background.setCallback(this);
         ...
    }

        而View对象哪来的?是通过Context对象获得的。
        因此,变量 background 持有View对象的引用,View持有Context的引用,所有background间接持有Context对象的引用了。这也就意味着,如果该 Context 对应的 Activity 退出 finish 掉的时候,该 Activity 是不能完全释放的,因为 static 的 drawable 持有该 Activity 的 Context 对象的间接引用,从而导致该 Activity 内存无法回收,导致内存泄露隐患。
        但是起因是由于 static drawable 持有 View 对象的引用导致内存泄露的,并不是由于 context.getResource 导致的,因此并不能用 context.getApplicationContext 来解决。Android 3.0之后修改了setBackgroundDrawable 方法的 setCallback(this)方法,里面实现使用了弱引用来持有 View 对象的引用,从而避免了内存泄露隐患。
所以,尽量避免使用静态资源,或者直接使用弱引用来解决也是可以的。
        一般而言,由 Context 所造成的内存泄露,几乎都是当 Context 销毁的时候,却由于被引用而导致销毁失败,所以我们应该记住正确使用 Context 的姿势:
当 Application 的 Context 能搞定的情况下,并且生命周期长的对象,优先使用 Application 的Context。
        不要让生命周期长于 Activity 的对象持有 Activity 的引用。
        尽量不要在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,而静态内部类不会持有外部类的引用,外部类会以正常的方式回收。如果静态内部类想使用外部类的属性或方法,可以将外部实例引用作为弱引用持有。

       点此进入:Android 人事+技术总贴
       点此进入:Android 基础篇总贴
       点此进入:Android 进阶篇总贴
       点此进入:Android 讨论区

       以上言论,如有错误或者表达不准确或者有纰漏的地方还请指正,同时欢迎大家在评论区留言给予一定的建议,我会根据大家的意见进行相应的改正,谢谢各位!



Context 源码的理解.html

357.58 KB, 阅读权限: 10, 下载次数: 11

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马