Android上下文Context

Android 上下文Context

源码基于Android8.0 API 26

1 Context的关联类

Android中,Activity可以直接new吗?

Activity mActivity = new Activity();

Android应用程序开发基于Java语言,Activity本质上也是一个对象,那么👆的写法有什么问题?Android应用程序不像Java应用程序那样,随便创建一个类,写一个main方法就可以运行,Android是基于组件的应用设计模式,组件的运行需要有一个完整的Android工程环境。 只有在这个环境下,ActivityServiceBroadcastReceiver等组件才可以正常工作。这个支持组件运行上下文环境就是Context 可以这样讲,Context是一个维持Android应用中各个组件能够正常工作的一个核心工程类。

如果想要在Android Studio中运行一个.java文件的main函数,需要在Project Root/.idea/gradle.xml的标签GradleProjectSettings添加:

<option name="delegatedBuild" value="false" />

在开发过程中,Context的使用场景总的来说可以分为两大类:

  • 使用Context调用方法,比如启动Activity、启动Service、发送广播、操作数据库、访问资源、调用系统服务等
  • 调用方法时传入Context,比如弹出Toast、创建Dialog

这些行为意味着需要访问系统。

那么Context是从哪来的呢?从AMSAMS是系统级进程,拥有访问系统的权利,应用程序的启动受AMS的调控,在程序启动的过程中,AMS会把一个“凭证”通过跨进程通信给应用程序,程序会把这个“凭证”封装程Context,并提供一系列的接口,这样我程序也就可以很方便的访问系统资源了。 这样做的好处是:系统可以对应用程序的操作进行调控,限制各种情境下的权限,同时也可以防止恶意攻击。

以下为Context的源码:

/**
 * Interface to global information about an application environment.  This is
 * an abstract class whose implementation is provided by
 * the Android system.  It
 * allows access to application-specific resources and classes, as well as
 * up-calls for application-level operations such as launching activities,
 * broadcasting and receiving intents, etc.
 *
 * 提供了关于应用程序环境的全局信息的接口。
 * 它是一个被Android系统支持的抽象类。它允许获取和应用程序相关的资源和类,包括应用级别操作,比如启动Activity、
 * 发送广播、接收Intent等操作
 */
public abstract class Context { }
1.1 ContextImplContextWrapper

既然Context是抽象类,在它的内部定义了很多方法以及静态常量,它的具体实现类为ContextImplContext相关联的类,除了ContextImpl,还有ContextWrapperContextThemeWrapperApplicationService等。

Context的关联类

从上图中看,ContextImplContextWrapper继承自Context

1.1.1 ContextImpl

ContextImpContext功能的实现类,应用程序中所调用的各种Context类的方法,其实现均来自该类。 因为外界需要使用并拓展ContextImpl的功能,因此设计上使用了装饰模式。

以下是ContextImpl的源码:

class ContextImpl extends Context { }
1.1.2 ContextWrapper

ContextWrapper是上下文功能的封装类,它对ContextImpl进行包装,主要起方法传递作用。ContextWrapper中几乎所有的方法都是调用ContextImpl来实现的,因此,在ContentWrapper的构造函数中包含一个真正的Context的引用——mBasemBase具体指向ContextImpl,同时ContextWrapper提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向给其所包含的真正的Context对象。

以下是ContextWrapper的源码:

public class ContextWrapper extends Context {
  @UnsupportedAppUsage
  Context mBase;
  
  public ContextWrapper(Context base) {
    mBase = base;
  }
  
  protected void attachBaseContext(Context base) {
    if (mBase != null) {
      throw new IllegalStateException("Base context already set");
    }
    mBase = base;
  }
  
  public Context getBaseContext() {
    return mBase;
  }
  
  @Override
  public Resources getResources() {
    return mBase.getResources();
  }
  
  @Override
  public ContentResolver getContentResolver() {
    return mBase.getContentResolver();
  }
  
