java/android中单例模式详解

单例模式介绍

一、什么是单例模式?

1.1 最通俗的理解

想象你家里的遥控器

  • 客厅里只有1个电视遥控器
  • 全家人都在用这同一个遥控器
  • 不需要每个人自己买个遥控器

单例模式 = 一个类只能创建一个对象 + 所有人都用这个对象

1.2 为什么要用单例模式?

问题场景

java 复制代码
// 没有单例模式的问题
DatabaseManager db1 = new DatabaseManager();  // 连接数据库1
DatabaseManager db2 = new DatabaseManager();  // 连接数据库2
DatabaseManager db3 = new DatabaseManager();  // 连接数据库3
// 内存浪费!而且3个连接可能造成数据混乱

解决方案

java 复制代码
// 使用单例模式
DatabaseManager db1 = DatabaseManager.getInstance();  // 获取唯一实例
DatabaseManager db2 = DatabaseManager.getInstance();  // 还是同一个
// db1 和 db2 是同一个对象,节省内存,数据一致

二、单例模式的3个核心要素

java 复制代码
public class Singleton {
    // 1. 私有静态变量:保存唯一实例
    private static Singleton instance;
    
    // 2. 私有构造函数:防止外部 new
    private Singleton() {
        // 初始化代码
    }
    
    // 3. 公共静态方法:提供全局访问点
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

三、Java中的6种单例写法(从简单到完善)

写法1:饿汉式(最简单,最常用)

java 复制代码
public class DatabaseManager {
    // 类加载时就创建,线程安全
    private static final DatabaseManager instance = new DatabaseManager();
    
    private DatabaseManager() {
        // 初始化数据库连接
        System.out.println("连接数据库");
    }
    
    public static DatabaseManager getInstance() {
        return instance;
    }
    
    public void query(String sql) {
        System.out.println("执行查询:" + sql);
    }
}

// 使用
DatabaseManager db = DatabaseManager.getInstance();
db.query("SELECT * FROM users");

优点 :简单、线程安全
缺点:即使不用也会创建,浪费内存

如果写起来有点复杂,那就简化

java 复制代码
// 饿汉式(最简单!)
public class UserManager {
    // 就这一行!
    public static final UserManager INSTANCE = new UserManager();
    
    private UserManager() {}  // 私有构造
    
    public void doSomething() {}
}

// 使用
UserManager.INSTANCE.doSomething();

写法2:懒汉式(延迟加载)

java 复制代码
public class ConfigManager {
    private static ConfigManager instance;
    
    private ConfigManager() {
        System.out.println("加载配置文件");
    }
    
    public static ConfigManager getInstance() {
        if (instance == null) {  // 第一次调用时才创建
            instance = new ConfigManager();
        }
        return instance;
    }
}

// 使用
ConfigManager config = ConfigManager.getInstance();  // 这时才真正创建

优点 :需要时才创建,节省内存
缺点:多线程环境下可能创建多个实例(新手可以暂时忽略)

写法3:双重检查锁(最推荐,兼顾性能和线程安全)

java 复制代码
public class UserManager {
    // volatile 关键字:防止指令重排序
    private static volatile UserManager instance;
    
    private UserManager() {
        System.out.println("初始化用户管理器");
    }
    
    public static UserManager getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (UserManager.class) {  // 加锁
                if (instance == null) {  // 第二次检查
                    instance = new UserManager();
                }
            }
        }
        return instance;
    }
    
    private String currentUser;
    
    public void setCurrentUser(String name) {
        this.currentUser = name;
    }
    
    public String getCurrentUser() {
        return currentUser;
    }
}

这是Android中最推荐的写法!

写法4:静态内部类(优雅的写法)

java 复制代码
public class ImageLoader {
    // 静态内部类负责创建实例
    private static class Holder {
        private static final ImageLoader INSTANCE = new ImageLoader();
    }
    
    private ImageLoader() {
        System.out.println("初始化图片加载器");
    }
    
