异步编程最佳实践

49

我最近写了我的第一个Android应用程序,大约有8,000-10,000行代码。在正常的设计模式中,一件不断妨碍我的事情是Android大量使用异步调用(打开对话框、活动等)。因此,我的代码很快开始看起来像"意大利面条",我最终开始讨厌看某些类。

是否有特定的设计模式或编程方法适用于这类系统,有人会推荐吗?有没有关于编写可管理的异步代码的建议?


3
如果你没有被事件驱动编程所强制实施的"意大利面条式"结构的例子,那么任何人都很难给你建议。 - CommonsWare
例如,在使用showDialog() / onDialogCreate() 调用时,结合对话框内的按钮监听器来跟踪条件程序状态。管理流程可能会变得棘手。 - Ryan
也许你不应该使用对话框。:-) 在Android上,对话框应该是例外而不是规则。例如:相对较少的Web应用程序使用任何模态对话框(HTML等效),尽管使用正确的库完全可以实现。 - CommonsWare
3
你的意思是事件驱动开发吗?所有用户界面(Windows,DHTML,...)都是作为对事件的响应序列编写的。在这方面,Android没有什么不同。 - Vladimir Dyuzhev
1
是的,这就是为什么我帖子的标题和整个问题都与异步代码有关,而不是特定于Android。 - Ryan
4个回答

47
  • 使用全局变量

如果您不想用简单的 Intent.putExtra() 调用来搞乱您的代码,并为每个独特的 Activity 管理这些事情,那么您将需要在应用程序中使用全局变量。扩展 Application 并将您需要的数据存储在应用程序存活期间。要实际实现它,请使用此优秀的答案。这将使活动之间的依赖关系消失。例如,假设您需要一个 "用户名" 用于应用程序的生命周期 - 这是一个非常好的工具。无需使用低质的 Intent.putExtra() 调用。

  • 使用样式

在制作第一个 Android 应用程序时,一个常见的错误是通常只会开始编写 XML 视图。XML 文件将(毫不费力且非常快速地)达到很多行代码。在这里,您可以有一个解决方案,仅使用 style 属性即可实现特定的行为。例如,请考虑以下代码片段:

values/styles.xml:

<style name="TitleText">
    <item name="android:layout_height">wrap_content</item>
    <item name="android:layout_width">wrap_content</item>
    <item name="android:textSize">18sp</item>
    <item name="android:textColor">#000</item>
    <item name="android:textStyle">bold</item>   
</style>

layout/main.xml:

如果你有两个 TextView,并且它们的行为应该相同,那么让它们使用 TitleText 样式。示例代码:

<!--- ... -->
<TextView
   android:id="@+id/textview_one"
   style="@style/TitleText" 
/>

<TextView
   android:id="@+id/textview_two" 
   style="@style/TitleText" 
/>
<!--- ... -->

简单且无需重复编码。如果您真的想进一步了解这个特定主题,请查看Layout Tricks: Creating Reusable UI Components

  • 使用字符串

这一点很简短,但我认为它很重要。开发人员可能会犯的另一个错误是跳过strings.xml,只是在代码中编写UI消息(和属性名称)(他将在其中需要)。为了使您的应用程序更易于维护,请在strings.xml文件中定义消息和属性。

  • 创建并使用全局工具类

当我写我的第一个应用程序时,我只是在需要它的地方编写(和复制)方法。结果?许多方法在各种活动之间具有相同的行为。我学到的是制作一个工具类。例如,假设您必须在所有活动中进行Web请求。在这种情况下,跳过在实际的Activity中定义它们,并为其创建一个静态方法。示例代码:

public final class Tools {

    private Tools() {
    }

    public static final void sendData(String url, 
              String user, String pass) {
        // URLConnections, HttpClients, etc...
    }

}
现在,您只需在需要向服务器发送数据的 Activity 中使用以下代码:
Tools.sendData("www.www.www", "user", "pass");

然而,您明白了这个要点。在需要时使用这个"模式",它会让您避免破坏您的代码。

  • 让自定义类定义用户与您的应用程序进行交互的行为

这可能是最有用的要点。仅仅定义 "用户需要与您的应用程序进行交互的地方"。比如说您有一个菜单(Menu),它的行为在几行代码中非常复杂,那么我们为什么要把菜单(Menu)的计算放在同一个类中呢?每一个小项都会让您的活动(Activity)类变成一堆令人痛苦的长代码——让您的代码看起来像 "意大利面条(Spaghetti)"。例如,与其写出以下代码:

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
    MenuItem item;
    item = menu.findItem(R.id.menu_id_one);
    if (aBooleanVariable) {
        item.setEnabled(true);
    } else {
        item.setEnabled(false);
    }
    // More code...
    return super.onPrepareOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem i) {
    // Code, calculations...
    // ...
    // ...
    return super.onOptionsItemSelected(i);
}