  @Override
  public Looper getMainLooper() {
    return mBase.getMainLooper();
  }
  
  @Override
  public Context getApplicationContext() {
    return mBase.getApplicationContext();
  }
  
  @Override
  public String getPackageName() {
    return mBase.getPackageName();
  }
  
  @Override
  public void startActivity(Intent intent) {
    mBase.startActivity(intent);
  }
  
  @Override
  public void sendBroadcast(Intent intent) {
    mBase.sendBroadcast(intent);
  }
  
  @Override
  public Intent registerReceiver(
    BroadcastReceiver receiver, IntentFilter filter) {
    return mBase.registerReceiver(receiver, filter);
  }
  
  @Override
  public void unregisterReceiver(BroadcastReceiver receiver) {
    mBase.unregisterReceiver(receiver);
  }
  
  @Override
  public ComponentName startService(Intent service) {
    return mBase.startService(service);
  }
  
  @Override
  public boolean stopService(Intent name) {
    return mBase.stopService(name);
  }
  
  @Override
  public boolean bindService(Intent service, ServiceConnection conn, int flags) {
    return mBase.bindService(service, conn, flags);
  }
  
  @Override
  public void unbindService(ServiceConnection conn) {
    mBase.unbindService(conn);
  }  
}

其实,在ContextWrapper中的方法是非常多的,但是,实现都非常统一,就是调用了mBase对象中对应当前方法名的方法。

ContextWrapper有三个直接的子类,ContextThemeWrapperServiceApplication。其中,ContextThemeWrapper是一个带主题的封装类,它有一个直接子类就是Activity

ContextWrapper的继承关系

ContextThemeWrapper类,其内容包含了主题Theme相关的接口,这里所说的主题是指在AndroidManifest.xml中通过android:themeApplication元素或者Activity元素制定的主题。当然只有Activity才需要主题,而Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapperApplication同理。

Context的关联类采用了装饰模式,主要有以下优点:

  • 使用者(比如Service)能够更方便的使用Context
  • 如果ContextImpl发生了变化,它的装饰类ContextWrapper不需要做任何修改
  • ContextImpl的实现不会暴露给使用者,使用者也不必关系ContextImpl的实现
  • 通过组合而非继承的方式,拓展ContextImpl的功能,在运行时选择不同的装饰类,实现不同的功能

总结:Context的两个子类分工明确,其中ContextImplContext的具体实现类,ContextWrapperContext的包装类。ActivityApplicationService虽然都继承自ContentWrapperActivity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象mBase,由ContextImpl实现Context中的方法。

由此可知,Context一共有三种类型,分别是ApplicationActivityService。这三个类分别各自承担了不同的作用,而具体的功能则是由ContextImpl类去实现的。

因此在大多数的场景下,ActivityServiceApplication这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全因素的考虑,Android是不允许ActivityDialog凭空出现的,一个Activity的启动必须建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,只能使用Activity类型的Context,否则将会出错。

Activity常用于与UI有关的操作,如添加Window,常规使用可以直接用Activity.thisService可以和Activity一样直接使用Service.this来使用Context,和Activity不同的是,Service没有界面,也不需要主题。ContentProvider使用的是ApplicationContextBroadcast使用ActivityContext

1.2 Context的数量

那么一个应用程序中到底有多少个Context呢?ContextApplicationActivityService三种类型,因此一个应用程序中Context数量的计算公式可以这样写:Context数量 = Activity数量 + Service数量 + 11代表Application的数量, 因为一个应用程序可以有多个Activity和多个Service,但只能有一个Application

那么在四大组件中,为什么只有ActivityService持有Context那么Broadcast ReceiverContent Provider并不是Context的子类,它们所持有的Context都是其他地方传过去的,所以并不计入Context总数。

1.3 Context的作用域