    public static ImageLoader getInstance() {
        return Holder.INSTANCE;
    }
    
    public void loadImage(String url) {
        System.out.println("加载图片:" + url);
    }
}

写法5:枚举(最安全,但Android中少用)

java 复制代码
public enum NetworkManager {
    INSTANCE;  // 唯一实例
    
    public void connect() {
        System.out.println("连接网络");
    }
}

// 使用
NetworkManager.INSTANCE.connect();

写法6:Kotlin的object(Android现代开发推荐)

kotlin 复制代码
// Kotlin 中一行代码搞定
object UserDataManager {
    var userName: String = ""
    var userAge: Int = 0
    
    fun saveUser(name: String, age: Int) {
        userName = name
        userAge = age
    }
}

// 使用
UserDataManager.saveUser("张三", 25)
println(UserDataManager.userName)

四、Android中的实际应用案例

案例1:全局用户信息管理

java 复制代码
public class UserSession {
    private static volatile UserSession instance;
    
    // 用户数据
    private String userId;
    private String userName;
    private boolean isLoggedIn;
    
    private UserSession() {}
    
    public static UserSession getInstance() {
        if (instance == null) {
            synchronized (UserSession.class) {
                if (instance == null) {
                    instance = new UserSession();
                }
            }
        }
        return instance;
    }
    
    // 登录
    public void login(String userId, String userName) {
        this.userId = userId;
        this.userName = userName;
        this.isLoggedIn = true;
    }
    
    // 退出
    public void logout() {
        this.userId = null;
        this.userName = null;
        this.isLoggedIn = false;
    }
    
    // 获取用户信息
    public String getUserName() {
        return userName;
    }
    
    public boolean isLoggedIn() {
        return isLoggedIn;
    }
}

// 在Activity中使用
public class LoginActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 登录成功后保存用户信息
        UserSession.getInstance().login("12345", "张三");
        
        // 跳转到主页
        startActivity(new Intent(this, MainActivity.class));
    }
}

// 在另一个Activity中获取
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 获取当前登录用户
        String userName = UserSession.getInstance().getUserName();
        TextView tvName = findViewById(R.id.tv_name);
        tvName.setText("欢迎:" + userName);
    }
}

案例2:网络请求管理器

java 复制代码
public class OkHttpManager {
    private static volatile OkHttpManager instance;
    private OkHttpClient okHttpClient;
    
    private OkHttpManager() {
        // 初始化 OkHttpClient
        okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build();
    }
    
    public static OkHttpManager getInstance() {
        if (instance == null) {
            synchronized (OkHttpManager.class) {
                if (instance == null) {
                    instance = new OkHttpManager();
                }
            }
        }
        return instance;
    }
    
    public void get(String url, Callback callback) {
        Request request = new Request.Builder()
            .url(url)
            .build();
        okHttpClient.newCall(request).enqueue(callback);
    }
}

// 使用
OkHttpManager.getInstance().get("https://api.example.com/data", new Callback() {
    @Override
    public void onResponse(Call call, Response response) {
        // 处理响应
    }
    
    @Override
    public void onFailure(Call call, IOException e) {
        // 处理错误
    }
});

案例3:SharedPreferences工具类

java 复制代码
public class SPManager {
    private static volatile SPManager instance;
    private SharedPreferences sp;
    private SharedPreferences.Editor editor;
    
    private SPManager(Context context) {
        // 使用ApplicationContext避免内存泄漏
        sp = context.getApplicationContext()
            .getSharedPreferences("my_app", Context.MODE_PRIVATE);
        editor = sp.edit();
    }
    
    public static SPManager getInstance(Context context) {
        if (instance == null) {
            synchronized (SPManager.class) {
                if (instance == null) {
                    instance = new SPManager(context);
                }
            }
        }
        return instance;
    }
    
    public void putString(String key, String value) {
        editor.putString(key, value).apply();
    }
    