重新设计为类似于这样的东西:

private MyCustomMenuInstance mMenuInstance;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);        
    setContentView(R.layout.main);

    mMenuInstance = new MyCustomMenuInstance();
}  

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
    mMenuInstance.onPrepareOptionsMenu(menu);
    return super.onPrepareOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem i) {
    mMenuInstance.onOptionsItemSelected(i);
    return super.onOptionsItemSelected(i);
}
例如,MyCustomMenuInstance
public class MyCustomMenuInstance { 

    // Member fields..

    public MyCustomMenuInstance() {
        // Init stuff.
    }

    public void onPrepareOptionsMenu(Menu menu) {
        // Do things..
        // Maybe you want to modify a variable in the Activity 
        // class? Well, pass an instance as an argument and create
        // a method for it in your Activity class.
    }

    public void onOptionsItemSelected(MenuItem i) {
        // Do things..
        // Maybe you want to modify a variable in the Activity 
        // class? Well, pass an instance as an argument and create
        // a method for it in your Activity class.
    }

}

你看到这里了吧。你可以将它应用于许多事情,例如 onClickonClickListeneronCreateOptionsMenu,清单很长。要了解更多的"最佳实践",你可以在Google的一些示例应用程序中查看这里。寻找他们如何以一种良好和正确的方式实现事物。

最后一个词;保持你的代码整洁,以逻辑方式命名你的变量和方法,特别是以正确的方式。始终要理解你在代码中的位置——这非常重要。


对于你的抽象工具类,不要那样做。采用组合而非继承的方式。请使用这种方式代替:http://en.wikipedia.org/wiki/Composition_over_inheritance - Robert Massaioli
1
@Robert Massaioli:这也不是继承,只是一个带有静态方法的工具类;在需要时可用。 - Wroclai
没错,你让我感到困惑了,因为通常说这个类不能被实例化或子类化的方式是给它一个私有构造函数。如果所有的方法都是公共静态的,那么你可以在任何地方直接调用它。你使用“抽象”这个词让我感到困惑。 - Robert Massaioli
@Robert - 在抽象层面上吗?- Pompe,+1 - 非常有用的东西。 - Peter Ajtai
3
通常更常用的是将类命名为 Util 而不是 Tool - om-nom-nom

6
从一个业余者的角度来看,我不指望我的第一次尝试就能得到一个干净、可投入生产的应用程序。有时候我会得到意大利面条、长型扁面条甚至是大馄饨式的代码。此时,我会重新思考我最不喜欢的代码是什么,并寻找更好的替代方案:
  • 重新思考你的类以更好地描述你的对象,
  • 将每个方法中的代码保持简洁,
  • 任何时候都避免依赖于静态变量,
  • 使用线程处理昂贵的任务,不要将其用于快速的程序,
  • 将 UI 与应用逻辑分开(将其保留在类中),
  • 任何时候都保留私有字段:当你想要更改你的类时,这将非常有帮助,
  • 重复上述步骤,直到你满意为止。

我看到异步方法中最常见的错误之一就是在创建一个或多个线程的循环内使用静态变量,而没有考虑到该值可能在另一个线程中发生改变。避免使用静态变量!

正如 OceanBlue 指出的那样,可能不清楚的是 final static 变量不会带来任何危险,但是公共静态变量可以发生改变。这并不是静态本身的问题,而是这样的观念——它们将有一个值,然后发现该值已经改变。这可能很难找到问题所在。典型的例子是点击计数器或计时器的值,当可能存在多个视图被点击或多个计时器时。

希望你能收到比我更有经验的人的建议。祝你好运!


@Aleamdam:你强调不要使用静态变量。我想知道你对于使用“public final static”值的看法是什么?这些基本上是业务值,而不是硬编码,可以将其放在一个单独的类中,例如:MyBusinessDefs.java? - OceanBlue
1
@OceanBlue 我强调不要依赖于可能在线程执行期间发生变化的静态、公共变量。当然,final static 变量不会出现这个问题。事实上,我更喜欢使用它们,因为它们不仅易于在需要时更改,而且有助于阅读清晰度。我将在我的答案中进一步澄清这一点。 - Aleadam

3
如果您最担心的是处理用户界面,那么您需要掌握事件驱动编程。事件驱动编程的思想贯穿于所有现代用户界面系统,并且在各种类型的应用中都很有用(不仅限于用户界面)。
我学习时觉得最简单的方法就是将每个组件和事件看作独立的实体。你只需要关注传递到事件方法中的事件对象即可。如果你习惯编写从头到尾运行的应用程序,那可能需要一些思维上的转变,但通过练习可以很快达到目标。

1

使用模型视图控制器模式怎么样?

至少你需要在“模型”(对象或一组对象)中隔离所有状态及其逻辑管理,并在单独的类(可能是Activity类)中拥有与视图、监听器、回调等相关的所有内容。


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