Context的作用域还是有一些限制的。由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数的场景下,ActivityServiceApplication这三种类型的Context都是可以通用的。不过有几种场景比价特殊,比如启动Activity、还有弹出Dialog出于安全原因的考虑,Android是不允许Activity或者Dialog凭空出现的,一个Activity的启动必须建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,只能用Activity类型的Context,否则将会出错。

Context作用域

从上图中可以看出Activity所持有的Context的作用域最广,因为Activity继承自ContextThemeWrapper,而ApplicationService继承自ContentWrapper,很显然ContextThemeWrapperContextWrapper的基础上又做了一些操作使得Activity变得更加强大。

2 Application Context

2.1 Application Context的创建过程

在一个应用程序启动完成后,应用程序就会有一个全局的Application Context。以下是Application Context的创建过程的时序图:

Application Context 创建过程时序图

ActivityThread类作为应用程序进程的主线程管理类,它会调用它的内部类ApplicationThreadscheduleLaunchActivity方法来启动Activity

scheduleLaunchActivity方法中向H类发送LAUNCH_ACTIVITY类型的消息,目的是将启动Activity的逻辑放在主线程的消息队列中,这样启动Activity的逻辑会在主线程中执行。

H继承自Handler,是ActivityThread的内部类。在H类的handleMessage方法对LAUNCH_ACTIVITY类型的消息的处理。

Android应用启动流程

2.2 Application Context的获取

一个应用程序启动完成后,就会有一个全局的Application Context。通过getApplicationContext()方法可以获得应用程序全局的Application ContextgetApplicationContext()方法在ContextWrapper中实现,如下所示:

@Override
public Context getApplicationContext() {
  return mBase.getApplicationContext();
}

mBase指的是ContextImplContextImplgetApplicationContext()方法:

@Override
public Context getApplicationContext() {
  return (mPackageInfo != null) ?
    mPackageInfo.getApplication() : mMainThread.getApplication();
}

如果LoadApk类型的mPackageInfo不为null,则调用LoadedApkgetApplication方法,否则调用ActivityThreadgetApplication方法。由于应用程序这时已经启动,因此LoadedApk不会为null,则会调用LoadedApkgetApplication方法,如下所示:

Application getApplication() {
  return mApplication;
}

这样就可以通过getApplicationContext()方法获取到Application Context

那么,getApplication()getApplicationContext()方法获得的是否是同一个对象呢?

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    MyApplication myApp = (MyApplication) getApplication();
    Log.d("TAG", "getApplication is " + myApp);
    Context appContext = getApplicationContext();
    Log.d("TAG", "getApplicationContext is " + appContext);
  }  
}

// getApplication is com.cah.androidtest.MyApplication@fafb347
// getApplicationContext is com.cah.androidtest.MyApplication@fafb347

// kotlin
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    Log.e("CAH", "onCreate: $application")
    Log.e("CAH", "onCreate: $applicationContext")
}

// onCreate: android.app.Application@a95ffff
// onCreate: android.app.Application@a95ffff

打印的内容是一样的,内存地址也是相同的,是同一个对象。 其实这个结果很好理解,Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是MyApplication本身的实例。

那么,既然这两个方法得到的结果都是相同的,为什么Android还要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,是用来获取Application实例的,但是这个方法只有在ActivityService中才能调用的。如果在一些其他的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了,如下所示:

public class MyReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    MyApplication myApp = (MyApplication) context.getApplicationContext();
    Log.d("TAG", "myApp is " + myApp);
  }
}

也就是说,getApplicationContext()方法的作用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法都可以拿到Application对象。

getBaseContext()

除了getApplicationContext()getApplication(),还有一个getBaseContext()方法,以下为getBaseContext()的源码:

// ContentWrapper.java
public class ContextWrapper extends Context {
  @UnsupportedAppUsage
  Context mBase;
  
  protected void attachBaseContext(Context base) {
    if (mBase != null) {
      throw new IllegalStateException("Base context already set");
    }
    mBase = base;
  }

  public Context getBaseContext() {
    return mBase;
  }
}