    public String getString(String key, String defaultValue) {
        return sp.getString(key, defaultValue);
    }
    
    public void putBoolean(String key, boolean value) {
        editor.putBoolean(key, value).apply();
    }
    
    public boolean getBoolean(String key, boolean defaultValue) {
        return sp.getBoolean(key, defaultValue);
    }
}

// 使用
SPManager.getInstance(this).putString("username", "张三");
String name = SPManager.getInstance(this).getString("username", "");

五、常见错误和注意事项

❌ 错误1:忘记私有化构造函数

java 复制代码
public class WrongSingleton {
    // 错误:构造函数是public,别人可以new多个实例
    public WrongSingleton() {}
}

❌ 错误2:在Android中持有Activity引用

java 复制代码
public class BadSingleton {
    private Context context;  // 危险!
    
    public void setContext(Context context) {
        this.context = context;  // 可能持有Activity导致内存泄漏
    }
}

// ✅ 正确做法:使用ApplicationContext
public class GoodSingleton {
    private Context context;
    
    public void setContext(Context context) {
        // 使用ApplicationContext,不会泄漏Activity
        this.context = context.getApplicationContext();
    }
}

❌ 错误3:在单例中做耗时操作

java 复制代码
public class BadNetworkManager {
    public void loadData() {
        // 错误:在主线程做网络请求
        HttpURLConnection conn = ...;  // 会卡死UI
    }
}

// ✅ 正确做法:使用线程或协程
public class GoodNetworkManager {
    public void loadData() {
        new Thread(() -> {
            // 在子线程做耗时操作
            HttpURLConnection conn = ...;
        }).start();
    }
}

六、什么时候用单例?

✅ 适合用单例的场景

  • 数据库操作类(Room、SQLite)
  • 网络请求类(OkHttp、Retrofit)
  • SharedPreferences工具类
  • 图片加载器(Glide、Picasso)
  • 全局配置管理
  • 日志记录器
  • 线程池管理

❌ 不适合用单例的场景

  • 用户信息(如果用单例,所有用户都一样了)
  • Activity、Fragment(系统会管理生命周期)
  • 包含大量可变状态的对象
  • 需要被继承的类

七、总结和最佳实践

新手记住这3点就够了:

  1. 写法选择 :Android开发用双重检查锁静态内部类
  2. Context处理 :永远用getApplicationContext()
  3. 避免持有:不要在单例中持有Activity/View引用

最简单的模板(复制即用):

java 复制代码
public class YourManager {
    private static volatile YourManager instance;
    
    private YourManager() {
        // 初始化
    }
    
    public static YourManager getInstance() {
        if (instance == null) {
            synchronized (YourManager.class) {
                if (instance == null) {
                    instance = new YourManager();
                }
            }
        }
        return instance;
    }
    
    // 你的方法
    public void doSomething() {
        // ...
    }
}

单例不是万能的,不要滥用。只有真正需要"全局唯一"的时候才用单例模式!

什么时候使用单例模式

不是所有全局数据都应该用单例,我来帮你判断什么时候该用,什么时候不该用。

快速判断表

数据类型 是否用单例 推荐方案
用户登录信息 ✅ 适合 单例 + 持久化存储
App配置参数 ✅ 适合 单例
网络请求管理器 ✅ 适合 单例
数据库帮助类 ✅ 适合 单例
购物车数据 ⚠️ 看情况 单例 或 ViewModel
页面间传递的数据 ❌ 不适合 Intent / ViewModel
不同用户的数据 ❌ 不适合 普通对象
临时性数据 ❌ 不适合 局部变量

详细分析:何时用单例?

✅ 适合用单例的场景

1. 系统级服务(全局只有一个)
java 复制代码
// 适合:网络管理器
public class NetworkManager {
    private static NetworkManager instance;
    private OkHttpClient client;
    
    // 整个App只需要一个网络客户端
    // ✅ 适合用单例
}

