异步编程最佳实践

Posted

技术标签:

【中文标题】异步编程最佳实践【英文标题】:Asynchronous programming best practices 【发布时间】:2011-08-12 01:04:18 【问题描述】:

我最近编写了我的第一个 android 应用程序,大约有 8,000-10,000 行代码。一直阻碍我使用正常设计模式的一件事是 android 大量使用异步调用(打开对话框、活动等)。由于这个原因,我的代码很快开始看起来像“意大利面条”,我最终开始不喜欢看某些类。

是否有任何人会推荐的适用于此类系统的特定设计模式或编程方法?对于编写可管理的异步代码有什么建议吗?

【问题讨论】:

如果没有一个你认为是事件驱动编程强加给你的“意大利面条”结构的例子,任何人都很难给你建议。 当与对话框内的按钮侦听器结合使用时,使用 showDialog() / onDialogCreate() 等调用时跟踪条件程序状态的示例。管理流程可能会很棘手。 也许你不应该使用对话框。 :-) Android 上的对话框应该是例外,而不是规则。恰当的例子:相对较少的 Web 应用程序使用任何模式对话框(html 等价物),尽管使用正确的库当然是可能的。 你的意思是事件驱动开发吗?所有 UI(Windows、DHTML、...)都被编写为对事件的一系列响应。在这方面,Android 没有什么不寻常的。 是的,这就是为什么我的帖子的标题和整体问题是关于异步代码的——尤其是关于 Android 的原因。 【参考方案1】: 使用全局变量

如果您不想通过简单的Intent.putExtra() 调用来弄乱您的代码并为每个唯一的Activity 管理这些东西,您将不得不在应用程序中使用全局变量。扩展Application 并存储您需要的数据,只要您的应用程序还活着。要实际实现它,use this excellent answer。这将使活动之间的依赖关系消失。例如,假设您在应用程序的生命周期中需要应用程序的“用户名”——这是一个很好的工具。不需要脏的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

现在,如果你有两个 TextViews 并且它们都应该具有相同的行为,请让它们使用 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 类变成一段痛苦的代码——你的代码看起来像“意大利面条”。例如,不要有这样的东西:

@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 here 的一些示例应用程序。看看他们是如何以一种好的和正确的方式实现的。

最后一句话;保持代码干净,以合乎逻辑的方式命名变量和方法,尤其是正确的方式。始终,始终了解您在代码中的位置 - 这非常重要。

【讨论】:

对于你的抽象工具类不要那样做。组成超过继承人。改为那样做:en.wikipedia.org/wiki/Composition_over_inheritance @Robert Massaioli:这也不是继承——只是一个带有静态方法的工具类;需要时可用。 啊,对,你把我弄糊涂了,因为你说这个类永远不会被实例化或子类化的通常方式是给它一个私有构造函数。如果您的所有方法都是公共静态的,那么您无论如何都可以直接从任何地方调用它。我对你使用“抽象”感到困惑。 @Robert - 在摘要中? - Pompe,+1 - 非常有用的东西。 将类命名为Util 而不是Tool 更为常见【参考方案2】:

从业余的角度来看,我不希望我的第一次尝试是一个干净的、可用于生产的应用程序。我有时会吃意大利面、意大利宽面条,甚至是馄饨代码。那时,我尝试从代码中重新思考我最不喜欢什么,并寻找更好的替代方案:

重新考虑您的类以更好地描述您的对象, 尽量减少每个方法中的代码, 尽可能避免依赖静态变量, 将线程用于昂贵的任务,不要将它们用于快速过程, 将 UI 与应用逻辑分开(将其保留在您的类中), 尽可能保留私有字段:当您想更改班级时,这会很有帮助, 遍历这些直到您喜欢代码

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

正如 OceanBlue 指出的那样,可能并不清楚 final static 变量不会造成任何危险,但 可以 更改的公共静态变量。这不是静态本身的问题,而是它们将具有值然后发现值改变的概念。可能很难发现问题出在哪里。典型示例是点击计数器或计时器值,此时可能有多个视图被点击或多个计时器。

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

【讨论】:

@Aleamdam:你强调不使用静态。我想知道您对使用“public final static”值有何看法?本质上是商业价值,而不是硬编码,可以放在一个单独的类中,例如:MyBusinessDefs.java? @OceanBlue 我强调不要依赖静态的公共变量,这些变量在线程执行期间可能会发生变化。当然,final static 变量不会造成这个问题。事实上,我更喜欢它的使用,因为它们不仅可以在需要时轻松更改,而且还有助于提高阅读清晰度。我将在我的回答中进一步澄清这一点。【参考方案3】:

如果处理 UI 是您最关心的问题,那么您需要掌握事件驱动编码。事件驱动编码背后的理念是所有现代 UI 系统背后的理念,并且在各种事物(不仅仅是 UI)中都有用。

我在学习时想到的最简单的方法就是将每个组件和事件视为独立的。您只需要担心传递给您的事件方法的事件对象。如果您习惯于编写基本上从头到尾运行的应用程序,这有点转变思路,但练习会让您很快达到目标。

【讨论】:

【参考方案4】:

如何使用模型视图控制器模式?

至少您必须在“模型”(对象或对象集)中进行隔离 所有状态及其逻辑管理,并具有 一个单独的类(可能是 Activity 类) 所有与视图、侦听器、回调相关的东西……)

【讨论】:

以上是关于异步编程最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

Java 异步编程最佳实践

异步编程最佳实践

异步编程你必须知道的6个最佳实践

C#网络编程的最佳实践

结合异步模型,再次总结Netty多线程编码最佳实践

角度 9 - 异步最佳实践?