深入解析Android App开发中Context的用法

时间:2022-05-22 07:44:07

context在开发android应用的过程中扮演着非常重要的角色,比如启动一个activity需要使用context.startactivity方法,将一个xml文件转换为一个view对象也需要使用context对象,可以这么说,离开了这个类,android开发寸步难行,对于这样一个类,我们又对他了解多少呢。我就说说我的感受吧,在刚开始学习android开发时,感觉使用context的地方一直就是传入一个activity对象,久而久之感觉只要是context的地方就传入一个activity就行了,那么我们现在就来详细的分析一下context和activity的关系吧!
在开始本文之前我们先放置一个问题在这里:
我们平时在获取项目资源时使用context.getresources()的时候为什么放回的是同一个值,明明是使用不同的activity调用getresources返回结果却是一样的。

深入解析Android App开发中Context的用法

context本身是一个纯的abstract类,contextwrapper是对context的一个包装而已,它的内部包含了一个context对象,其实对contextwrapper的方法调用最终都是调用其中的context对象完成的,至于contextthremewrapper,很明显和theme有关,所以activity从contextthemmwrapper继承,而service从contextwrapper继承,contextimpl是唯一一个真正实现了context中方法的类。
 
从上面的继承关系来看,每一个activity就是一个context,每一个service就是一个context,这也就是为什么使用context的地方可以被activity或者service替换了。
 

创建context
根据前面所说,由于实现了context的只有contextimpl类,activity和service本没有真正的实现,他们只是内部包含了一个真实的context对象而已,也就是在在创建activity或者service的时候肯定要创建爱你一个contextimpl对象,并赋值到activity中的context类型变量中。那我们就来看看andorid源码中有哪些地方创建了contextimpl.
据统计android中创建contextimpl的地方一共有7处:

  • 在packageinfo.makeapplication()中
  • 在performlaunchactivity()中
  • 在handlecreatebackupagent()中
  • 在handlecreateservice()中
  • 2次在hanldbinderappplication()中
  • 在attach()方法中


由于创建contextimpl的基本原理类似,所以这里只会分析几个比较有代表性的地方:
1、  application对应的context
在应用程序启动时,都会创建一个application对象,所以辗转调用到handlebindapplication()方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private final void handlebindapplication(appbinddata data) {
    mboundapplication = data;
    mconfiguration = new configuration(data.config);
 
    ....
    data.info = getpackageinfonocheck(data.appinfo);
 
      ...
     
    application app = data.info.makeapplication(data.restrictedbackupmode, null);
    minitialapplication = app;
 
   ....
 }

其中data.info是loadedapk类型的,到getpackageinfonocheck中看看源码

?
1
2
3
public final loadedapk getpackageinfonocheck(applicationinfo ai) {
    return getpackageinfo(ai, null, false, true);
}

里面其实调用的是getpackageinfo,继续跟进:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (includecode) {
        ref = mpackages.get(ainfo.packagename);
      } else {
        ref = mresourcepackages.get(ainfo.packagename);
      }
      loadedapk packageinfo = ref != null ? ref.get() : null;
      if (packageinfo == null || (packageinfo.mresources != null
          && !packageinfo.mresources.getassets().isuptodate())) {
        if (locallogv) slog.v(tag, (includecode ? "loading code package "
            : "loading resource-only package ") + ainfo.packagename
            + " (in " + (mboundapplication != null
                ? mboundapplication.processname : null)
            + ")");
        packageinfo =
          new loadedapk(this, ainfo, this, baseloader,
              securityviolation, includecode &&
              (ainfo.flags&applicationinfo.flag_has_code) != 0);
if (includecode) {
          mpackages.put(ainfo.packagename,
              new weakreference<loadedapk>(packageinfo));
        } else {
          mresourcepackages.put(ainfo.packagename,
              new weakreference<loadedapk>(packageinfo));
        }

由于includecode传入的是true,所以首先从mpackages中获取,如果没有,则new一个出来,并放入mpackages里面去,注意,这里的mpackages是activitythread中的属性。
下面继续分析一下loadedapk这个类中的makeapplication函数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
      java.lang.classloader cl = getclassloader();
      //创建一个contextimpl对象
      contextimpl appcontext = new contextimpl();
      appcontext.init(this, null, mactivitythread);
      app = mactivitythread.minstrumentation.newapplication(
          cl, appclass, appcontext);
      appcontext.setoutercontext(app);
    } catch (exception e) {
      if (!mactivitythread.minstrumentation.onexception(app, e)) {
        throw new runtimeexception(
          "unable to instantiate application " + appclass
          + ": " + e.tostring(), e);
      }
    }