打印一下这三个方法的获得的值:

Context baseContext = getBaseContext();
Log.d("TAG", "getBaseContext is " + baseContext);

// getApplication is com.cah.androidtest.MyApplication@fafb347
// getApplicationContext is com.cah.androidtest.MyApplication@fafb347
// getBaseContext is android.app.ContextImpl@6592774

这次得到不同的对象,getBaseContext()方法得到的是一个ContextImpl对象。ContextImpl正是上下文功能的实现类。也就是说像ApplicationActivityService这样的类其实并不会去具体实现Context的功能,而仅仅是做了一层接口封装而已,Context的具体功能都是由ContextImpl类去完成的。

ContextWrapper中的方法还是非常多的,但是实现都非常统一,就是调用了mBase对象中对应当前方法名的方法。

那么这个mBase对象又是什么呢?在attachBaseContext()方法中,这个方法传入一个base参数,并把这个参数赋值给了mBase对象。而attachBaseContext()方法其实是由系统来调用的,它会把ContextImpl对象作为参数传递到attachBaseContext()方法当中,从而赋值给mBase对象,之后ContextWrapper中的所有方法其实都是通过这种委托的机制交由ContextImpl去具体实现的,所有ContextImpl是上下文功能的实现类是非常准确的。

getBaseContext()方法中,返回了mBase对象,而mBase对象其实就是ContextImpl对象。

3 Activity Context

ActivityContext会在Activity的启动过程中被创建,以下是ActivityContext创建过程的时序图:

Activity的Context创建过程的时序图

ActivityThread是应用程序进程的主线程管理类,它的内部类ApplicationThread会调用scheduleLaunchActivity方法来启动ActivityscheduleLaunchActivity方法如下所示:

private class ActivityThread extends IApplicationThread.Stub {
  
  final H mH = new H();
  
  private void sendMessage(int what, Object obj) {
    	sendMessage(what, obj, 0, 0, false);
  }
  
  private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
      Message msg = Message.obtain();
      msg.what = what;
      msg.obj = obj;
      msg.arg1 = arg1;
      msg.arg2 = arg2;
      if (async) {
        msg.setAsynchronous(true);
      }
      mH.sendMessage(msg);
  }
  
  private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, 
                                    String reason){
    	Activity a = performLaunchActivity(r, customIntent);
  }
  
  private class ApplicationThread extends IApplicationThread.Stub {
      @Override
      public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
              ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
              CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
              int procState, Bundle state, PersistableBundle persistentState,
              List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
              boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
        updateProcessState(procState, false);
        ActivityClientRecord r = new ActivityClientRecord();
        r.token = token;
        r.ident = ident;
        r.intent = intent;
        r.referrer = referrer;
        // ...

        sendMessage(H.LAUNCH_ACTIVITY, r);
      }
  }
  
   private class H extends Handler {
       public static final int LAUNCH_ACTIVITY         = 100;
       public void handleMessage(Message msg) {
           switch (msg.what) {
             case LAUNCH_ACTIVITY: {
               Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
               final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
               r.packageInfo = getPackageInfoNoCheck(
                 r.activityInfo.applicationInfo, r.compatInfo);
               handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
               Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
             } break;
           }
       }
   }
  
}

scheduleLaunchActivity方法将启动Activity的参数封装称为 ActivityClientRecordsendMessage方法向H类发送类型为LAUNCH_ACTIVITY的消息,并将ActivityClientRecord传递过去。sendMessage方法的目的是将启动Acitivty的逻辑放在主线程的消息队列中,这样,启动Activity的逻辑就会在主线程中执行。

H类的handleMessage方法会对LAUNCH_ACTIVITY类型的消息进行处理,其中调用了ActivityThreadhandleLaunchActivity方法,而在handleLaunchActivity方法中又调用了ActivityThreadperformLaunchActivity方法。以下是performLaunchActivty的源码:

private class ActivityThread extends IApplicationThread.Stub {
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 1. 创建Activity的ContextImpl
      	ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
          	// 2. 创建Activity的实例
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            
        }

        try {

            if (activity != null) {
                // 3. 将此前创建的Activity实例赋值给ContextImpl的成员变量mOuterContext,这样ContextImpl也可以访问Activity的变量和方法
                appContext.setOuterContext(activity);
                // 4. 将ContextImpl传入activity的attach方法
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

         
                if (r.isPersistable()) { 
                   // 5. mInstrumentation.callActivityOnCreate调用Activity的onCreate方法
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
            }
            r.paused = true;

            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            
        }

        return activity;
    }
}

注释4处是Activity.attach方法的源码:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback, WindowControllerCallback,
        AutofillManager.AutofillClient {
         
    final void attach(Context context, ActivityThread aThread,
                    Instrumentation instr, IBinder token, int ident,
                    Application application, Intent intent, ActivityInfo info,
                    CharSequence title, Activity parent, String id,
                    NonConfigurationInstances lastNonConfigurationInstances,
                    Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                    Window window, ActivityConfigCallback activityConfigCallback) {
        // 1. attachBaseContext方法在ContextThemeWrapper中实现
      	attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
				// 2. 创建PhoneWindow,代表程序窗口。PhoneWindow在运行期间会间接触发好多事件,比如点击、菜单弹出、屏幕焦点变化等事件,这些事件都需要转发给PhoneWindow关联的Activity,转发通过Window.callback接口实现,Activity实现了这个接口
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
      	// 3. 将当前的Activity通过Window的setCallback方法传递给PhoneWindow
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);

      	// 4. 为PhoneWindow设置WindowManager
        mWindow.setWindowManager(
          (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
          mToken, mComponent.flattenToString(),
          (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
          mWindow.setContainer(mParent.getWindow());
        }
        // 5. 获取WindowManager并赋予给Activity的成员变量mWindowManager,这样在Activity中就可以通过getWindowManager方法来获取WindowManager
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
        mWindow.setColorMode(info.colorMode);
  }         
}

以下是ContextThemeWrapper.attachBaseContext方法的源码:

public class ContextThemeWrapper extends ContextWrapper {
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }
}

以下是ContextThemeWrapper父类attachBaseContextattachBaseContext方法的源码:

public class ContextWrapper extends Context {
    Context mBase;
    protected void attachBaseContext(Context base) {
      if (mBase != null) {
        throw new IllegalStateException("Base context already set");
      }
      // 一路传递过来的Activity的ContentImpl,将它赋值给ContextWrapper的成员变量mBase
      mBase = base;
  	}
  
  @Override
  public Resources.Theme getTheme() {
    return mBase.getTheme();
  }
}

ContextWrapper的功能就可以交给由ContextImpl来处理。比如:调用ContextWrappergetTheme方法实际上是调用ContextImplgetTheme方法。

总结: 在启动Activity的过程中创建ContextImpl,并赋值给ContextWrapper的成员变量mBaseActivity继承自ContextWrapper的子类ContextThemeWrapper,这样,在Activity中就可以使用Context定义的方法了。

4 Service Context

ServiceContext也是在Service的启动过程中被创建,和ActivityContext创建过程类似,以下是Service的创建时序图:

Service的Context的时序图

Service启动流程

ActivityThread的内部类ApplicationThread会调用scheduleCreateService方法启动Service,以下是scheduleCreateService的源码:

public final class ActivityThread {
  
  final H mH = new H();
  
  private void sendMessage(int what, Object obj) {
    sendMessage(what, obj, 0, 0, false);
  }
  
