【问题标题】:How to modell an Android AsyncTask class?如何建模 Android AsyncTask 类?
【发布时间】:2012-01-07 22:40:34
【问题描述】:

我决定学习 Scala / Play(服务器端),并决定同时学习 Android(客户端)游戏开发,为开发增添趣味。
我有一个关于如何在 Android 中为 HTTP 请求做一个好的设计的问题。
据我了解,最好的方法是将 HTTP 请求委托给扩展抽象 AsyncTask 类的类。
您是否必须为您覆盖的 doInBackground 方法中的每个不同逻辑对 AsyncTask 进行新的扩展?
对我来说,为每个请求逻辑都有一个类并不自然,而是将几个连贯的方法封装在一个类中。

我刚开始玩一点,但我对设计不满意,因为我不喜欢doInBackground(Object... params) 中的可变参数对象的设计。
通过这种设计,我失去了类型安全性,并且 params 对象远非直观,而直观是我在代码中所追求的。

这是我要改进的代码。

public class GameActivity extends Activity {

    private class MyCellListener implements ICellListener {
        public void onCellSelected() {
            ServerProxy.postSelectedCell(row, col, player.getUser());    
            ...
            // ServerProxy.other();

public class ServerProxy extends AsyncTask<Object, Void, Void>{

    private static final String TAG = ServerProxy.class.getSimpleName();
    private static final String SERVER_ADDRESS = "http://127.0.0.1";

    // Prevent external instantiation
    private ServerProxy(){};

    public static void postSelectedCell(int row, int cell, User user){
         List<NameValuePair> postParameters = new ArrayList<NameValuePair>(3);
         postParameters.add(new BasicNameValuePair("row", String.valueOf(row)));
         postParameters.add(new BasicNameValuePair("cell", String.valueOf(cell)));
         postParameters.add(new BasicNameValuePair("userName", user.getUserName()));
         new ServerProxy().doInBackground("setSelectedCell" , postParameters);
    }

//    public static void postOther() {
//      new ServerProxy().doInBackground("other" , //some parameters); 
//    }

    /**
     * @param postParameters First object URL postfix<br/>
     * Second parameter is post parameters inform of {@code List<NameValuePair>}
     * @return null
     */
    @SuppressWarnings("unchecked")
    @Override
    protected Void doInBackground(Object... params) {

        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost(SERVER_ADDRESS +"/" + params[0]);
        httppost.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);

        try {
            httppost.setEntity(new UrlEncodedFormEntity((List<NameValuePair>) params[1]));
            httpclient.execute(httppost);
        } catch (ClientProtocolException e) {
         Log.e(TAG,e.toString());
        } catch (IOException e) {
         Log.e(TAG,e.toString());
        }
        return null;
    }
}

【问题讨论】:

    标签: java android oop http asynchronous


    【解决方案1】:

    我想你不明白 AsyncTask 是做什么的。您不能在单个 AsyncTask 子类上定义诸如 postLastName、postFirstName、postWhatever 之类的方法,并让它在 UI 线程上执行该方法调用。 AsyncTask 旨在使执行需要更新 UI 的后台作业变得更容易,而无需强制您直接处理线程。

    为了与您使用 HttpClient 的服务器通信,调用 HttpClient.execute() 将阻塞,直到服务器的响应返回。这可能需要很长时间,尤其是在服务器繁忙、死机或蜂窝无线电出现故障的情况下。当该调用花费太长时间时,您不希望您的 UI 停止响应用户。你想显示一个旋转的微调器,让用户知道正在发生一些事情。如果您使用您提供的代码,您的 UI 将停止绘制,直到 HttpClient.execute() 调用返回,因为您在 UI 线程上调用它。

    解决此问题的方法是将此调用从 UI 线程移至另一个线程。让该线程等待响应,然后让它通知 UI 线程它已完成并使用 UI 线程使用新数据更新 UI。为什么不能让后台线程更新 UI?因为这会违反 Android 的线程规则,即只有 UI 线程才能更新 UI。