这里创建了一个contextimpl对象,并调用了它的init方法,现在进入init方法。 

?
1
2
mpackageinfo = packageinfo;
mresources = mpackageinfo.getresources(mainthread);

对mpackageinof和mresources两个变量初始化
回到makeapplication中,创建了一个application对象,并将appcontext传进去,其实就是将appcontext传递给contextwrapper中的context类型变量(application也是继承contextwrapper)
2、activity中的context
在创建一个activity时,经过辗转调用,会执行handlelaunchactivity(),然后调用performlaunchactivity(),该方法创建contextimpl代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
r.packageinfo= getpackageinfo(ainfo.applicationinfo,
          context.context_include_code);
 
contextimplappcontext = new contextimpl();
        appcontext.init(r.packageinfo,r.token, this);
        appcontext.setoutercontext(activity);
 
activity.attach(appcontext,this, getinstrumentation(), r.token,
            r.ident, app, r.intent,r.activityinfo, title, r.parent,
            r.embeddedid,r.lastnonconfigurationinstance,
            r.lastnonconfigurationchildinstances, config);

由于getpackageinfo函数之前已经分析过了,稍微有点区别,但是大致流程是差不多的,所以此处的appcontext执行init之后,其中的mpackages变量和mresources变量时一样的,activity通过attach函数将该appcontext赋值到contextwrapper中的context类型变量。

3、service中的context
同样 在创建一个service时,经过辗转调用会调用到schedulecreateservice方法,之后会巧用handlecreateservice

?
1
2
3
4
5
6
7
8
9
10
loadedapkpackageinfo = getpackageinfonocheck(
        data.info.applicationinfo);
 
contextimplcontext = new contextimpl();
      context.init(packageinfo, null,this);
 
      application app =packageinfo.makeapplication(false, minstrumentation);
      context.setoutercontext(service);
      service.attach(context, this,data.info.name, data.token, app,
          activitymanagernative.getdefault());

其思路和上面两个基本一样,在此就不再详述。

context对资源的访问
很明确,不同的context得到的都是同一份资源。这是很好理解的,请看下面的分析

得到资源的方式为context.getresources,而真正的实现位于contextimpl中的getresources方法,在contextimpl中有一个成员 private resources mresources,它就是getresources方法返回的结果,mresources的赋值代码为:

?
1
2
mresources = mresourcesmanager.gettoplevelresources(mpackageinfo.getresdir(),
          display.default_display, null, compatinfo, activitytoken);

下面看一下resourcesmanager的gettoplevelresources方法,这个方法的思想是这样的:在resourcesmanager中,所有的资源对象都被存储在arraymap中,首先根据当前的请求参数去查找资源,如果找到了就返回,否则就创建一个资源对象放到arraymap中。有一点需要说明的是为什么会有多个资源对象,原因很简单,因为res下可能存在多个适配不同设备、不同分辨率、不同系统版本的目录,按照android系统的设计,不同设备在访问同一个应用的时候访问的资源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public resources gettoplevelresources(string resdir, int displayid,
    configuration overrideconfiguration, compatibilityinfo compatinfo, ibinder token) {
  final float scale = compatinfo.applicationscale;
  resourceskey key = new resourceskey(resdir, displayid, overrideconfiguration, scale,
      token);
  resources r;
  synchronized (this) {
    // resources is app scale dependent.
    if (false) {
      slog.w(tag, "gettoplevelresources: " + resdir + " / " + scale);
    }
    weakreference<resources> wr = mactiveresources.get(key);
    r = wr != null ? wr.get() : null;
    //if (r != null) slog.i(tag, "isuptodate " + resdir + ": " + r.getassets().isuptodate());
    if (r != null && r.getassets().isuptodate()) {
      if (false) {
        slog.w(tag, "returning cached resources " + r + " " + resdir
            + ": appscale=" + r.getcompatibilityinfo().applicationscale);
      }
      return r;
    }
  }
 
  //if (r != null) {
  //  slog.w(tag, "throwing away out-of-date resources!!!! "
  //      + r + " " + resdir);
  //}
 
  assetmanager assets = new assetmanager();
  if (assets.addassetpath(resdir) == 0) {
    return null;
  }
 
  //slog.i(tag, "resource: key=" + key + ", display metrics=" + metrics);
  displaymetrics dm = getdisplaymetricslocked(displayid);
  configuration config;
  boolean isdefaultdisplay = (displayid == display.default_display);
  final boolean hasoverrideconfig = key.hasoverrideconfiguration();
  if (!isdefaultdisplay || hasoverrideconfig) {
    config = new configuration(getconfiguration());
    if (!isdefaultdisplay) {
      applynondefaultdisplaymetricstoconfigurationlocked(dm, config);
    }
    if (hasoverrideconfig) {
      config.updatefrom(key.moverrideconfiguration);
    }
  } else {
    config = getconfiguration();
  }
  r = new resources(assets, dm, config, compatinfo, token);
  if (false) {
    slog.i(tag, "created app resources " + resdir + " " + r + ": "
        + r.getconfiguration() + " appscale="
        + r.getcompatibilityinfo().applicationscale);
  }
 
  synchronized (this) {
    weakreference<resources> wr = mactiveresources.get(key);
    resources existing = wr != null ? wr.get() : null;
    if (existing != null && existing.getassets().isuptodate()) {
      // someone else already created the resources while we were
      // unlocked; go ahead and use theirs.
      r.getassets().close();
      return existing;
    }
 
    // xxx need to remove entries when weak references go away
    mactiveresources.put(key, new weakreference<resources>(r));
    return r;
  }
}

