刚刚写垂重跑马灯demo的时候,被报了一个 System services not available to Activities before onCreate() 错误

错误日志如下:

05-18 17:13:24.634 1598-1598/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.verticalmarquee.maomao.verticalmarqueedemo, PID: 1598
    java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.verticalmarquee.maomao.verticalmarqueedemo/com.verticalmarquee.maomao.verticalmarqueedemo.MainActivity}: java.lang.IllegalStateException: System services not available to Activities before onCreate()
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2354)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2503)
        at android.app.ActivityThread.-wrap11(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1353)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5529)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:745)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:635)
     Caused by: java.lang.IllegalStateException: System services not available to Activities before onCreate()
        at android.app.Activity.getSystemService(Activity.java:5293)
        at android.view.LayoutInflater.from(LayoutInflater.java:229)
        at com.verticalmarquee.maomao.verticalmarqueedemo.MainActivity.<init>(MainActivity.java:15)
        at java.lang.Class.newInstance(Native Method)
        at android.app.Instrumentation.newActivity(Instrumentation.java:1067)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2344)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2503) 
        at android.app.ActivityThread.-wrap11(ActivityThread.java) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1353) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:148) 
        at android.app.ActivityThread.main(ActivityThread.java:5529) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:745) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:635) 

我以为是自己在onCreate里面启动跑马灯动画的原因,但细想一下,不对啊,动画至少延时2秒之后执行,一个小demo,不至于启动这么慢吧?

最后发现我做了一个偷懒的操作。

因为跑马灯需要inflate很多个View,所以我将初始化LayoutInflater的操作放在了Activity的属性里面。


public class MainActivity extends AppCompatActivity {

    private VerticalMarqueeLayout marqueeRoot;
    private LayoutInflater mInflater = LayoutInflater.from(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    }
}

为什么作为属性就会导致应用挂掉了呢?

原因分析:

通过错误日志的堆栈信息顺藤摸瓜找到了Activity启动的位置。


@Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        ...

        final Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            if (!r.activity.mFinished && pendingActions != null) {
                pendingActions.setOldState(r.state);
                pendingActions.setRestoreInstanceState(true);
                pendingActions.setCallOnPostCreate(true);
            }
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManager.getService()
                        .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                                Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }

        return a;
    }

跟进去看performLaunchActivity(r, customIntent);的代码,发现activity实例化之后调用了activity.attach()方法。

/**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ...
        } catch (Exception e) {
            ...
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                ...
                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);

                ...

                r.activity = activity;
            }
            r.setState(ON_CREATE);

            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }

        return activity;
    }

我的报错位置就是在Activity实例化的时候。我们再看看LayoutInflater.from(this);里面的源码发现:它是通过Context获取到一个layout_inflater的Service。

    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

然而Activity的Context是在attach方法里面才绑定,在实例化Activity的过程中,Activity里面不包含可用的Context。

    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) {

        attachBaseContext(context);

    }

总结:

Activity初始化之后,如果activity不为null,回调用activity.attach(...)方法,attach方法里面的attachBaseContext(context)方法会将context实例与activity实例绑定在一起。

问题原因:

由于我在Activity初始化的时候写了LayoutInflater.from(this);而执行LayoutInflater.from(this);代码的时候传入的Context是不可用的。最后在查找Service的时候抛出了异常。

得到的教训:

  • 不是所有省空间的做法都是对的,不能打破原有的规律;
  • 源码要常翻翻,了解了原理才能更好的掌控代码;

(标签:Activity、LayoutInflater、Context)