    AsyncTask 允许您在 UI 线程 (doInBackground()) 之外运行某些内容,并将返回值发布到 UI 线程 (onPostExecute()),这样它就可以在不违反 Android 规则的情况下安全地更新 UI。您不直接调用 doInBackground() 或 onPostExecute() 而是调用 AsyncTask.execute() 并且 AsyncTask 中的代码将在后台线程上调用 doInBackground() ,当它完成时它将在 UI 线程上调用 onPostExecute() .这样你就不会纠结于自己做所有的线程。

    现在,让多个请求通过单个子类运行的想法不会奏效,因为 AsyncTask 将进行服务调用的两个部分联系在一起:执行服务调用以获取响应的详细信息,以及如何处理它更新 UI 的响应。如果调用 postFirstName() 方法,则在返回响应后执行的操作可能与调用 postLastName() 不同。而且因为它不同意味着你不能只为所有这些不同的调用定义一个 AsyncTask。您可以通过组合或子类化基类来共享代码,但是您必须为要在服务器上执行的每个唯一操作创建一个子类。所以每次调用都要考虑 Class 而不是 Method。

    您不必使用传递给 doInBackground() 的参数。如果您有多种类型的参数要传递给 AsyncTask。将它们传递给构造函数。由于您不能重用 AsyncTask 实例(即每个实例不能多次调用 AsyncTask.execute() )。它们的生命周期必须是实例化、执行()和折腾。这意味着在构造函数中传递输入不会影响您使用 AsyncTask 的能力。

    我编写了我自己的 AsyncTask 版本,它分离出三个回调方法: success() 用于处理从 doInBackground() 返回的结果;当 doInBackground() 抛出任何异常时调用 handleException();并且无论 doInBackground() 调用 doFinally() 返回某些内容或引发异常。默认情况下,无论错误或成功,都会调用 AsyncTask onPostExecute()。它看起来像这样:

    public class MyTask extends EnhancedAsyncTask<Param,Integer,MyResult> {
        MyParam1 param1;
        MyParam2 param2;
    
        public MyTask( MyParam1 param1, MyParam2 param2 ) {
            this.param1 = param;
            this.param2 = param2;
        }
    
        protected MyResult doInBackground( Param... params ) {
            // do server work here
            server.send( param1, param2 );
        }
    
        protected void success( MyResult result ) {
            // do Update of the UI
        }
    
        protected void handleException( Exception ex ) {
           // show an error here
        }
    }
    
    // Now to use this AsyncTask you would do something like this:
    
    MyTask task = new MyTask( param1, param2 ).execute();
    

    通常我会以匿名内部类的形式执行此操作,因此不需要传递对 UI 的引用,但同样,如果您不使用 Anon 类,您可以将它们传递给构造函数。如果 Activity 被销毁(例如用户翻转屏幕),您只需要小心不要触摸 UI。

    【讨论】:

    • 感谢您的回答,但我认为您误解了我的代码/问题,或者我的措辞很糟糕。您写道,如果我进行两次往返,我需要两个实例。我将拥有它,因为我所有的公共方法调用都使用 new ServerProxy() 实例化一个新实例。如果我错了,请纠正我,因为您的代码看起来不错,但它根本没有解决我的问题?问题是在一个类中有许多不同类型的请求。例如,单个 Server 类,以某种方式扩展 AsyncTask,包含许多方法 postFirstName、postLastName、getAllFriends 等,所有这些方法都具有唯一的参数。
    • 我更新了我的答案以更好地解释 AyncTask 的实际作用,因为我认为您不了解它的目的。
    【解决方案2】:

    在设计之前,有些事情看起来你做错了。

    • 你不应该像这里一样直接调用doInBackground

      new ServerProxy().doInBackground("setSelectedCell" , postParameters);

      相反,它应该调用任何version of execute

    至于设计,你可以试试这个

    • 您可以创建一个ServerProxy 类并且不要从AsyncTask 扩展它。只需使用它来封装您的逻辑即可。
    • 现在您可以创建一个ServerProxyAsync,它将ServerProxy 作为组合。
    • 现在你可以做的一个选择是

      公共类 ServerProxy 扩展 AsyncTask

      这里的String 可能是您要在ServerProxy 实例上调用的方法名称,您可以为此使用反射。

    注意:这个问题可能还有其他解决方案,这只是该问题的第一次尝试

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-30
      • 1970-01-01
      相关资源
      最近更新 更多