Android 安卓-定义一个静态按钮会泄露我的活动吗?我可以避免吗?

Android 安卓-定义一个静态按钮会泄露我的活动吗?我可以避免吗?,android,memory-leaks,android-fragments,android-activity,android-view,Android,Memory Leaks,Android Fragments,Android Activity,Android View,我需要按钮是静态的,以便在显示活动的情况下从我的服务中启用/禁用它。我仍然设置了ClickListener,不管怎样,静态视图被认为是危险的。我漏水吗?我可以避免吗 public class MonitorActivity extends FragmentActivity implements OnClickListener { private static Button updateButton; // static?? @Override prot

我需要按钮是静态的,以便在显示活动的情况下从我的服务中启用/禁用它。我仍然设置了ClickListener,不管怎样,静态视图被认为是危险的。我漏水吗?我可以避免吗

public class MonitorActivity extends FragmentActivity implements
        OnClickListener {

    private static Button updateButton; // static??

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_monitor);
        // button
        updateButton = (Button) findViewById(R.id.update_data_button);
        updateButton.setOnClickListener(this); // oops ?
    }

    public static void onDataUpdated(Context ctx) {
        if (updateButton != null) { //that's why I need it static
            updateButton.setEnabled(true); // + set the text etc
        }
    }

    public static void onUpdating() {
        if (updateButton != null) {
            updateButton.setEnabled(false);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.update_data_button:
            serviceIntent.putExtra(MANUAL_UPDATE_INTENT_KEY, true);
            this.startService(serviceIntent);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        Boolean isUpdating = AccessPreferences.get(this, updateInProgressKey,
            false);
        // set the button right
        updateButton.setText((isUpdating) ? defaultUpdatingText
                : getResources().getString(R.string.update_button_text));
        updateButton.setEnabled(!isUpdating);
    }
}

我认为您可以在MonitorActivity中创建BroadcastReceiver。并从服务发送额外消息以启用/禁用按钮。

我建议您使用
LocalBroadcastManager

在您的
活动中
定义一个
广播接收器
并在
onStart()
onResume()
中注册广播,然后在
onStop()
中注销广播 从
服务
将广播发送到
活动
如果
活动
处于活动状态,它将接收广播并更新用户界面,如果不是没有任何事情发生

在您的
服务中定义另一个
BroadcastReceiver
,在
onCreate()
中注册该广播,并在
onDestroy()中注销它
当您的活动启动时,向
服务发送广播
,并让
服务
使用第一次广播更新UI来回复
活动

更新
在做了一些调查之后,我发现你是对的“不鼓励粘性广播”,但是如果你检查一下那篇文章的日期,它是在2008年——在谷歌实施
LocalBroadcastManager
之前

我已经检查了,它不是一个真正的广播,它是一个接口,带有广播接收器列表的单例(不是全局的,也没有IPC通信)


我真的很讨厌公共静态,我总是避免使用它们。每个人都应该这样做。

所以是的-静态按钮会泄露我的活动。我想出了下面的回调,但它是丑陋的。我最终通过让活动扩展到SharedPreferenceChangeListener解决了这个问题

public final class MonitorActivity extends FragmentActivity implements
        OnClickListener, OnSharedPreferenceChangeListener {

    private Button updateButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        updateButton = (Button) findViewById(R.id.update_data_button);
        updateButton.setOnClickListener(this); //no need to unregister methinks
    }

    @Override
    public synchronized void onSharedPreferenceChanged(
            SharedPreferences sharedPreferences, String key) {
        if (updateInProgressKey.equals(key)) {
            final Boolean isUpdating = AccessPreferences.get(this,
                updateInProgressKey, false);
            // set the button right
            updateButton.setText((isUpdating) ? sDefaultUpdatingText
                    : sUpdateButtonTxt);
            updateButton.setEnabled(!isUpdating);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        AccessPreferences.registerListener(this, this);
        AccessPreferences.callListener(this, this, updateInProgressKey);
    }

    @Override
    protected void onStop() {
        // may not be called (as onStop() is killable), but no leak,
        // see: http://stackoverflow.com/a/20493608/281545
        AccessPreferences.unregisterListener(this, this);
        super.onStop();
    }
}
回拨
onPause()
保证运行-因此我将那里的静态字段设为空,并在
onResume()上填充它们。我只读取默认共享首选项,因此在同步块中不会花费很长时间(我必须同步,因为服务可能会在任何奇数时间调用
onUpdate()
onDataUpdate()
)。但不确定是否要注销侦听器