// 适合:数据库管理器
public class DatabaseManager {
    private static DatabaseManager instance;
    private RoomDatabase db;
    
    // 整个App只需要一个数据库连接池
    // ✅ 适合用单例
}
2. 全局配置信息
java 复制代码
// 适合:App配置
public class AppConfig {
    private static AppConfig instance;
    
    private boolean isDarkMode;
    private String apiUrl;
    private int timeoutSeconds;
    
    // 配置全局只有一份,所有页面共用
    // ✅ 适合用单例
}
3. 用户会话数据(当前登录用户)
java 复制代码
// 适合:当前用户信息
public class UserSession {
    private static UserSession instance;
    
    private User currentUser;
    private String authToken;
    
    // 当前登录的用户信息,全局唯一
    // ✅ 适合用单例
}

❌ 不适合用单例的场景

1. 多个用户的数据
java 复制代码
// ❌ 错误:用户列表不应该用单例
public class UserList {
    private static UserList instance;  // 错误!
    private List<User> users;
}

// ✅ 正确:普通类,需要时创建
public class UserList {
    private List<User> users;
    // 不需要单例
}

// 使用
UserList friends = new UserList();     // 好友列表
UserList followers = new UserList();   // 粉丝列表
// 这是两个不同的对象,不应该用单例
2. 页面间临时传递的数据
java 复制代码
// ❌ 错误:用单例传递数据
public class TempDataHolder {
    private static TempDataHolder instance;
    private String tempData;  // 容易混乱
    
    public static TempDataHolder getInstance() {
        // ...
    }
}

// Activity A
TempDataHolder.getInstance().setTempData("some data");
startActivity(new Intent(this, ActivityB.class));

// Activity B
String data = TempDataHolder.getInstance().getTempData();
// 问题:如果Activity被系统杀死,数据丢失
// 问题:多个页面共用会互相覆盖

// ✅ 正确:用Intent传递
Intent intent = new Intent(this, ActivityB.class);
intent.putExtra("data", "some data");
startActivity(intent);
3. 列表中的多个项目
java 复制代码
// ❌ 错误:购物车商品条目
public class CartItem {
    private static CartItem instance;  // 错误!
    private String productName;
    private int quantity;
}
// 购物车有多个商品,不应该用单例

// ✅ 正确:普通类
public class CartItem {
    private String productName;
    private int quantity;
    // 每个商品独立创建
}

特殊情况:需要根据生命周期判断

场景:购物车数据

java 复制代码
// 方案1:如果购物车需要全局访问,可以用单例
public class ShoppingCart {
    private static ShoppingCart instance;
    private List<CartItem> items;
    
    public void addItem(CartItem item) {
        items.add(item);
    }
    // ✅ 可以用单例,但要注意数据持久化
}

// 方案2:更好的方案 - ViewModel(推荐)
public class ShoppingCartViewModel extends ViewModel {
    private MutableLiveData<List<CartItem>> items = new MutableLiveData<>();
    
    public void addItem(CartItem item) {
        // 处理添加逻辑
    }
}
// ✅ 更推荐:生命周期感知,屏幕旋转不丢失

Android特有的最佳实践

1. Application类作为单例替代品

java 复制代码
// 方式1:自定义Application
public class MyApplication extends Application {
    private UserData userData;
    
    public UserData getUserData() {
        return userData;
    }
    
    public void setUserData(UserData data) {
        this.userData = data;
    }
}

// 使用
MyApplication app = (MyApplication) getApplicationContext();
app.setUserData(userData);
// ✅ 优点:生命周期跟随App,永远不会为null

2. ViewModel + LiveData(推荐用于UI相关数据)

java 复制代码
// 推荐:用于Activity/Fragment间的数据共享
public class SharedViewModel extends ViewModel {
    private MutableLiveData<UserData> userData = new MutableLiveData();
    
    public void setUserData(UserData data) {
        userData.setValue(data);
    }
    
    public LiveData<UserData> getUserData() {
        return userData;
    }
}

