2013-01-17 147 views
24

Android示例中的「Login」實現了AsyncTask作爲非靜態內部類。然而,根據Commonsguys的說法,這個類應該是靜態的,並且使用對外部活動see this的弱引用。實現AsyncTask的正確方法是什麼?靜態或非靜態嵌套類?

那麼實施AsyncTask的正確方法是什麼?靜態還是非靜態?

Commonsguy實施
https://github.com/commonsguy/cw-android/tree/master/Rotation/RotationAsync/

登錄例子中,谷歌

package com.example.asynctaskdemo; 

import android.animation.Animator; 
import android.animation.AnimatorListenerAdapter; 
import android.annotation.TargetApi; 
import android.app.Activity; 
import android.os.AsyncTask; 
import android.os.Build; 
import android.os.Bundle; 
import android.text.TextUtils; 
import android.view.KeyEvent; 
import android.view.Menu; 
import android.view.View; 
import android.view.inputmethod.EditorInfo; 
import android.widget.EditText; 
import android.widget.TextView; 

/** 
* Activity which displays a login screen to the user, offering registration as 
* well. 
*/ 
public class LoginActivity extends Activity { 
    /** 
    * A dummy authentication store containing known user names and passwords. 
    * TODO: remove after connecting to a real authentication system. 
    */ 
    private static final String[] DUMMY_CREDENTIALS = new String[] { "[email protected]:hello", "[email protected]:world" }; 

    /** 
    * The default email to populate the email field with. 
    */ 
    public static final String EXTRA_EMAIL = "com.example.android.authenticatordemo.extra.EMAIL"; 

    /** 
    * Keep track of the login task to ensure we can cancel it if requested. 
    */ 
    private UserLoginTask mAuthTask = null; 

    // Values for email and password at the time of the login attempt. 
    private String mEmail; 
    private String mPassword; 

    // UI references. 
    private EditText mEmailView; 
    private EditText mPasswordView; 
    private View mLoginFormView; 
    private View mLoginStatusView; 
    private TextView mLoginStatusMessageView; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 

     setContentView(R.layout.activity_login); 

     // Set up the login form. 
     mEmail = getIntent().getStringExtra(EXTRA_EMAIL); 
     mEmailView = (EditText) findViewById(R.id.email); 
     mEmailView.setText(mEmail); 

     mPasswordView = (EditText) findViewById(R.id.password); 
     mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { 
      @Override 
      public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { 
       if (id == R.id.login || id == EditorInfo.IME_NULL) { 
        attemptLogin(); 
        return true; 
       } 
       return false; 
      } 
     }); 

     mLoginFormView = findViewById(R.id.login_form); 
     mLoginStatusView = findViewById(R.id.login_status); 
     mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message); 

     findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View view) { 
       attemptLogin(); 
      } 
     }); 
    } 

    @Override 
    public boolean onCreateOptionsMenu(Menu menu) { 
     super.onCreateOptionsMenu(menu); 
     getMenuInflater().inflate(R.menu.activity_login, menu); 
     return true; 
    } 

    /** 
    * Attempts to sign in or register the account specified by the login form. 
    * If there are form errors (invalid email, missing fields, etc.), the 
    * errors are presented and no actual login attempt is made. 
    */ 
    public void attemptLogin() { 
     if (mAuthTask != null) { 
      return; 
     } 

     // Reset errors. 
     mEmailView.setError(null); 
     mPasswordView.setError(null); 

     // Store values at the time of the login attempt. 
     mEmail = mEmailView.getText().toString(); 
     mPassword = mPasswordView.getText().toString(); 

     boolean cancel = false; 
     View focusView = null; 

     // Check for a valid password. 
     if (TextUtils.isEmpty(mPassword)) { 
      mPasswordView.setError(getString(R.string.error_field_required)); 
      focusView = mPasswordView; 
      cancel = true; 
     } 
     else if (mPassword.length() < 4) { 
      mPasswordView.setError(getString(R.string.error_invalid_password)); 
      focusView = mPasswordView; 
      cancel = true; 
     } 

     // Check for a valid email address. 
     if (TextUtils.isEmpty(mEmail)) { 
      mEmailView.setError(getString(R.string.error_field_required)); 
      focusView = mEmailView; 
      cancel = true; 
     } 
     else if (!mEmail.contains("@")) { 
      mEmailView.setError(getString(R.string.error_invalid_email)); 
      focusView = mEmailView; 
      cancel = true; 
     } 

     if (cancel) { 
      // There was an error; don't attempt login and focus the first 
      // form field with an error. 
      focusView.requestFocus(); 
     } 
     else { 
      // Show a progress spinner, and kick off a background task to 
      // perform the user login attempt. 
      mLoginStatusMessageView.setText(R.string.login_progress_signing_in); 
      showProgress(true); 
      mAuthTask = new UserLoginTask(); 
      mAuthTask.execute((Void) null); 
     } 
    } 

    /** 
    * Shows the progress UI and hides the login form. 
    */ 
    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) 
    private void showProgress(final boolean show) { 
     // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow 
     // for very easy animations. If available, use these APIs to fade-in 
     // the progress spinner. 
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { 
      int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); 

      mLoginStatusView.setVisibility(View.VISIBLE); 
      mLoginStatusView.animate().setDuration(shortAnimTime).alpha(show ? 1 : 0).setListener(new AnimatorListenerAdapter() { 
       @Override 
       public void onAnimationEnd(Animator animation) { 
        mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); 
       } 
      }); 

      mLoginFormView.setVisibility(View.VISIBLE); 
      mLoginFormView.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1).setListener(new AnimatorListenerAdapter() { 
       @Override 
       public void onAnimationEnd(Animator animation) { 
        mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); 
       } 
      }); 
     } 
     else { 
      // The ViewPropertyAnimator APIs are not available, so simply show 
      // and hide the relevant UI components. 
      mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); 
      mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); 
     } 
    } 

    /** 
    * Represents an asynchronous login/registration task used to authenticate 
    * the user. 
    */ 
    public class UserLoginTask extends AsyncTask<Void, Void, Boolean> { 
     @Override 
     protected Boolean doInBackground(Void... params) { 
      // TODO: attempt authentication against a network service. 

      try { 
       // Simulate network access. 
       Thread.sleep(2000); 
      } 
      catch (InterruptedException e) { 
       return false; 
      } 

      for (String credential : DUMMY_CREDENTIALS) { 
       String[] pieces = credential.split(":"); 
       if (pieces[0].equals(mEmail)) { 
        // Account exists, return true if the password matches. 
        return pieces[1].equals(mPassword); 
       } 
      } 

      // TODO: register the new account here. 
      return true; 
     } 

     @Override 
     protected void onPostExecute(final Boolean success) { 
      mAuthTask = null; 
      showProgress(false); 

      if (success) { 
       finish(); 
      } 
      else { 
       mPasswordView.setError(getString(R.string.error_incorrect_password)); 
       mPasswordView.requestFocus(); 
      } 
     } 

     @Override 
     protected void onCancelled() { 
      mAuthTask = null; 
      showProgress(false); 
     } 
    } 
} 