public class MonitorActivity extends FragmentActivity implements
        OnClickListener {

    private static TextView dataTextView; //null this onPause() to avoid a leak
    private static Button updateButton; // null this onPause() to avoid a leak
    // ...

    public static synchronized void onDataUpdated(Context ctx) {
        if (updateButton != null) {
            updateButton.setEnabled(true); // + set the text etc
        }
    }

    public static synchronized void onUpdating() {
        if (updateButton != null) {
            updateButton.setEnabled(false);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.update_data_button:
            serviceIntent.putExtra(MANUAL_UPDATE_INTENT_KEY, true);
            this.startService(serviceIntent);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        synchronized (MonitorActivity.class) {
            Boolean isUpdating = AccessPreferences.get(this,
                updateInProgressKey, false);
            updateButton = (Button) findViewById(R.id.update_data_button);
            updateButton.setOnClickListener(this);
            // set the button right
            updateButton.setText((isUpdating) ? defaultUpdatingText
                    : getResources().getString(R.string.update_button_text));
            updateButton.setEnabled(!isUpdating);
        }
    }

    @Override
    protected void onPause() {
        synchronized (MonitorActivity.class) {
            updateButton.setOnClickListener(null); // TODO : needed ??
            dataTextView = updateButton = null; // to avoid leaking my activity
        }
        super.onPause();
    }
}

有3种解决方案可供您选择:

  • 当上下文被破坏时,Set button=null(在顶部)

  • 对按钮字段使用WeakReference,例如:

  • 私有静态WeakReference更新按钮

  • 不创建静态按钮。它总是保持上下文

  • 是的,很危险。活动已被销毁,但您仍然有一个对按钮的静态引用,而按钮又有一个对活动上下文的引用,因此您泄漏了整个活动(在DDMS中很容易看到)。相反,创建一个接口,从您的服务回调到活动或广播接收器。@Simon:broadcasts应该是粘性的-这是一个nono(IPC等)。如果没有静态成员(onUpdate等都是回调),我如何回调?好的,明白了-那么有回调的接口吗?@Simon:你能举个例子吗?一个界面如何让我不再需要静态按钮?不-它们应该是粘性的,这是错误的(根据Hackborn的说法)“它们应该是粘性的”这意味着什么?请向Diane的帖子提供链接,说明问题所在。@Simon:如果我在活动未激活时广播,广播将丢失-但这不是一个选项-因此BC应该是粘性的-但粘性BC适用于例外情况:“BroadcastReceiver”是您需要的,将其与“LocalBroadcastManager”一起使用在OnStart()中注册广播并在OnStop()中注销广播??无论如何,这有点混乱-如果你按照我上面的链接,你会发现应用程序中不鼓励广播。1-Android在任何地方都使用广播(消息、呼叫、引导、网络更新等)。2-在活动中注册/未注册的广播没有粘性(清单广播是)。3-您可以在“onPuse()”中注销广播,而Android即使在内存不足的情况下也必须调用此函数。4-使用诸如“EventBus”和“Otto”等LIB有更多的选择。1。2.3.这就是我要做的,在简历4上注册。这对我的应用来说是一个过度的杀伤力,但感谢链接-看看我的解决方案,并将其与你的进行比较-我会在某个时候看一看,但不会很快遇到单身汉??另外,“您讨厌公共静态”——您使用的库中有一半是公共静态库,比如Thread.sleep()、Collections.sort()和System.out.println();)提出了一个解决方案-这样我可以在可见的情况下更新活动,但不必担心注销-请参阅我的答案。至于本地广播经理——可能有点棘手:@Simon:这就是我想到的——仍然需要确保它在所有情况下都能正常工作,但我认为它可以。缺点是在某些edhe情况下可能会更新两次UI(onResume()然后onDataUpdated())与LocalBroadcastManager相比,这是一个不错的解决方案,代码更干净