译文《Context,到底什么是Context?》

by admin on 2019年9月8日

本文译自《Context, What
Context?》注:文中提到的“导入布局”,即是指利用LayoutInflater来inflate
layout的操作。

什么是Android Context?

一个Context意味着一个场景,一个场景就是我们和软件进行交互的一个过程。比如和妹纸约会的月下小桥,比如当你使用微信的时候,场景包括聊天界面、通讯录、朋友圈,以及背后的一些数据。

那么从安卓程序的角度来看,Context是什么?其实一个Activity就是一个Context,一个Service也是一个Context。

一个应用程序可以认为是一个约会环境,用户在这个环境中会切换到不同的场景,比如先去有情调的饭店吃饭,再去电影院看个电影,然后再去xxx(此处省略一万字…)。

Activity类的确是基于Context,而Service类也是基于Context。Activity除了基于Context类外,还实现了一些其他重要的接口,从架构设计的角度看,interface仅仅是某些功能,而extends才是类的本质,即Activity的本质是一个Context,其所实现的其他接口只是为了扩充Context的功能而已,扩充后的类称之为一个Activity或Service。

不明白,context是什么?

哪里都有context,我就不明白了,这context到底是个什么玩意儿?

做营销的朋友大概都看到报道了,最近谷歌遇到了大麻烦。

参考:http://blog.csdn.net/zhiyi2010/article/details/13017171

Context实例非常常见,在许多的情境下(加载资源、启动一个Activity、取得一个系统级的Service、取得应用独有的文件存储路径还有创建View等)都需要用到一个Context实例,但如果不加区分地使用任意的Context实例,很容易会导致一些没意料到的状况发生。

一个应用程序中应该有多少个Context对象

我们在应用程序开发中经常会调用Context的一些方法,这些方法看起来似乎会返回一些全局的对象,而不仅仅是某个Activity,可能会有点疑问,一个应用程序到底有多少个Context对象呢?比如,Context.getResources()返回该应用程序所对应的Resource类对象,无论从哪个Activity中调用,都会返回同一个Resource对象。

  • 一个Activity就是一个场景(Context),一个Service也是一个场景,所以,应用程序中有多少个Activity或者Service就会有多少个Context对象,也就是有多少个场景。
  • getResource()等方法返回的是同一个全局对象。

引用1

Context是一个抽象基类,我们通过它访问当前包的资源(getResources、getAssets)和启动其他组件(Activity、Service、Broadcast)以及得到各种服务(getSystemService),当然,通过Context能得到的不仅仅只有上述这些内容。对Context的理解可以来说:Context提供了一个应用的运行环境,在Context的大环境里,应用才可以访问资源,才能完成和其他组件、服务的交互,Context定义了一套基本的功能接口,我们可以理解为一套规范,而Activity和Service是实现这套规范的子类,这么说也许并不准确,因为这套规范实际是被ContextImpl类统一实现的,Activity和Service只是继承并有选择性地重写了某些规范的实现。

有250多个广告主出于广告安全的原因,联合抵制谷歌及Youtube广告,因为通过谷歌投放的广告可能会与恐怖主义等负面内容同时出现。

应用程序在运行的过程中如果需要向手机上保存数据,一般是把数据保存在SDcard中的。
大部分应用是直接在SDCard的根目录下创建一个文件夹,然后把数据保存在该文件夹中。
这样当该应用被卸载后,这些数据还保留在SDCard中,留下了垃圾数据。
如果你想让你的应用被卸载后,与该应用相关的数据也清除掉,该怎么办呢?

Context的种类

并不是所有的Context实例都是一样的构造流程。常见的Context子类如下所列:

  • Application——在你的应用进程中单例存在的一个实例。可以通过Activity或Service的getApplication()方法或者其他任意Context子类的getApplicationContext()方法来取得。不论是在哪里以及何时取得的Application实例,它都是进程唯一的。

  • Activity/Service——继承自ContextWrapper类,它们实现了与Context类同样的API,但代理了所有的方法到一个对外不可见的Context实例,也就是它们的base
    Context。每当系统框架创建一个新的Activity或者Service实例时,它同时也会创建一个ContextImpl实例去执行不同的组件所需要做的不同逻辑。每个Activity或Service,以及它们相应的base
    context,都是实例唯一的。

  • BroadcastReceiver——这并不是一个Context子类。但每个Receiver都会实现onReceive(Context
    context, Intent
    intent)这个回调方法,每次系统发送通知都是调用到这个回调方法,这里就给Receiver传入了一个Context实例。这里传入的Context实例又与其他的Context实例不一样,这里传入的Context实例是不能调用registerReceiver()方法和bindService()方法的。每次发送一个通知的时候,这里传入的Context实例都是不一样的。

  • ContentProvider——这同样也不是一个Context子类。但它内部持有一个Context实例,这个实例可以通过getContext()方法取得。如果ContentProvider与调用者是运行在同一个进程中,那么它的getContext()方法返回的Context实例其实就是这个进程里的始终单例的Application
    Context。不过如果ContentProvider与调用者是运行在不同的进程中的,如应用A去调用应用B的ContentProvider,那么这时候ContentProvider的getContext()方法返回的则是应用B里的Application
    Context。