根据上述代码中资源的请求机制,再加上resourcesmanager采用单例模式,这样就保证了不同的contextimpl访问的是同一套资源,注意,这里说的同一套资源未必是同一个资源,因为资源可能位于不同的目录,但它一定是我们的应用的资源,或许这样来描述更准确,在设备参数和显示参数不变的情况下,不同的contextimpl访问到的是同一份资源。设备参数不变是指手机的屏幕和android版本不变,显示参数不变是指手机的分辨率和横竖屏状态。也就是说,尽管application、activity、service都有自己的contextimpl,并且每个contextimpl都有自己的mresources成员,但是由于它们的mresources成员都来自于唯一的resourcesmanager实例,所以它们看似不同的mresources其实都指向的是同一块内存(c语言的概念),因此,它们的mresources都是同一个对象(在设备参数和显示参数不变的情况下)。在横竖屏切换的情况下且应用中为横竖屏状态提供了不同的资源,处在横屏状态下的contextimpl和处在竖屏状态下的contextimpl访问的资源不是同一个资源对象。

 

代码:单例模式的resourcesmanager类

?
1
2
3
4
5
6
7
8
public static resourcesmanager getinstance() {
  synchronized (resourcesmanager.class) {
    if (sresourcesmanager == null) {
      sresourcesmanager = new resourcesmanager();
    }
    return sresourcesmanager;
  }
}

getapplication和getapplicationcontext的区别
 

getapplication返回结果为application,且不同的activity和service返回的application均为同一个全局对象,在activitythread内部有一个列表专门用于维护所有应用的application

 

?
1
final arraylist<application> mallapplications = new arraylist<application>();

getapplicationcontext返回的也是application对象,只不过返回类型为context,看看它的实现

?
1
2
3
4
5
@override
public context getapplicationcontext() {
  return (mpackageinfo != null) ?
      mpackageinfo.getapplication() : mmainthread.getapplication();
}

上面代码中mpackageinfo是包含当前应用的包信息、比如包名、应用的安装目录等,原则上来说,作为第三方应用,包信息mpackageinfo不可能为空,在这种情况下,getapplicationcontext返回的对象和getapplication是同一个。但是对于系统应用,包信息有可能为空,具体就不深入研究了。从这种角度来说,对于第三方应用,一个应用只存在一个application对象,且通过getapplication和getapplicationcontext得到的是同一个对象,两者的区别仅仅是返回类型不同。


在此总结一下:
(1)context是一个抽象类,contextwrapper是对context的封装,它包含一个context类型的变量,contextwrapper的功能函数内部其实都是调用里面的context类型变量完成的。application,service,activity等都是直接或者间接继承自contextwrapper,但是并没有真正的实现其中的功能,application,service,activity中关于context的功能都是通过其内部的context类型变量完成的,而这个变量的真实对象必定是contextimpl,所以没创建一个application,activity,servcice便会创建一个contextimpl,并且这些contextimpl中的mpackages和mresources变量都是一样的,所以不管使用acitivty还是service调用getresources得到相同的结果
(2)在一个apk中,context的数量等于activity个数+service个数+1.