  private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
    Message msg = Message.obtain();
    msg.what = what;
    msg.obj = obj;
    msg.arg1 = arg1;
    msg.arg2 = arg2;
    if (async) {
      msg.setAsynchronous(true);
    }
    mH.sendMessage(msg);
  }
  
  private void handleCreateService(CreateServiceData data) {
    try {
      // 1. 通过ContextImpl的createAppContext方法创建ContextImpl,并将该ContextImpl传入注释2处service的attach方法中
      ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
      context.setOuterContext(service);

      Application app = packageInfo.makeApplication(false, mInstrumentation);
      // 2. 
      service.attach(context, this, data.info.name, data.token, app,
                     ActivityManager.getService());
      service.onCreate();
      mServices.put(data.token, service);
      try {
        ActivityManager.getService().serviceDoneExecuting(
          data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
      } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
      }
    } catch (Exception e) {
    }
  }

  
  private class ApplicationThread extends IApplicationThread.Stub {
    
    public final void scheduleCreateService(IBinder token,
                ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
            updateProcessState(processState, false);
            CreateServiceData s = new CreateServiceData();
            s.token = token;
            s.info = info;
            s.compatInfo = compatInfo;

            sendMessage(H.CREATE_SERVICE, s);
        }
  }
  
  private class H extends Handler {
    public static final int CREATE_SERVICE          = 114;
    public void handleMessage(Message msg) {        
      switch (msg.what) { 
        case CREATE_SERVICE:
          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
          handleCreateService((CreateServiceData)msg.obj);
          Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
          break;
      }
    } 
  }  
}

sendMessage方法向H类发送CREATE_SERVICE类型的消息,H类的handleMessage方法会对CREATE_SERVICE类型的消息进行处理,其中调用了ActivityThreadhandleCreateService方法。

注释2处serviceattach方法:

public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
  public final void attach(Context context, ActivityThread thread, String className, 
                           IBinder token, Application application, Object activityManager) {
    attachBaseContext(context); // 1
    mThread = thread;           // NOTE:  unused - remove?
    mClassName = className;
    mToken = token;
    mApplication = application;
    mActivityManager = (IActivityManager)activityManager;
    mStartCompatibility = getApplicationInfo().targetSdkVersion
      < Build.VERSION_CODES.ECLAIR;
  }
}

注释1处调用了ContextWrapperattachBaseContext方法:

public class ContextWrapper extends Context {
  Context mBase;
  protected void attachBaseContext(Context base) {
    if (mBase != null) {
      throw new IllegalStateException("Base context already set");
    }
    mBase = base;
  }
}

这样base一路传递过来的是ContextImpl,将ContextImpl赋值给ContextWrapperContext类型的成员mBase,这样在ContextWrapper中就可以使用Context方法,而Service继承自ContextWrapper,同样可以使用Context的方法。

5 其他

5.1 Application的设计

在开发过程中,基本上每一个应用程序都会有自己的一个Application,并让它继承系统的Application类,然后在自己的Application类中去封装一些通用的操作。其实这并不是Google所推荐的一种做法,因为这样只能把Application当成一个通用工具类来使用,而实际上使用一个简单的单例也可以是想同样的功能。但是,也有太多的项目都是这样使用Application的,当然,这种做法并没有什么副作用。

以下对Application设计进行分析:

首先新建一个MyApplication并让它继承自Application,然后在AndroidManifest.xml文件中对MyApplication进行指定, 如下所示:

<application
     android:name=".MyApplication"
     android:allowBackup="true"
     android:icon="@drawable/ic_launcher"
     android:theme="@style/AppTheme">
  
</application>

指定完成后,当程序启动时Android系统会创建一个MyApplication的实例,如果这里不指定的话就会默认创建一个Application的实例。

有很多的Application都是被当作通用工具类来使用的,那么既然作为一个通用工具类,要怎样才能获取到它的实例呢?——只需要调用getApplication()方法就能拿到我们自定义的Application的实例了。

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    MyApplication myApp = (MyApplication) getApplication();
    Log.d("TAG", "getApplication is" + myApp);
  }    
}
5.2 使用Application的问题

虽说Application的用法确实比较简单,但是我们平时的开发工作当中也着实存在着不少Application误用场景。

