Android onClickListener实现的最佳实践

6

有四种方法可以为可点击的视图(例如按钮)添加onClick监听器:

  1. 在布局文件中设置onClick属性,该属性指向活动中的某个方法。
  2. 创建一个匿名内部类。
  3. 将onClick监听器分配给私有成员变量。
  4. 让Activity上下文实现onClick监听器接口。

那么我的问题是,如何选择这些实现技术中的一种?是否根据特定条件有最佳实践,还是仅仅是程序员的偏好问题?


2
匿名内部类很危险,可能会导致外部类泄漏,因此我建议避免使用这种模式。如果需要引用,则建议使用带有对外部类的WeakReference的静态内部类,或者让Activity本身成为onClickListener。 - Michael Krause
也要避免成员变量选项吗?如果是,为什么?谢谢! - JaeW
在成员变量持有一个对onClickListener的引用的情况下,你仍然需要一个声明的类来实现onClick方法。因此,你需要确保它是静态的,如果它是在外部类的作用域内声明的,并且如果它需要对外部类的引用(无论它是作为内部类声明还是作为单独的类在其他地方声明),那么它应该是一个WeakReference。 - Michael Krause
@Michael Krause,你能展示一个不使用AsyncTask、Handler或任何线程的内存泄漏示例吗? - Maxim G
非静态匿名内部类怎么样? - Maxim G
显示剩余2条评论
3个回答

4

这里我们使用所谓的回调模式。

public class Button {
    private Callback callback;

    public Button(Callback callback) {
        this.callback = callback;
    }

    public void update() {
        // Check if clicked..
        callback.onClick(this);
    }

    public interface Callback {
        public void onClick(Button Button);
    }
}


Button b = new Button(new Callback() {
    @Override
    public void onClick(Button b) {
        System.out.println("Clicked");
    }
});

在我们的情况下,onClick处理程序实现了View.OnClickListener接口。
关键点:
- 与活动/片段一致性; - 访问活动/片段的成员; - 可读性; - @Michael Krause展示了一个关于内存泄漏的好观点memory leaks
1)XML文件中的属性只能用于活动,如@Karakuri所述,它使用反射,速度较慢。
2)匿名内部类对访问封闭类的成员有特殊规则(请参阅[1][2])。在某些情况下可能会发生内存泄漏(例如,使用AsyncTask、Handlers进行线程处理)。
3)在这里,您可以完全访问封闭类的成员。
4)是第三个的变化。

易读性取决于处理程序的大小,小逻辑可以内联,但对于较大的代码块,请考虑使用第三和第四维。


所以在Android中,接口已经以onClickListner()的形式创建。如果实现了一个Android Button(与您上面的代码不同),则没有接受onClickListner()作为参数的(现有)构造函数;相反,您将不得不使用setOnClickListener(),它从View类继承而来。这都正确吗? - JaeW
例子仅展示了回调模式的思想。在Android API中,您将使用类似于setOnClickListener()的设置方法,该方法期望接口View.OnClickListener。顺便说一下,相同的概念也适用于片段与主机活动之间的聊天(示例)。 - Maxim G

2
我从不使用onClick属性,因为它将布局与特定的Activity绑定在一起(必须通过反射找到方法)。它在Fragments上不起作用。
选项2和3几乎相同。如果您想将私有成员用作多个视图的OnClickListener,则选项3可能更有优势。
选项4接近选项3。一个关键区别是它改变了类声明,因此如果对您来说保持类声明没有接口实现很重要(或者也许您需要维护某种二进制兼容性),则可能不想使用此选项。
我的建议是避免选项1,并选择最适合您代码风格的选项。您也不需要在代码中的每个地方都使用相同的方法。

谢谢。非常有帮助。另外一个问题:为什么要保持类声明不包含接口实现? - JaeW
@JaeW 一个例子是你可能想要保持与该类的先前版本的二进制兼容性(也许如果这是某种SDK中的话)。此外,如果你使该类实现接口,任何能够以某种方式获取它的实例的人都可以在期望该接口的任何地方使用它,而你可能不希望这种情况发生。 - Karakuri

2

有四种方法可以使用OnClickListener

第一种方式

在方法调用处定义OnClickListener

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Button button = findViewById(R.id.myButton);
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // do something
            }
        });
    }
}  

首先,避免使用这种方法的原因是它会使onCreate方法变得混乱。当您想要观察多个视图的点击事件时,这一点变得更加明显。
其次,避免使用这种方法是因为如果多个按钮应该执行相同的操作,它不会促进代码重用。

第二种方式

第二种方式与第一种方式几乎相同,只是将实现分配给了类中的字段。

public class MainActivity extends AppCompatActivity {

    private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // do something
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Button button = findViewById(R.id.myButton);
        button.setOnClickListener(clickListener);
    }
}  

这种方法与第一种方法基本相同,唯一的优点是可以重复使用该方法来处理多个按钮。

第三种方法

这种方法是通过声明一个内部类来实现OnClickListener。如果将其多次使用,则最好将实例定义为字段。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Button button = findViewById(R.id.myButton);
        button.setOnClickListener(new ButtonClick());
    }

    class ButtonClick implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            // do something
        }
    }
}  

这种方法的优点在于它有助于组织代码。您可以轻松地折叠此内部类并在不需要查看它时忘记它。
另一个好处是它可以转换成公共类,并在其他应用程序领域中重复使用。

第四种方式

第四种方法涉及将Activity实现OnClickListener

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Button button = findViewById(R.id.myButton);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        // do something
    }
}  

这种方式的第一个缺点是在Activity中创建了一个公共方法,当调用setOnClickListener时需要传递this活动。
避免这种方式的第二个原因是,如果添加了另一个按钮,您应该确定哪个按钮被点击。然后,您应该使用switch()if()语句。这不会执行,因为它会浪费每次按钮单击的周期或多个周期。
这种方式的最后一个缺点是难以组织类。例如,您有一个实现多个接口的活动。突然之间,所有这些接口的方法都交织在一起,在向其中一些接口添加方法后,这变得更加明显。现在,您无法添加一个名为onClick的方法的接口。

这些方法之间存在一些差异,但您应根据代码和需求选择自己的方法


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