关于内存泄漏和通用方法

4

这些选项中哪一个更适合在我的应用程序中使用?

public class NetworkCheck {

    Context context;

    public NetworkCheck(Context context) {
        this.context=context;
    }

    public boolean isNetworkConnected() {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        return cm.getActiveNetworkInfo() != null;
    }
}

...    

if(new NetworkCheck(this).isNetworkConnected()){
    //statement
}

对于上面的内容,每次我使用它的方法时都必须创建堆内存。当其作用域结束(即大括号结尾)时,堆内存将被销毁...

或者,您可以选择:

public class NetworkCheck {

    public static boolean isNetworkConnected(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        return cm.getActiveNetworkInfo() != null;
    }
}

...

if(NetworkCheck.isNetworkConnected(){
    //statement
}

对于这个,我不需要创建任何堆内存。我读过许多文章,在这些文章中人们说创建静态变量和方法会导致应用程序的内存泄漏。

请帮我创建下面方法中的通用 getLocalData() 方法……

 public static <T> void saveLocalData(Context context, String key, T value) {
        SharedPreferences prefs = context.getSharedPreferences(
                "Qikqrup", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        if (value instanceof String)
            editor.putString(key, (String) value);
        else if (value instanceof Boolean)
            editor.putBoolean(key, (Boolean) value);
        else if (value instanceof Integer)
            editor.putInt(key, (Integer) value);
        editor.commit();
    }

仅凭以上代码,我没有发现任何可能的内存泄漏。你可以使用两者。查看此链接 http://blog.nimbledroid.com/2016/05/23/memory-leaks.html 以更好地理解内存泄漏。 - masp
已经阅读了许多关于此的文章,你的是其中之一...谢谢回复。 - sushildlh
我读过很多文章,人们说在应用程序中创建静态变量和方法会导致内存泄漏。那不是真的。请重新阅读这些文章。正如@Alan K.在下面所说的那样,第二种方式更快。据文献资料显示,速度提高了15-20%。 - masp
请注意,T 变量没有意义。只需将参数类型更改为 Object 即可。 - Andy Turner
5个回答

5
第二种情况是最严格的,无法被击败。
在第一种情况下,只要NetworkCheck对象存在,Context对象就会保持活动状态。而且还有第二个对象(NetworkCheck)。
然而:
- 它的使用是有限的,对象将很快超出范围并被垃圾回收。所以效果就像我们周围自发产生粒子和反粒子一样引人注目。 - 在其他情况下,构造函数和调用的分离,也许多次调用会证明有用。
关于:
public static <T> void saveLocalData(Context context, String key, T value)

不太有用,Object 更好。事实上 null 不会被捕获。通常情况下,此函数具有部分域:并非所有类都适用。而且再次检索值将是猜测工作。

可以使用运行时信息。

public static <T> void saveLocalData(Context context, String key, Class<T> type, T value)
public static <T> T loadLocalData(Context context, String key, Class<T> type)

Java还有一个可以使用的Serializable接口。

public static <T> void saveLocalData(Context context, String key, Serializable value)

当您将对象存储后,您会按原样收到该对象。您需要阅读相关主题。


先生,您能否举个例子来解释一下这个问题呢? :) - sushildlh
我看了一下SharedPreferences,似乎你的方法非常标准:适用于许多情况。至于Serializable:当保存二进制数据时,这是最好的选择。使用SharedPreferences时,GSON或类似的工具可能是更好的选择。我无法提供完整的示例。 - Joop Eggen

4
在你的saveLocalData()方法中,不一定会出现内存泄漏,但是根据你的使用方式,可能会出现内存泄漏。例如,看下面的代码:
for( long i = 0;  i < Long.MAX_VALUE;  i++ )
{
    String name = String.valueOf( i );
    saveLocalData( context, name, i );
}

这个循环将在内存用尽之前不断向你的映射表添加值。但是,任何集合都可能发生这种情况,而不仅仅是映射表,无论你的集合是静态分配还是非静态分配。对于静态集合而言,它比非静态集合更容易发生这种情况,因为非静态集合可以理论上超出范围并进行垃圾回收,而静态集合通常注定要永远存在。
然而,这个说法并不绝对:
- 一方面,非静态集合可能通过被静态对象引用而被永久地锚定到内存中; - 另一方面,一个静态映射表可能会被一个小心谨慎的程序员明确释放和重新分配,或者只是简单地清空,以防止它变得太大。
为了编写 `getLocalData()`,需要按照以下方式声明它:
public static <T> T getLocalData( Context context, String key, Class<T> classOfValue )

然后按如下方式调用:

String name = MyClass.getLocalData( context, "Name", String.class );

在函数内部,您需要执行类似以下操作:

if( classOfValue == String.class )
    return editor.getString( key );
...

你的两个版本的NetworkCheck和你们各自想要使用的目的大致相同,选择其中一个并没有太多的得失。使用new NetworkCheck(this)会产生冗余的内存分配,但正如你已经理解的那样,这些内存很快就被垃圾回收了,因此不会有任何损害。


3
我建议在这种情况下使用静态方法版本,因为仅仅为了检查连接而创建一个新对象是过度的。
“静态方法只是方法,它们不存储在堆上,它们只是不能使用“this”参数。” 关于内存泄漏和通用方法

你的回答中有什么新内容吗?你所说的已经在我的问题中提到了...感谢你的努力... - sushildlh
1
静态变量只有在你打算在不使用它时将其设置为null,并且在使用完毕后忘记清除它时才会导致内存“泄漏”。从本质上讲,如果有的话,它并不是真正的“泄漏”,因为最多只能有一个实例。 - Alan K.

3
静态变量和方法会导致内存泄漏。这仅适用于类的静态字段,而不适用于静态方法。这是因为静态成员的值将在声明它们的类存在于内存中的时间内一直存在于内存中,或者至少在您不将静态变量的值设置为 null 以允许 GC 回收内存的时间内存在于内存中。(顺便说一下:正确的术语应该是“存储的实例导致长期内存消耗”只有当这些实例从未使用时才会发生泄漏,否则它们就会成为“有用的长期内存使用”)。
至于静态方法,如果它们没有将创建的任何实例存储在静态变量中,则这些实例将:
1. 如果是局部变量/非返回实例-在超出范围后尽快被 CG-ed(注意:“尽快”不能保证“立即” - 这就是“尽可能”意味着的)。 2. 如果它们是返回值-只有在调用者释放它们(即不再“存储”它们以供未来使用)后,它们才会被 GC-ed。关于返回值,您的静态方法的作用就像一个“工厂”:忽略特定的配置序列(假设比构造函数提供的更复杂),它们与只使用 new ClassOfTheReturnedValue(...) 创建的实例没有区别。
关于 public boolean isNetworkConnected() 与 static public boolean isNetworkConnected():在您使用的示例中,它们在内存泄漏或创建将被 GC-ed 的实例的能力方面没有区别-因为您的 NetworkCheck check=new NetworkCheck(this); 只是一个临时/局部变量,一旦超出范围就可以收集。两种方法之间唯一的区别是非静态实际上创建了一个对象(因此在 CG 下游需要更多的工作),而静态形式则不会。
关于 public static void saveLocalData(Context context, String key, T value):
嗯,您发布的方式“极其次优”。
  1. 如果使用除StringBooleanIntegerLongFloat 之外的任何东西调用该方法,编译器将按照请求进行连线。在执行时,您将花费不必要的类型检查(全部失败),只是为了调用没有真正更改的editor.commit()

  2. 关于类型安全性,您否定了编译器在编译时执行类型检查的可能性,转而采用所有运行时类型检查。您的方法与public static void saveLocalData(Context context, String key, Object value)没有区别-所有类型检查都推迟到运行时。通用表单和“普通对象”形式之间唯一的区别是:编译器将更努力地编译通用方法,但将达到与“Object value”方法相同的最终结果。

实现相同功能的最佳方法是放弃泛型的所有智能和使用纯老式方法重载,如下所示:

protected static SharedPreferences.Editor resolveEditor(
  Context context, String prefOwner
) {
    SharedPreferences prefs = context.getSharedPreferences(
            prefOwner, Context.MODE_PRIVATE
    );
    return prefs.edit();
}
public static void saveLocalData(Context context, String key, boolean value) {
    SharedPreferences.Editor editor = resolveEditor(context, "sushildlh");
    editor.putBoolean(key, value);
    editor.commit();
}
public static void saveLocalData(Context context, String key, int value) {
    SharedPreferences.Editor editor = resolveEditor(context, "sushildlh");
    editor.putInt(key, value);
    editor.commit();
}
public static void saveLocalData(Context context, String key, String value) {
    SharedPreferences.Editor editor = resolveEditor(context, "sushildlh");
    editor.putString(key, value);
    editor.commit();
}

// etc

使用以上方法:
  1. 编译器可以验证未使用不合适类型的值调用方法

  2. 代码是最优的

毕竟,SharedPreferences.Editor 没有实现通用的 void putValue<T>(T value) 是有原因的 - 你认为自己从外部能做得更好吗?如果可能的话,我估计 SharedPreferences.Editor 的作者们会在他们的实现中实现它。

谢谢您的回答,但我正在寻找一般的方法... :) - sushildlh

2

saveLocalData()方法不会引起任何内存泄漏,因为您没有持有超过其生命周期的任何引用。

在使用活动上下文时,您应注意以下几点:

不要在可能超出活动生命周期的类中使用活动上下文。

例如:不要在数据库帮助程序类或单例中使用活动上下文。

关于NetworkCheck,class2更好,因为isNetworkConnected()方法是实用程序方法,不表示NetworkCheck的任何状态。

优点:

  1. 提高性能,因为您不需要实例化额外的对象。
  2. 不依赖于实例创建
  3. 可以轻松地在您的整个应用程序中使用(在执行网络操作的不同类中)
  4. 将使代码更易于理解

缺点:

  1. 无法轻松模拟进行单元测试。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接