ApplicationContext的其中一种类型,那么是否就意味着,只要是Application的实例,就能随时使用Context的各种方法呢?

public class MyApplication extends Application {

    public MyApplication() {
        String packageName = getPackageName();
        Log.e("TAG", "package name is : " + packageName);
    }

}

这是一个非常简单的自定义Application,我们在MyApplication的构造方法中获取了当前应用程序的包名,并打印欻里,获取包名使用了getPackageName()方法,这个方法就是由Context提供的,那么上面的代码能正常运行吗?

Application获取包名

应用程序一启动就立刻崩溃了,报了一个空指针异常。如果把代码改成以下写法:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        String packageName = getPackageName();
        Log.e("TAG", "package name is : " + packageName);
    }
}
//  package name is : com.cah.androidtest

在构造方法中调用Context的方法就会崩溃,在onCreate()方法中调用Context的方法就一切正常,那么这两个方法之间到底发生了什么事情呢?我们从新回顾一个ContextWrapper类的源码,ContextWrapper中有一个attachBaseContext()方法,这个方法将会掺入一个Context参数赋值给mBase对象,之后mBase对象就有值了。而我们又知道,所有Context的方法都是调用Context中的任何一个方法时,就会出现空指针异常,上面的代码就是这种情况。Application中方法的执行顺序如下图所示:

Application方法执行顺序

Application中在onCreate()方法里去初始化各种全局的变量数据是一种比较推荐的做法,但是如果你想把初始化的时间点提前到极致,也可以去重写attachBaseContext(),如下所示:

public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        // 在这里,调用Context方法会崩溃
        super.attachBaseContext(base);
        // 在这里,可以正常调用Context方法
    }
}

以上是我们平时在使用Application时需要注意的一个点,下面来介绍另外一种非常普遍的Application误用情况。

其实Android官方并不太推荐我们使用自定义的Application,基本上只有需要做一些全局初始化的时候可能才需要用到自定义的Application,官方描述如下:

There is normally no need to subclass Application. In most situation, static singletons can provide the same functionality in a more modular way. If your singleton needs a global context (for example to register broadcast receivers), the function to retrieve it can be given a Context which internally uses Context.getApplicationContext() when first constructing the singleton.

但是,就观察而言,现在自定义的Application的使用情况基本上可以达到100%了,也就是我们平时自己写测试Demo的时候可能不会使用,正式的项目几乎全部都会使用自定义的Application。可使用归使用,有不少的项目对自定义Application的用法不到位,正如官方文档中所描述的一样,多数项目只是把自定义Application当成了一个通用工具类,而这个功能并不需要借助Application来是想,使用单例可能是一种更加标准的方式。

不过自定义Application也并没有什么副作用,它和单例模式二选一都可以实现同样的功能,有的项目也会把自定义Application和单例模式混合到一起使用,这就过分了,以下是代码:

public class MyApplication extends Application {

    public static MyApplication app;

    public static MyApplication getInstance() {
        if (app == null) {
            app = new MyApplication();
        }
        return app;
    }
}

就像单例模式一样,这里提供了一个getIntance()方法,用于获取MyApplication的实例,有了这个实例之后,就可以调用MyApplication中的各种工具方法了。

但是这种写法是不对的,因为我们知道Application是属于系统组件,系统组件大的实例就是要由系统去创建,如果这里我们自己new一个MyApplication的实例,它就只是一个普通的Java对象而已,而不具备任何Context的能力,有很多人反馈使用LitePal时发生了空指针错误其实就是这个原因,因为你提供给LitePal的只是一个普通的Java对象,它无法通过这个对象来进行Context操作。

那么如果真的想要提供一个获取MyApplication实例的方法,比较标准的写法又是什么样的呢?其实这里我们只需要谨记一点,Application全局只有一个,它本身就已经是单例了,无需在用单例模式去为它做多重实例保护了,代码如下:

public class MyApplication extends Application {

    public static MyApplication app;