Context 继承关系是怎么样的呢?

澳门威斯尼人平台登陆 1

Context类本身是一个纯abstract类。为了使用方便又定义了Context包装类-ContextWrapper,穿上了一身装备显得也比较强大,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中有attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象。

ContextThemeWrapper内部包含了与主题相关的接口,这里的主题就是指在AndroidManifest.xml中通过android:theme为Application或者Activity指定的主题。

只有Activity才需要主题,Service默默的后台工作者不需要穿的那么鲜艳,所以Service直接继承于ContextWrapper。

ContextImpl类真正实现了Context中所有的函数,真正的八块腹肌,我们所调用的各种Context类的方法其实实现均来自于该类。

引用解释2

每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。

http://godcoder.me/2016/04/16/context/
https://www.zhihu.com/question/26387327

这其实不仅是谷歌的麻烦,也是整个程序化广告购买的麻烦。

通过Context.getExternalFilesDir()方法可以获取到
SDCard/Android/data/你的应用的包名/files/
目录,一般放一些长时间保存的数据
澳门威斯尼人平台登陆,通过Context.getExternalCacheDir()方法可以获取到
SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据

引用的保存

呐,我们先来说说非常常见的一种保存Context实例的引用从而导致内存泄漏的情形:一个实例或一个类,它保存了一个生命周期比自己短的Context实例,这就会导致内存泄漏。举个例子,创建一个需要依赖一个Context实例的单例类来进行一些通用操作如加载资源、调用一个ContentProvider,并把当前Activity或者Service作为它依赖的Context实例设置进去。

public class CustomManager { private static CustomManager sInstance; public static CustomManager getInstance(Context context) { if (sInstance == null) { sInstance = new CustomManager; } return sInstance; } private Context mContext; private CustomManager(Context context) { mContext = context; }}

这段代码最大的问题是我们并不知道传入的Context参数是啥Context,所以对于我们这个单例来说直接保存这个Context的引用是很危险的(例如这里的Context是一个Activity或者Service的时候)。因为单例里面的对象是静态的,这就会导致它引用的所有资源都不会被系统GC回收掉,假设这里的Context是一个Activity的话,我们这样做就会导致这个Activity相关的View啊还有别的占内存的对象一直不能被系统回收掉,进而导致了内存泄漏。为了避免这种情况,我们在下面的单例中改为始终是保存Application
Context的引用。

public class CustomManager { private static CustomManager sInstance; public static CustomManager getInstance(Context context) { if (sInstance == null) { //不管什么Context,都改为取Application Context sInstance = new CustomManager(context.getApplicationContext; } return sInstance; } private Context mContext; private CustomManager(Context context) { mContext = context; }}

这样我们就不用关心传入的Context到底是什么了,因为我们现在持有的引用是Application
Context。就像前文提到的,Application
Context是在整个应用程序中进程单例的,所以哪怕我们在代码中对它持有静态引用也不会导致什么内存泄漏。那,为什么我们不能总是使用Application
Context来完成各处需要Context的逻辑呢?这样不就可以永不担心Context相关的内存泄漏了吗?原因其实很简单,就像我在一开头就提到的——一个Context实例并不一定能与另一个Context实例等同。

什么时候创建的Context?

每一个应用程序在客户端都是从ActivityThread类开始的,创建Context对象也是在该类中完成,具体创建ContextImpl类的地方一共有6处:

  • PackageInfo.makeApplication()
  • performLaunchActivity()
  • handleCreateBackupAgent()
  • handleCreateService()
  • handleBindApplication()
  • attach()

其中attach()方法仅在Framework进程启动时调用,应用程序运行时不会调用到该方法。

曾经,我们津津乐道于广告投放已从资源购买转变为受众购买,似乎一个更精准、更高ROI的营销时代已来临。然而,很快我们发现受众购买有个大问题:广告主不能亲眼所见自己的广告有没有、在哪向TA展示了。

如果使用上面的方法,当你的应用在被用户卸载后,SDCard/Android/data/你的应用的包名/
这个目录下的所有文件都会被删除,不会留下垃圾信息。
而且上面二个目录分别对应
设置->应用->应用详情里面的”清除数据“与”清除缓存“选项

不同种类的Context的能力区别

直接参考下表即可:

|Application | Activity | Service | ContentProvider |
BroadcastReceiver—|—|—|—|—|—构造展示一个Dialog | NO | YES |
NO | NO | NO启动一个Activity | NO1 | YES | NO1 | NO1 | NO1导入布局文件 |
NO2 | YES | NO2 | NO2 | NO2启动一个Service | YES | YES | YES | YES |
YES绑定到一个Service | YES | YES | YES | YES | NO发送一个广播 | YES |
YES | YES | YES | YES注册一个BroadcastReceiver | YES | YES | YES | YES |
NO3加载资源数值 | YES | YES | YES | YES | YES附注:

  1. 一个非Activity的Context可以用于启动一个Activity,但这样启动的Activity需要新创建一个Activity堆叠栈。这个在某些特定情形下或许会适用,但这种设计一般来说都不太好。
  2. 这个其实也是可以的,但是这样导入的布局会用当前系统的默认主题来设置,而不是用你在你的应用程序中设定的主题来设置的。
  3. 在Android
    4.2及以上的系统里,如果receiver是null,那这也是可以的。这样做是为了取得一个严格广播的当前值。

Application对应的Context

程序第一次启动时,会辗转调用到makeApplication()方法。具体代码如下:

ContextImpl appContext = new ContextImpl();
appContext.init(this,null,mActivityThread);
....
appContext.setOuterContext(app);

程序化购买时,大家担心自己的广告预算都消耗在了更low的长尾流量上,后来PDB(私有程序化购买)应运而生;但即使在优质媒体平台上,同样可能存在不适合品牌展示的媒体内容,就像现在谷歌碰到的麻烦。假如投放广告时不能对当前页的媒体内容进行甄别,或者甄别得不够细——比如识别出一则新闻与韩国相关,于是展示旅游广告,但其实该新闻是关于三胖阅兵的……求广告主心里的阴影面积。

如果要保存下载的内容,就不要放在以上目录下

用户交互界面

从上表可以看出好些操作不适合使用Application
Context来执行,而这些操作无一例外地全都是和用户交互界面直接相关的。适合执行这些与用户交互界面直接相关的操作的Context只有一种,那就是Activity;其他的Context其实和Application
Context的功能都差不多。不过其实这些个与UI相关的操作其实大多数时候都是在Activity中才会有执行的机会。假设使用一个非Activity的Context来调用展示一个Dialog,在调用Dialog实例的show()方法时就会报以下的错误直接崩溃:

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application

又或者使用一个非Activity的Context来启动另一个Activity,同样也会报错崩溃:

Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

但如果是使用一个非Activity的Context来导入布局,应用并不会报错崩溃。详细的流程可以参见我之前写的《布局的导入》。此时,Android框架会默默地返回你需要的布局文件对应的View,其中的各个View的层次关系都是正确正常的,只是你在应用程序中设定的主题和样式(在AndroidManifest.xml中设定的值)不会被应用到此时导入布局文件而产生的View中去,而是应用了系统默认的主题。这是因为在Manifest中定义的主题实际上是仅仅绑定到Activity这种Context上的,所以如果使用非Activity的Context实例来导入布局,那就只会应用系统默认的主题,从而导入了一个可能并不是你所期望的布局样式。

Activity对应的Context

启动Activity时,Ams会通过IPC调用到ActivityThread的scheduleLaunchActivity()方法,该方法包含两种参数。一种是ActivityInfo,这是一个实现了Parcelable接口的数据类,意味着该对象是Ams创建的,并通过IPC传递到ActivityThread;另一种是其他的一些参数。

scheduleLaunchActivity()方法中会根据以上两种参数构造一个本地ActivityRecord数据类,ActivityThread内部会为每一个Activity创建一个ActivityRecord对象,并使用这些数据对象来管理Activity。

然后会调用handleLaunchActivity(),再调用performLaunchActivity(),该方法中创建ContextImpl的代码如下:

ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo,r.token,this);
appContext.setOuterContext(activity);

在performLaunchActivity()开始执行时,会为r.packageInfo变量赋值。r.packageInfo对象的PackageInfo对象和Application对应的packageInfo对象是同一个。

澳门威斯尼人平台登陆 2

但上述规则是不是有不完善的地方?

有些同学在开发的时候会发现,依照目前的程序设计,我们的程序就是要长时间的持有一个Context实例,而且这个实例还必须是Activity,因为在这长时间的持有过程中,会涉及到UI相关的操作逻辑。那么假设真的有这种情况,我强烈建议你们重新审视你们的程序的设计,因为这种情形完全就是在对抗Android系统框架

Service对应的Context

启动Service时,Ams会通过IPC调用到ActivityThread的scheduleCreateService()方法,该方法也包含两种参数。第一种是ServiceInfo,这是实现了一个Parcelable接口的数据类,该对象由AmS创建,并通过IPC传递到ActivityThread内部;第二种是其他参数。

在scheduleCreateService()方法中,会使用以上两种参数构造一个CreateServiceData的数据对象,ActivityThread会为其所包含的每一个Service创建该数据对象,并通过这些对象来管理Service。

然后在执行handleCreateService()方法,创建ContextImpl对象代码如下:

ContextImpl appContext = new ContextImpl();
appContext.init(packageInfo,null,this);
...
appContext.setOuterContext(service);

Service对应的Context对象内部的mPackageInfo与Activity、Application中是完全相同的。

注:该图非真实截屏,纯属举个栗子

经验总结

在大多数情形下,代码是跑在哪类Context内就使用当前可获得的这类Context即可。只要这个Context类引用并不会超脱出它所引用的组件的生命周期,那你完全可以在你的逻辑代码中持有这个引用。但是如果你需要长时持有一个Context引用,这个引用甚至会超脱你的Activity或Service的生命周期,哪怕仅仅是短暂地超脱出生命周期,也务必要把这个Context引用改为Application引用。

这几个Context之间的关系

从以上可以看出,创建Context对象的过程基本上是相同的,不同的仅仅是针对Application、Activity、Service使用了不同的数据对象。

一个应用程序包含的Context个数应该为:Context个数 =
Service个数+Activity个数+1,最后的1是Application类本身也会对应一个Context对象。

应用程序中包含多个ContextImpl对象,而内部变量mPackageInfo却指向同一个PackageInfo对象,这种设计结构一般意味着ContextImpl是一种轻量级类,而PackageInfo是一个重量级类。事实上确实是这样,ContextImpl中的大多数进行包操作的重量级函数实际上都是转向了mPackageInfo对象相应的方法,也就是事实上调用了同一个PackageInfo对象。

如有问题请留言,转载请注明出处

备注:以上部分思想来自于《Android内核剖析》

澳门威斯尼人平台登陆 3

越来越多广告主意识到了context的重要性。

译者说两句

这段时间断更了抱歉。这篇文章虽然是2013年的老博文了,但在我看来还是非常有学习价值的。这是我第一次翻译技术类文章,所以可能表述得不太好,我日后会继续努力提升翻译水平的。依文中所说,在需要Context的时候,直接取能取到的“最近”的Context实例即可,一般情形下是不会导致内存泄漏的。举个例子,在一个Activity
A里有个Fragment a,然后Fragment a里面有Adapter
View,那这时候就需要透传Context实例来构造Adapter View里面的Item
View了,那这时候,其实大胆地在a里面透传A的引用到Adapter中其实是没有问题的,只要不要把持有的A的引用声明为静态就好。再比如,在后台有个定时任务或者什么的,在特定时机要往SharedPreferences里面写数据啊或者要读取资源文件中的string字符串啥的,这时候就可以在定时任务的代码中长期持有一个Application
Context的引用来执行相关的操作,这样也是不会引发内存泄漏的。

这两年,在营销中context这个词越来越多被提及。我们平时中英文夹杂时会被嘲为“4A腔”,但其实总有些词——比如这个context,要找个精准对应的中文译词还真不容易。

context字典里是“上下文”的意思;在营销中,大致可理解为“环境”。以前我们在制定媒介策略时,主要就是对渠道(channel)和点位(spot)的选择。Context一词,是在强调除了关注硬广位,其周围的环境也很重要。

如今媒介及广告形式越发复杂,这个词的含义也变得复杂。我所理解的context,包括了这四个层面:

1. 广告位置

即硬广时代起我们就关注的渠道与点位。媒体的流量及粘性,广告资源所在的位置及尺寸等等,直接决定了广告投在此处的效果。

如果你打算购买的是主流媒体的标准优质资源,比如门户网站首页的硬广位、视频网站拿出来单独售卖的某些热门节目的前贴片,平台级APP的开机报头……那么context的考量停留在这一层面也可以了;但如果打算购买的是媒体跨频道/节目打通售卖的内容页广告,那就还得考虑广告所在的内容环境。

2. 内容环境

媒体通过广告变现的本质是售卖用户关注,用户关注由媒体内容吸引而来。不同的内容会影响用户阅读/观看时的情绪,引发用户的思考与联想。正此时看到广告,受众可能会将这份情绪和联想与品牌联系起来。若将媒体内容与广告的这种“配合”,交由机器来判断,是否靠谱?从这次谷歌事件来看,貌似还有待改进。

除了广告安全,媒体内容太过吸引用户从而影响用户关注到广告也是个问题。我还在门户网站工作时,有回某频道要改版,认真参考了频道首页的热力关注图,新版的广告位就设置在关注度高的区域。但后来并没带来更好的效果。因为用户的关注度并非集中在左上或右上等固定区域,而只是循着内容的位置阅读,避开了广告位而已。

内容环境这一层context对广告效果的影响,不仅是投放阶段的挑战,在投放前的广告测试阶段亦然。传统的广告测试中,受访者被要求认真地观看广告,完了填答问卷。但在实际的媒体环境中,消费者不一定能“认真地观看广告”。广告主希望在更真实的媒介环境中开展广告测试,以获得更准确的结果。

有一次客户将在新闻客户端上投放信息流广告,需要在6个软文标题的备选方案中挑一个。假如只是通过问卷调研来测试,无助于判断这个标题是否会淹没在茫茫信息流中。后来我们通过DSP以这6个标题分别在新闻客户端上进行小额投放,了解其实际点击率高低。结果发现原先闭门brainstorming时被认为最好的标题,其真实点击率却是最低的。

澳门威斯尼人平台登陆 4

3. 广告环境

广告其实也是媒体内容的一部分。所以除了关注媒体平台上的图文、视频,与你家广告相邻的、或一起轮播的别人家的广告也是context的一部分。

曾经在门户时有回做竞品分析,碰到个有意思的例子。门户A和B各有自家的母婴频道,在用户市场中,由于B家母婴频道的论坛运营得不错,所以A在流量规模、用户活跃度等各项指标均不占优。但是在广告市场,A却更受广告主青睐,能开出更高的价格,获得更高的收入。后来我们在分析两个频道的广告收入数据时发现:A的广告环境明显好于B。

澳门威斯尼人平台登陆 5

A的广告收入主要来自于婴幼儿食品与护理用品行业,与频道内容相契合;而B的广告主则多而杂,行业覆盖数比A多一倍,且收入更多来自于服装、网络服务、IT数码甚至医疗广告。后来在与一些广告主的访谈中,果然也获得了对广告环境重视的反馈。

对广告环境的重视是双向的。很多媒体平台在销售广告或加入网盟时,也会对广告主的行业做严格的限制——比如大家普遍排斥某些医疗广告。

4. 生活场景

随着移动互联网普及、户外广告数字化,广告不一定要在我们专心地浏览/观看特定媒体内容时突兀地出现,而是可以在我们日常生活中作为及时有用的信息恰当好处地推送过来。生活场景是最复杂的一层context。但已被业界意识到其巨大价值,并屡屡有振奋人心的案例出现。

每到下雨天,地铁站出口处总有些人卖雨伞,这其实就是一种场景化营销。今天,通过识别你的位置,给你手机发送面前商场的购物优惠券;中午进写字楼电梯正和同事嘀咕吃啥时,楼宇电视中出现马路对面新开的餐厅广告……这些技术上实现已不成问题。

场景营销并不是指通过时间、地点去精准定向;而是在消费者产生需求的那个时间地点能够将信息传递给他/她。这对消费者洞察有很高的要求。所以广告主也很想验证这样的场景化营销方案是否确有奇效。怎样知道来我餐厅吃饭的顾客,是中午对面写字楼电梯里播放的广告带来的呢?营销效果评估要兼顾这一层context,挑战不小,我们现在也在携手客户与合作伙伴一起探索,获得了一些突破。

所以,我们营销中所说的context,大致是对营销环境的一个整体概念。具体来说,有广告位置、内容环境、广告环境和消费者生活场景四个层面。随着内容营销的兴起,未来内容环境与广告环境的边界会逐渐模糊,二者可能会作为媒体环境放在一块考量。

以前我们在做营销决策时,是要决定这3个C:consumer、creative和channel,分别是要回答who、what、where的问题;现在这3个C变成了:consumer、content和context,而其中context则是where
&
how的问题。从老3C到新3C,不论是广告主、代理公司还是我们第三方评估机构,都需在新的理念、方法、技术方面及时改变和创新。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图