// Activity A
SharedViewModel viewModel = new ViewModelProvider(this).get(SharedViewModel.class);
viewModel.setUserData(userData);

// Activity B(同一个Activity的Fragment)
SharedViewModel viewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
viewModel.getUserData().observe(this, data -> {
    // 自动接收更新
});
// ✅ 优点:生命周期安全,屏幕旋转不丢失

3. 依赖注入(现代Android推荐)

kotlin 复制代码
// 使用Hilt
@Singleton
class UserManager @Inject constructor() {
    // Hilt保证全局单例
}

@HiltAndroidApp
class MyApplication : Application()

决策流程图

复制代码
需要全局管理的数据
    │
    ├─ 是系统级服务(网络、数据库)? ──YES──→ 使用单例 ✅
    │
    ├─ 是App全局配置? ──YES──→ 使用单例 ✅
    │
    ├─ 是当前登录用户信息? ──YES──→ 使用单例 ✅
    │
    ├─ 是多个独立对象(用户列表、商品列表)? ──YES──→ 不要用单例 ❌
    │
    ├─ 是页面间临时数据? ──YES──→ 用Intent/Arguments ❌
    │
    ├─ 与UI相关,需要感知生命周期? ──YES──→ 用ViewModel ✅
    │
    └─ 都不符合 ──→ 用普通类或依赖注入

实战示例:混合使用

java 复制代码
// 1. 用户会话 - 用单例
public class UserSession {
    private static UserSession instance;
    private User currentUser;  // 当前登录用户
    
    public static UserSession getInstance() { ... }
}

// 2. 消息列表 - 不用单例,每个会话独立
public class MessageList {
    private List<Message> messages;
    private String conversationId;  // 每个对话有自己的消息列表
}

// 3. 应用配置 - 用单例
public class SettingsManager {
    private static SettingsManager instance;
    private boolean isDarkMode;
    private String language;
}

// 4. 页面间共享数据 - 用ViewModel
public class SharedViewModel extends ViewModel {
    private MutableLiveData<OrderData> orderData;
}

总结建议

用单例当:

  • ✅ 整个App只需要一个实例(网络、数据库)
  • ✅ 配置信息全局唯一
  • ✅ 当前登录用户信息

不用单例当:

  • ❌ 会有多个独立对象(列表数据)
  • ❌ 生命周期短暂(临时数据)
  • ❌ 不同场景不同数据

更好的替代方案:

  1. ViewModel:UI相关的共享数据
  2. Intent/Bundle:页面间简单数据传递
  3. Application类:App级别的全局变量
  4. 依赖注入:大型项目的推荐方式

单例不是全局数据的唯一解决方案,选择合适的方式更重要!

相关推荐
许彰午2 小时前
CacheSQL(二):主从复制——OpLog 环形缓冲区与故障自动恢复
java·数据库·缓存
liang_jy2 小时前
Android SparseArray
android·源码
liang_jy2 小时前
Activity 启动流程扩展篇(一)—— startActivityInner 任务决策全解析
android·源码
Bat U2 小时前
JavaEE|多线程初阶(七)
java·开发语言
NPE~3 小时前
[App逆向]脱壳实战
android·教程·逆向·android逆向·逆向分析
木易 士心4 小时前
别再只会用 drawCircle 了!一文搞懂 Android Canvas 底层机制
android
掌心向暖RPA自动化5 小时前
如何获取网页某个元素在屏幕可见部分的中心坐标影刀RPA懒加载坐标定位技巧
java·javascript·自动化·rpa·影刀rpa
AtOR CUES5 小时前
MySQL——表操作及查询
android·mysql·adb
日取其半万世不竭5 小时前
Minecraft Java版社区服务器搭建教程(Linux,适合新手)
java·linux·服务器
TeamDev6 小时前
JxBrowser 9.0.0 版本发布啦!
java·前端·混合应用·jxbrowser·浏览器控件·跨平台渲染·原声输入