You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用SkinManager单例getColor方法触发空指针异常原因排查

崩溃现场截图

SkinManager调用getColor触发NullPointerException排查

崩溃日志

Caused by: java.lang.NullPointerException: 
  at com.manager.loader.SkinManager.getColor (SkinManager.java:28)
  at base.util.ui.titlebar.BaseTitlebarFragmentActivity.getImmersiveColor (BaseTitlebarFragmentActivity.java:6)
  at base.util.ui.titlebar.BaseTitlebarFragmentActivity.onCreate (BaseTitlebarFragmentActivity.java)

关联代码片段

BaseTitlebarFragmentActivity 代码

public abstract class BaseTitlebarFragmentActivity extends FragmentActivity {

    public void onCreate(Bundle savedInstanceState) {
        setImmersive(getImmersiveColor());
        super.onCreate(savedInstanceState);
    }

    protected int getImmersiveColor(){
        return SkinManager.getInstance().getColor(R.color.v8_common_title_bg);
    }
}

SkinManager 核心代码

public class SkinManager implements ISkinLoader {

    private static final Object synchronizedLock = new Object();
    private static SkinManager instance;
    private Context context;
    private Resources mResources;
    private boolean isDefaultSkin;

    private SkinManager() { }

    public static SkinManager getInstance() {
        if (instance == null) {
            synchronized (synchronizedLock) {
                if (instance == null){
                    instance = new SkinManager();
                }
            }
        }
        return instance;
    }

    public void init(Context context) {
        this.context = context.getApplicationContext();
        // 其余初始化逻辑
    }

    public int getColor(int resId){
        int originColor;
        if(mResources == null || isDefaultSkin){
            try {
                originColor = context.getResources().getColor(resId);
                return originColor;
            } catch (Exception e) {
                // 空实现,吞掉所有异常
            }
        }

        int trueColor = 0;
        try{
            trueColor = mResources.getColor(resId);
        }catch(Exception e){
            originColor = context.getResources().getColor(resId);
            trueColor = originColor;
        }
        
        return trueColor;
    }
}

Application 初始化代码

public class App extends BaseApplication {

    public void onCreate() {
        super.onCreate();
        initSkins();
    }

    private void initSkins() {
        AttrFactory.addSupportAttr("iiv_background_color", new IconicsImageViewBgColorAttr());
        AttrFactory.addSupportAttr("iiv_color", new IconicsImageViewColorAttr());
        AttrFactory.addSupportAttr("matProg_barColor", new ProgressWheelBarColorAttr());
        AttrFactory.addSupportAttr("progressDrawable", new ProgressBarDrawableAttr());
        AttrFactory.addSupportAttr("indeterminateDrawable", new ProgressBar2DrawableAttr());
        AttrFactory.addSupportAttr("button", new CheckBoxButtonDrawableAttr());

        SkinManager.getInstance().init(this);
        SkinManager.getInstance().setUpSkinFile();
        if (!ProcessManager.isMainProcess(getContext())) {
            SkinManager.getInstance().registerProcessSkinLoadReceiver();
        }
    }
}

根因分析

崩溃直接原因是getColor方法中调用了null对象的方法,结合代码逻辑具体触发路径按概率排序如下:

  • 单例多线程安全缺陷:双检锁实现的单例没有给instance加volatile关键字修饰,JVM指令重排序会导致其他线程拿到半初始化的实例——实例对象已经分配内存地址赋值给instance,但context、mResources等成员变量还没完成赋值,此时调用getColor就会触发空指针。
  • Activity生命周期顺序错误:BaseTitlebarFragmentActivity的onCreate方法中,先执行了setImmersive(getImmersiveColor())业务逻辑,后调用super.onCreate(savedInstanceState),部分ROM的冷启动/进程重建场景下,会出现Activity启动和Application初始化时序错位,调用getColor时SkinManager还没执行init方法,成员变量都是null。
  • 空catch块吞异常导致错误扩散:第一个if分支中如果context为null,调用context.getResources()抛出的NPE会被空catch直接吞掉,代码不会中断,会继续向下执行到mResources.getColor(resId)行,此时mResources也为null,就会在这行抛出崩溃日志中记录的NPE,反而掩盖了最初的异常点。
  • 多进程逻辑错误:Android每个进程有独立的虚拟机,单例对象不跨进程共享。当前代码中仅对非主进程注册换肤广播,逻辑明显写反,非主进程场景下SkinManager很可能初始化不全。

修复方案

  1. 修正Activity生命周期调用顺序,onCreate必须第一行先调用super.onCreate,再执行业务逻辑:
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setImmersive(getImmersiveColor());
}
  1. 修复双检锁单例的线程安全问题,给静态实例加volatile修饰禁止指令重排序:
private static volatile SkinManager instance;
  1. 移除空catch块,在getColor方法入口增加兜底校验,避免初始化未完成时触发NPE:
public int getColor(int resId){
    // 初始化未完成时走全局默认兜底
    if (context == null) {
        return App.getInstance().getResources().getColor(resId);
    }
    if(mResources == null || isDefaultSkin){
        return context.getResources().getColor(resId);
    }
    try{
        return mResources.getColor(resId);
    }catch(Exception e){
        return context.getResources().getColor(resId);
    }
}
  1. 修正多进程判断逻辑,确保所有进程下SkinManager都完成初始化,换肤广播注册逻辑符合业务预期。
  2. 如果存在第三方SDK提前启动组件的场景,可以将SkinManager的初始化提前到ContentProvider中执行(执行时机早于Application onCreate),避免时序问题。

内容的提问来源于stack exchange,提问作者thecr0w

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 12:45:31