    public static MyApplication getInstance() {
        return app;
    }

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

getInstance()方法可以照常提供,但是里面不需要做任何逻辑判断,直接返回app对象就可以了,而app对象又是什么呢?在onCreate方法中我们将app对象赋值成thisthis就是当前Application的实例,那么app也就是当前Application的实例了。

5.3 ApplicationService所不推荐的两种使用情况
  • 如果用ApplicationContext去启动一个LaunchModestandardAcitivity的时候会报错android.util.AndroidRuntimeException:Calling startActivity from outside of an Activity context require the FLAG_ACTIVITY_NEW_TAG falg. Is this really what you want?这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。 解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,ServiceApplication
  • ApplicationService中去layout inflate也是合法的,但是会使系统默认的主题样式,如果你自定义了某些样式可能不会被使用,所以这种方式也不推荐使用。

一句话总结:凡是跟UI相关的 ,都应该使用Acitivty做为Context来处理,其他的一些操作,ServiceActivityApplication等实例都可以,注意Context引用的持有,防止内存泄漏。

如何获取Context

通常我们想要获取Context对象,主要有以下四种方法:

  1. View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象
  2. Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局进程Context
  3. ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用不多,也不建议使用
  4. Activity.this返回当前Activity实例,如果UI控件不需要使用Activity作为Context对象,但是默认Toast实际上使用ApplicationContext也可以
5.4 Context引起的内存泄漏

使用Context最重要的问题之一是注意内存泄漏。 不同的Context的生命周期不同,Application是在应用存在的期间一直存在,而Activity是会随着界面的销毁而销毁,如果我们的代码长时间持有ActivityContext,如静态引用或者单例类,那么会导致Activity无法释放,

5.4.1 静态引用

如下面的代码:

object MyClass {
  lateinit var mContext: Context
  fun showToast(context: Context) {
    mContext = context
  }
}

单例类在应用持续的时间内会一直存在,这样Context也会被一直持有,Activity无法被回收,导致内存泄漏。

那,都换成Application不就可以了,如下:

object MyClass {
  lateinit var mContext: Context
  fun showToast(context: Context) {
    mContext = context.getApplicationContext
  }
}

不可以。什么时候可以使用Application,不涉及UI以及启动Activity操作ActivityContext是拥有主题属性的,如果使用Application来操作UI,那么会丢失自定义的主题,采用系统默认的主题。同时,有些UI操作只有Activity可以执行,如弹出Dialog,这涉及到Windowtoken问题。这也是官方对Context不同权限的设计,没有界面的Context,就不应该有操作界面的权利。使用Application启动的Activity必须制定task以及标记singleTask,因为Application是没有任务栈的,需要重新开一个新的任务栈。因此,根据不同Context的职责来执行不同的任务。

5.4.2 错误的单例模式
public class Singleton {
  private static Singleton instance;
  private Context context;
  private Singleton(Context context) {
    this.mContext = context;
  }
  
  public static Singleton getInstance (Context context) {
    if(instance == null) {
      instance = new Singleton(context);
    }
    return instance;
  }
}

这是一个非线程安全的单例模式,instance作为静态对象,其声明周期要长于普通的对象,其中也包含Activity,假如Activity AgetInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并且一致持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。

5.4.3 View持有Acitivty引用
public class MainActivity extends Activity {
  private static Drawable mDrawable;
  
  @Override
  protected void onCreate(Bundle saveInstanceState) {
    super.onCreate(saveInstanceState);
    setContentView(R.layout.activity_main);
    ImageView iv = new ImageView(this);
    mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
    iv.setImageDrawable(mDrawable);
  }
}

用一个静态Drawable对象当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入的thisMainActivitymContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。

参考

https://zhuanlan.zhihu.com/p/24847247
https://blog.csdn.net/yanbober/article/details/45967639
https://juejin.cn/post/6887499574383116302/
https://blog.csdn.net/hfy8971613/article/details/107201238/

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值