如果這取決於具體的情況,然後用ListView項目(文本+加位圖)從裝互聯網使用HttpClient,我應該如何實現我的AsyncTask?

回答

13

一般來說,我會建議靜態實現(雖然都可以接受)。

谷歌方法將需要更少的代碼,但你的asynctask將與你的actitivy緊密耦合(這意味着不容易重用)。但有時這種方法更具可讀性。使用CommonsGuy方法,需要更多的努力(以及更多的代碼)來分離活動和異類,但最終你會得到更多模塊化,更可重用的代碼。

+1

根據我的理解,非靜態嵌套類在類之外保留引用。如果用戶突然取消當前活動(點擊後退按鈕),而仍有幾個任務仍在線程池中排隊等待。那麼這種方法(非靜態)可能會造成內存泄漏,因爲GC將無法回收該活動的內存。我是否正確理解這一點?順便說一句,非常感謝。 – Chan

+1

@Chan AsyncTasks容易泄漏,內部和外部嵌套。如果設備更改配置並重新創建活動,則很容易忘記取消舊任務,然後將其留在bg中運行。 –

+0

@MisterSmith:謝謝。那麼還有其他的替代方法嗎? – Chan

2

鏈接的文章已經說明了

這並強調,雖然,你希望你的AsyncTask的doInBackground()從活動完全脫鉤。如果你只在主應用程序線程上觸摸你的Activity,你的AsyncTask可以在方向改變中保持不變。

不要觸摸活動從AsyncTask(例如它的成員),這是符合Static Nested Classes

靜態嵌套類
與類方法和變量,靜態嵌套類與其外部類相關聯。像靜態類方法一樣,靜態嵌套類不能直接引用其封閉類中定義的實例變量或方法 - 它只能通過對象引用來使用它們。

雖然,從機器人,AsyncTask referenceUsing AsyncTask仍在使用非靜態嵌套類的例子。

並據此Static nested class in Java, why?,我會先用靜態內部類去,只能求助於非靜態版本,如果確實有必要。

14

實施AsyncTask沒有單一的「正確」方法。但這裏是我的兩分錢:

該課程旨在在活動環境中執行「輕量級」工作。這就是爲什麼它具有在UI線程中運行的方法onPreExecute,onProgressUpdateonPostExecute,以便他們可以快速訪問字段並更新GUI。任何可能需要更長時間才能完成的任務並不意味着更新特定的活動,因此應將其移至服務中。

這些方法主要用於更新GUI。由於GUI與Activity實例相關(這些字段可能被聲明爲私有成員變量),因此將AsyncTask作爲非靜態嵌套類實現會更方便。這也是我認爲最自然的方式。

如果任務將在其他活動中重用,我認爲應該允許它有自己的類。說實話,我不是靜態嵌套類的粉絲,尤其是內部視圖。如果它是一個類,則意味着它在概念上與活動不同。如果它是靜態的,則意味着它與該活動的具體實例無關。但是,因爲它們是嵌套的,所以這些類在父類中可視化,使其難以閱讀,並且可以在項目包資源管理器中不被注意,因爲它只顯示文件。儘管與內部類的耦合度較低,但這並不是很有用:如果類更改,則必須將整個父文件合併/提交到版本控制。如果你在哪裏重複使用它,那麼你必須在任何地方以Parent.Nested的身份訪問它。因此,爲了不將其他活動耦合到Parent類,您可能想重構它並將嵌套類提取到它自己的文件中。

因此,對於我來說,問題將是內部類與頂級類。

相關問題