Estou trabalhando em um aplicativo de smartphone / tablet, usando apenas um APK e carregando recursos conforme necessário, dependendo do tamanho da tela, a melhor escolha de design parece ser usar Fragments via ACL.
Este aplicativo tem funcionado bem até agora sendo apenas baseado em atividades. Esta é uma aula simulada de como eu lido com AsyncTasks e ProgressDialogs nas Atividades para que funcionem mesmo quando a tela é girada ou ocorre uma alteração na configuração durante a comunicação.
Não vou alterar o manifesto para evitar a recriação da Activity, há muitos motivos pelos quais não quero fazer isso, mas principalmente porque os documentos oficiais dizem que não é recomendado e eu consegui sem ele até agora, então por favor não recomende isso rota.
public class Login extends Activity {
static ProgressDialog pd;
AsyncTask<String, Void, Boolean> asyncLoginThread;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.login);
//SETUP UI OBJECTS
restoreAsyncTask();
}
@Override
public Object onRetainNonConfigurationInstance() {
if (pd != null) pd.dismiss();
if (asyncLoginThread != null) return (asyncLoginThread);
return super.onRetainNonConfigurationInstance();
}
private void restoreAsyncTask();() {
pd = new ProgressDialog(Login.this);
if (getLastNonConfigurationInstance() != null) {
asyncLoginThread = (AsyncTask<String, Void, Boolean>) getLastNonConfigurationInstance();
if (asyncLoginThread != null) {
if (!(asyncLoginThread.getStatus()
.equals(AsyncTask.Status.FINISHED))) {
showProgressDialog();
}
}
}
}
public class LoginThread extends AsyncTask<String, Void, Boolean> {
@Override
protected Boolean doInBackground(String... args) {
try {
//Connect to WS, recieve a JSON/XML Response
//Place it somewhere I can use it.
} catch (Exception e) {
return true;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (result) {
pd.dismiss();
//Handle the response. Either deny entry or launch new Login Succesful Activity
}
}
}
}
Este código está funcionando bem, tenho cerca de 10.000 usuários sem reclamação, então parecia lógico apenas copiar essa lógica para o novo Fragment Based Design, mas, é claro, não está funcionando.
Aqui está o LoginFragment:
public class LoginFragment extends Fragment {
FragmentActivity parentActivity;
static ProgressDialog pd;
AsyncTask<String, Void, Boolean> asyncLoginThread;
public interface OnLoginSuccessfulListener {
public void onLoginSuccessful(GlobalContainer globalContainer);
}
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
//Save some stuff for the UI State
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setRetainInstance(true);
//If I setRetainInstance(true), savedInstanceState is always null. Besides that, when loading UI State, a NPE is thrown when looking for UI Objects.
parentActivity = getActivity();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
loginSuccessfulListener = (OnLoginSuccessfulListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnLoginSuccessfulListener");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RelativeLayout loginLayout = (RelativeLayout) inflater.inflate(R.layout.login, container, false);
return loginLayout;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//SETUP UI OBJECTS
if(savedInstanceState != null){
//Reload UI state. Im doing this properly, keeping the content of the UI objects, not the object it self to avoid memory leaks.
}
}
public class LoginThread extends AsyncTask<String, Void, Boolean> {
@Override
protected Boolean doInBackground(String... args) {
try {
//Connect to WS, recieve a JSON/XML Response
//Place it somewhere I can use it.
} catch (Exception e) {
return true;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (result) {
pd.dismiss();
//Handle the response. Either deny entry or launch new Login Succesful Activity
}
}
}
}
}
Não posso usar onRetainNonConfigurationInstance()
porque tem que ser chamado da Activity e não do Fragment, o mesmo acontece getLastNonConfigurationInstance()
. Eu li algumas perguntas semelhantes aqui sem resposta.
Eu entendo que pode ser necessário algum trabalho ao redor para que essas coisas sejam organizadas adequadamente em fragmentos. Dito isso, gostaria de manter a mesma lógica básica de design.
Qual seria a maneira adequada de reter o AsyncTask durante uma alteração de configuração e, se ainda estiver em execução, mostrar um progressDialog, levando em consideração que o AsyncTask é uma classe interna ao Fragment e é o próprio Fragment que invoca o AsyncTask.execute ()?
Fragment
sRespostas:
Na verdade, os fragmentos podem tornar isso muito mais fácil. Basta usar o método Fragment.setRetainInstance (boolean) para que sua instância de fragmento seja mantida durante as alterações de configuração. Observe que esta é a substituição recomendada para Activity.onRetainnonConfigurationInstance () nos documentos.
Se, por algum motivo, você realmente não quiser usar um fragmento retido, existem outras abordagens que você pode adotar. Observe que cada fragmento possui um identificador exclusivo retornado por Fragment.getId () . Você também pode descobrir se um fragmento está sendo dividido para uma alteração de configuração por meio de Fragment.getActivity (). IsChangingConfigurations () . Então, no ponto em que você decidir parar seu AsyncTask (em onStop () ou onDestroy () provavelmente), você pode, por exemplo, verificar se a configuração está mudando e, se assim for, colocá-la em um SparseArray estático sob o identificador do fragmento, e, em seguida, em seu onCreate () ou onStart (), verifique se você tem uma AsyncTask no array esparso disponível.
fonte
onPostExecute
método ainda precisa aguarde antes de ser finalmente processado pela fila de mensagens do thread principal.Acho que você vai gostar do meu exemplo extremamente abrangente e funcional detalhado a seguir.
Editar
Conforme solicitado por Brad Larson, reproduzi a maior parte da solução vinculada abaixo. Além disso, desde que o postei, fui apontado
AsyncTaskLoader
. Não tenho certeza se é totalmente aplicável aos mesmos problemas, mas você deve dar uma olhada de qualquer maneira.Usando
AsyncTask
com diálogos de progresso e rotação do dispositivo.Uma solução funcional!
Finalmente coloquei tudo para funcionar. Meu código tem os seguintes recursos:
Fragment
cujo layout muda com a orientação.AsyncTask
no qual você pode fazer algum trabalho.DialogFragment
que mostra o progresso da tarefa em uma barra de progresso (não apenas um botão giratório indeterminado).Não acho que essa combinação de funcionalidade possa ser encontrada em nenhum outro lugar.
A ideia básica é a seguinte. Existe uma
MainActivity
classe que contém um único fragmento -MainFragment
.MainFragment
tem layouts diferentes para orientação horizontal e vertical esetRetainInstance()
é falso para que o layout possa ser alterado. Isto significa que quando a orientação do dispositivo é alterada, ambosMainActivity
eMainFragment
são completamente destruídas e recriado.Separadamente, temos
MyTask
(estendido deAsyncTask
) que faz todo o trabalho. Não podemos armazená-loMainFragment
porque será destruído e o Google suspendeu o uso de qualquer coisa parecidasetRetainNonInstanceConfiguration()
. Isso nem sempre está disponível e é um hack feio na melhor das hipóteses. Em vez disso, armazenaremosMyTask
em outro fragmento, umDialogFragment
chamadoTaskFragment
.Este fragmento irá tersetRetainInstance()
definida como verdadeiro, de modo a rotação do dispositivo este fragmento não é destruída, eMyTask
é retida.Finalmente, precisamos dizer ao
TaskFragment
quem informar quando estiver concluído, e fazemos isso usandosetTargetFragment(<the MainFragment>)
quando o criamos. Quando o dispositivo é girado, oMainFragment
é destruído e uma nova instância é criada, usamos oFragmentManager
para localizar a caixa de diálogo (com base em sua tag) e fazersetTargetFragment(<the new MainFragment>)
. É basicamente isso.Havia duas outras coisas que eu precisava fazer: primeiro cancelar a tarefa quando a caixa de diálogo for descartada e, em segundo lugar, definir a mensagem de dispensar como nula, caso contrário, a caixa de diálogo será estranhamente descartada quando o dispositivo for girado.
O código
Não vou listar os layouts, eles são bastante óbvios e você pode encontrá-los no download do projeto abaixo.
Atividade principal
Isso é muito simples. Eu adicionei um retorno de chamada a esta atividade para que ela saiba quando a tarefa for concluída, mas você pode não precisar disso. Principalmente, eu só queria mostrar o mecanismo de retorno de chamada de atividade de fragmento porque é muito legal e você pode não ter visto antes.
public class MainActivity extends Activity implements MainFragment.Callbacks { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void onTaskFinished() { // Hooray. A toast to our success. Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show(); // NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't* // the duration in milliseconds. ANDROID Y U NO ENUM? } }
MainFragment
É longo, mas vale a pena!
public class MainFragment extends Fragment implements OnClickListener { // This code up to onDetach() is all to get easy callbacks to the Activity. private Callbacks mCallbacks = sDummyCallbacks; public interface Callbacks { public void onTaskFinished(); } private static Callbacks sDummyCallbacks = new Callbacks() { public void onTaskFinished() { } }; @Override public void onAttach(Activity activity) { super.onAttach(activity); if (!(activity instanceof Callbacks)) { throw new IllegalStateException("Activity must implement fragment's callbacks."); } mCallbacks = (Callbacks) activity; } @Override public void onDetach() { super.onDetach(); mCallbacks = sDummyCallbacks; } // Save a reference to the fragment manager. This is initialised in onCreate(). private FragmentManager mFM; // Code to identify the fragment that is calling onActivityResult(). We don't really need // this since we only have one fragment to deal with. static final int TASK_FRAGMENT = 0; // Tag so we can find the task fragment again, in another instance of this fragment after rotation. static final String TASK_FRAGMENT_TAG = "task"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // At this point the fragment may have been recreated due to a rotation, // and there may be a TaskFragment lying around. So see if we can find it. mFM = getFragmentManager(); // Check to see if we have retained the worker fragment. TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG); if (taskFragment != null) { // Update the target fragment so it goes to this fragment instead of the old one. // This will also allow the GC to reclaim the old MainFragment, which the TaskFragment // keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't // use weak references. To be sure you aren't leaking, you may wish to make your own // setTargetFragment() which does. taskFragment.setTargetFragment(this, TASK_FRAGMENT); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_main, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // Callback for the "start task" button. I originally used the XML onClick() // but it goes to the Activity instead. view.findViewById(R.id.taskButton).setOnClickListener(this); } @Override public void onClick(View v) { // We only have one click listener so we know it is the "Start Task" button. // We will create a new TaskFragment. TaskFragment taskFragment = new TaskFragment(); // And create a task for it to monitor. In this implementation the taskFragment // executes the task, but you could change it so that it is started here. taskFragment.setTask(new MyTask()); // And tell it to call onActivityResult() on this fragment. taskFragment.setTargetFragment(this, TASK_FRAGMENT); // Show the fragment. // I'm not sure which of the following two lines is best to use but this one works well. taskFragment.show(mFM, TASK_FRAGMENT_TAG); // mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK) { // Inform the activity. mCallbacks.onTaskFinished(); } }
TaskFragment
// This and the other inner class can be in separate files if you like. // There's no reason they need to be inner classes other than keeping everything together. public static class TaskFragment extends DialogFragment { // The task we are running. MyTask mTask; ProgressBar mProgressBar; public void setTask(MyTask task) { mTask = task; // Tell the AsyncTask to call updateProgress() and taskFinished() on this fragment. mTask.setFragment(this); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Retain this instance so it isn't destroyed when MainActivity and // MainFragment change configuration. setRetainInstance(true); // Start the task! You could move this outside this activity if you want. if (mTask != null) mTask.execute(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_task, container); mProgressBar = (ProgressBar)view.findViewById(R.id.progressBar); getDialog().setTitle("Progress Dialog"); // If you're doing a long task, you probably don't want people to cancel // it just by tapping the screen! getDialog().setCanceledOnTouchOutside(false); return view; } // This is to work around what is apparently a bug. If you don't have it // here the dialog will be dismissed on rotation, so tell it not to dismiss. @Override public void onDestroyView() { if (getDialog() != null && getRetainInstance()) getDialog().setDismissMessage(null); super.onDestroyView(); } // Also when we are dismissed we need to cancel the task. @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); // If true, the thread is interrupted immediately, which may do bad things. // If false, it guarantees a result is never returned (onPostExecute() isn't called) // but you have to repeatedly call isCancelled() in your doInBackground() // function to check if it should exit. For some tasks that might not be feasible. if (mTask != null) { mTask.cancel(false); } // You don't really need this if you don't want. if (getTargetFragment() != null) getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_CANCELED, null); } @Override public void onResume() { super.onResume(); // This is a little hacky, but we will see if the task has finished while we weren't // in this activity, and then we can dismiss ourselves. if (mTask == null) dismiss(); } // This is called by the AsyncTask. public void updateProgress(int percent) { mProgressBar.setProgress(percent); } // This is also called by the AsyncTask. public void taskFinished() { // Make sure we check if it is resumed because we will crash if trying to dismiss the dialog // after the user has switched to another app. if (isResumed()) dismiss(); // If we aren't resumed, setting the task to null will allow us to dimiss ourselves in // onResume(). mTask = null; // Tell the fragment that we are done. if (getTargetFragment() != null) getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_OK, null); } }
Minha tarefa
// This is a fairly standard AsyncTask that does some dummy work. public static class MyTask extends AsyncTask<Void, Void, Void> { TaskFragment mFragment; int mProgress = 0; void setFragment(TaskFragment fragment) { mFragment = fragment; } @Override protected Void doInBackground(Void... params) { // Do some longish task. This should be a task that we don't really // care about continuing // if the user exits the app. // Examples of these things: // * Logging in to an app. // * Downloading something for the user to view. // * Calculating something for the user to view. // Examples of where you should probably use a service instead: // * Downloading files for the user to save (like the browser does). // * Sending messages to people. // * Uploading data to a server. for (int i = 0; i < 10; i++) { // Check if this has been cancelled, e.g. when the dialog is dismissed. if (isCancelled()) return null; SystemClock.sleep(500); mProgress = i * 10; publishProgress(); } return null; } @Override protected void onProgressUpdate(Void... unused) { if (mFragment == null) return; mFragment.updateProgress(mProgress); } @Override protected void onPostExecute(Void unused) { if (mFragment == null) return; mFragment.taskFinished(); } } }
Baixe o projeto de exemplo
Aqui está o código-fonte e o APK . Desculpe, o ADT insistiu em adicionar a biblioteca de suporte antes de permitir que eu fizesse um projeto. Tenho certeza que você pode removê-lo.
fonte
DialogFragment
, porque ela tem elementos de interface do usuário que contêm referências ao contexto antigo. Em vez disso, armazenariaAsyncTask
em outro fragmento vazio e definiriaDialogFragment
como seu destino.onCreateView()
chamado novamente? O antigomProgressBar
será substituído pelo menos por um novo.mProgressBar = null;
emonDestroyView()
se você quiser ser extra certeza. O método da Singularidade pode ser uma boa ideia, mas aumentará ainda mais a complexidade do código!mTask.execute()
paraMainFragment.onClick()
. Alternativamente, você pode permitir que parâmetros sejam passadossetTask()
ou até mesmo armazená-los neleMyTask
mesmo. Não tenho certeza do que sua primeira pergunta significa, mas talvez você queira usarTaskFragment.getTargetFragment()
? Tenho certeza de que funcionará usando umViewPager
. MasViewPagers
não são muito bem compreendidos ou documentados, então boa sorte! Lembre-se de que seu fragmento não é criado até que seja visível pela primeira vez.Publiquei recentemente um artigo que descreve como lidar com alterações de configuração usando
Fragment
s retidos . Ele resolve o problema de reter umAsyncTask
mudança em uma rotação bem.O TL; DR é usar o host
AsyncTask
dentro de umFragment
, chamarsetRetainInstance(true)
oFragment
e relatar oAsyncTask
progresso / resultados de volta a eleActivity
(ou a seu destinoFragment
, se você escolher usar a abordagem descrita por @Timmmm) por meio do retidoFragment
.fonte
onAttach
eonDetach
, será melhor se estiver dentroTaskFragment
, apenasgetActivity
ligamos sempre que precisamos disparar um retorno de chamada. (Verificando instaceofTaskCallbacks
)onAttach()
eonDetach()
pude evitar lançar constantemente a atividade paraTaskCallbacks
todas as vezes que eu quisesse usá-la.Minha primeira sugestão é evitar AsyncTasks internas , você pode ler uma pergunta que fiz sobre isso e as respostas: Android: Recomendações AsyncTask: classe privada ou classe pública?
Depois disso, comecei a usar o não-interno e ... agora vejo MUITOS benefícios.
A segunda é manter uma referência de sua AsyncTask em execução na
Application
classe - http://developer.android.com/reference/android/app/Application.htmlSempre que você iniciar uma AsyncTask, defina-a no aplicativo e, quando terminar, defina-a como nula.
Quando um fragmento / atividade começa, você pode verificar se alguma AsyncTask está em execução (verificando se é nula ou não no aplicativo) e, em seguida, definir a referência interna para o que quiser (atividade, fragmento etc para que você possa fazer callbacks).
Isso resolverá seu problema: Se você tiver apenas 1 AsyncTask em execução em um determinado momento, poderá adicionar uma referência simples:
AsyncTask<?,?,?> asyncTask = null;
Caso contrário, tenha na aplicação um HashMap com referências a eles.
A caixa de diálogo de progresso pode seguir exatamente o mesmo princípio.
fonte
Eu vim com um método de usar AsyncTaskLoaders para isso. É muito fácil de usar e requer menos sobrecarga de IMO.
Basicamente, você cria um AsyncTaskLoader como este:
public class MyAsyncTaskLoader extends AsyncTaskLoader { Result mResult; public HttpAsyncTaskLoader(Context context) { super(context); } protected void onStartLoading() { super.onStartLoading(); if (mResult != null) { deliverResult(mResult); } if (takeContentChanged() || mResult == null) { forceLoad(); } } @Override public Result loadInBackground() { SystemClock.sleep(500); mResult = new Result(); return mResult; } }
Então, em sua atividade que usa o AsyncTaskLoader acima quando um botão é clicado:
public class MyActivityWithBackgroundWork extends FragmentActivity implements LoaderManager.LoaderCallbacks<Result> { private String username,password; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.mylayout); //this is only used to reconnect to the loader if it already started //before the orientation changed Loader loader = getSupportLoaderManager().getLoader(0); if (loader != null) { getSupportLoaderManager().initLoader(0, null, this); } } public void doBackgroundWorkOnClick(View button) { //might want to disable the button while you are doing work //to prevent user from pressing it again. //Call resetLoader because calling initLoader will return //the previous result if there was one and we may want to do new work //each time getSupportLoaderManager().resetLoader(0, null, this); } @Override public Loader<Result> onCreateLoader(int i, Bundle bundle) { //might want to start a progress bar return new MyAsyncTaskLoader(this); } @Override public void onLoadFinished(Loader<LoginResponse> loginLoader, LoginResponse loginResponse) { //handle result } @Override public void onLoaderReset(Loader<LoginResponse> responseAndJsonHolderLoader) { //remove references to previous loader resources } }
Isso parece lidar bem com as mudanças de orientação e sua tarefa em segundo plano continuará durante a rotação.
Algumas coisas a serem observadas:
Tenho mais detalhes sobre como usar isso para chamadas http aqui
fonte
getSupportLoaderManager().getLoader(0);
não retornará nulo (porque o carregador com esse id, 0, ainda não existe)?Eu criei uma pequena biblioteca de tarefas em segundo plano de código aberto que é fortemente baseada no Marshmallow,
AsyncTask
mas com funcionalidades adicionais, como:A biblioteca usa internamente um
Fragment
sem qualquer interface de usuário, que é mantida durante as alterações de configuração (setRetainInstance(true)
).Você pode encontrá-lo no GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks
Exemplo mais básico (versão 0.2.0):
Este exemplo retém totalmente a tarefa, usando uma quantidade muito limitada de código.
Tarefa:
private class ExampleTask extends Task<Integer, String> { public ExampleTask(String tag){ super(tag); } protected String doInBackground() { for(int i = 0; i < 100; i++) { if(isCancelled()){ break; } SystemClock.sleep(50); publishProgress(i); } return "Result"; } }
Atividade:
public class Main extends TaskActivityCompat implements Task.Callback { @Override public void onClick(View view){ ExampleTask task = new ExampleTask("activity-unique-tag"); getTaskManager().execute(task, this); } @Override public Task.Callback onPreAttach(Task<?, ?> task) { //Restore the user-interface based on the tasks state return this; //This Activity implements Task.Callback } @Override public void onPreExecute(Task<?, ?> task) { //Task started } @Override public void onPostExecute(Task<?, ?> task) { //Task finished Toast.makeText(this, "Task finished", Toast.LENGTH_SHORT).show(); } }
fonte
Minha abordagem é usar o padrão de design de delegação, em geral, podemos isolar a lógica de negócios real (ler dados da Internet ou banco de dados ou qualquer outro) de AsyncTask (o delegador) para BusinessDAO (o delegado), em seu método AysncTask.doInBackground () , delegue a tarefa real ao BusinessDAO e, em seguida, implemente um mecanismo de processo singleton no BusinessDAO, de modo que várias chamadas para BusinessDAO.doSomething () acionarão apenas uma tarefa real em execução a cada vez e aguardando o resultado da tarefa. A ideia é reter o delegado (ou seja, BusinessDAO) durante a alteração da configuração, em vez do delegador (ou seja, AsyncTask).
Crie / implemente nosso próprio aplicativo, o objetivo é criar / inicializar o BusinessDAO aqui, de modo que o ciclo de vida do nosso BusinessDAO tenha o escopo do aplicativo, não do escopo da atividade, observe que você precisa alterar o AndroidManifest.xml para usar MyApplication:
public class MyApplication extends android.app.Application { private BusinessDAO businessDAO; @Override public void onCreate() { super.onCreate(); businessDAO = new BusinessDAO(); } pubilc BusinessDAO getBusinessDAO() { return businessDAO; } }
Nosso Activity / Fragement existente está praticamente inalterado, ainda implementa AsyncTask como uma classe interna e envolve AsyncTask.execute () de Activity / Fragement, a diferença agora é que AsyncTask delegará a tarefa real ao BusinessDAO, portanto, durante a mudança de configuração, uma segunda AsyncTask será inicializado e executado, e chamará BusinessDAO.doSomething () segunda vez; no entanto, a segunda chamada para BusinessDAO.doSomething () não acionará uma nova tarefa em execução, em vez disso, aguardará a conclusão da tarefa em execução atual:
public class LoginFragment extends Fragment { ... ... public class LoginAsyncTask extends AsyncTask<String, Void, Boolean> { // get a reference of BusinessDAO from application scope. BusinessDAO businessDAO = ((MyApplication) getApplication()).getBusinessDAO(); @Override protected Boolean doInBackground(String... args) { businessDAO.doSomething(); return true; } protected void onPostExecute(Boolean result) { //Handle task result and update UI stuff. } } ... ... }
Dentro do BusinessDAO, implemente o mecanismo do processo singleton, por exemplo:
public class BusinessDAO { ExecutorCompletionService<MyTask> completionExecutor = new ExecutorCompletionService<MyTask(Executors.newFixedThreadPool(1)); Future<MyTask> myFutureTask = null; public void doSomething() { if (myFutureTask == null) { // nothing running at the moment, submit a new callable task to run. MyTask myTask = new MyTask(); myFutureTask = completionExecutor.submit(myTask); } // Task already submitted and running, waiting for the running task to finish. myFutureTask.get(); } // If you've never used this before, Callable is similar with Runnable, with ability to return result and throw exception. private class MyTask extends Callable<MyTask> { public MyAsyncTask call() { // do your job here. return this; } } }
Não tenho 100% de certeza se isso vai funcionar, além disso, o trecho de código de amostra deve ser considerado como pseudocódigo. Estou apenas tentando dar-lhes algumas pistas do nível de design. Quaisquer comentários ou sugestões são bem-vindos e apreciados.
fonte
Você pode tornar o AsyncTask um campo estático. Se você precisa de um contexto, deve enviar o contexto do seu aplicativo. Isso evitará vazamentos de memória, caso contrário, você manterá uma referência para toda a sua atividade.
fonte
Se alguém encontrar o caminho para este thread, eu descobri que uma abordagem limpa seria executar a tarefa assíncrona de um
app.Service
(iniciado com START_STICKY) e, em seguida, recriar a iteração sobre os serviços em execução para descobrir se o serviço (e, portanto, a tarefa assíncrona) ainda está corrida;public boolean isServiceRunning(String serviceClassName) { final ActivityManager activityManager = (ActivityManager) Application.getContext().getSystemService(Context.ACTIVITY_SERVICE); final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE); for (RunningServiceInfo runningServiceInfo : services) { if (runningServiceInfo.service.getClassName().equals(serviceClassName)){ return true; } } return false; }
Se estiver, adicione novamente
DialogFragment
(ou qualquer outra coisa) e, se não estiver, certifique-se de que a caixa de diálogo foi descartada.Isso é particularmente pertinente se você estiver usando as
v4.support.*
bibliotecas, pois (no momento da escrita) elas têm problemas conhecidos com osetRetainInstance
método e a paginação de visualizações. Além disso, ao não reter a instância, você pode recriar sua atividade usando um conjunto diferente de recursos (ou seja, um layout de visualização diferente para a nova orientação)fonte
Eu escrevo o mesmo código para resolver este problema
A primeira etapa é criar a classe Application:
public class TheApp extends Application { private static TheApp sTheApp; private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>(); @Override public void onCreate() { super.onCreate(); sTheApp = this; } public static TheApp get() { return sTheApp; } public void registerTask(String tag, AsyncTask<?,?,?> task) { tasks.put(tag, task); } public void unregisterTask(String tag) { tasks.remove(tag); } public AsyncTask<?,?,?> getTask(String tag) { return tasks.get(tag); } }
Em AndroidManifest.xml
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" android:name="com.example.tasktest.TheApp">
Código em atividade:
public class MainActivity extends Activity { private Task1 mTask1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTask1 = (Task1)TheApp.get().getTask("task1"); } /* * start task is not running jet */ public void handletask1(View v) { if (mTask1 == null) { mTask1 = new Task1(); TheApp.get().registerTask("task1", mTask1); mTask1.execute(); } else Toast.makeText(this, "Task is running...", Toast.LENGTH_SHORT).show(); } /* * cancel task if is not finished */ public void handelCancel(View v) { if (mTask1 != null) mTask1.cancel(false); } public class Task1 extends AsyncTask<Void, Void, Void>{ @Override protected Void doInBackground(Void... params) { try { for(int i=0; i<120; i++) { Thread.sleep(1000); Log.i("tests", "loop=" + i); if (this.isCancelled()) { Log.e("tests", "tssk cancelled"); break; } } } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void onCancelled(Void result) { TheApp.get().unregisterTask("task1"); mTask1 = null; } @Override protected void onPostExecute(Void result) { TheApp.get().unregisterTask("task1"); mTask1 = null; } } }
Quando a orientação da atividade muda, a variável mTask é iniciada no contexto do aplicativo. Quando a tarefa é concluída, a variável é definida como nula e removida da memória.
Para mim é o suficiente.
fonte
Dê uma olhada no exemplo abaixo, como usar o fragmento retido para reter a tarefa em segundo plano:
public class NetworkRequestFragment extends Fragment { // Declare some sort of interface that your AsyncTask will use to communicate with the Activity public interface NetworkRequestListener { void onRequestStarted(); void onRequestProgressUpdate(int progress); void onRequestFinished(SomeObject result); } private NetworkTask mTask; private NetworkRequestListener mListener; private SomeObject mResult; @Override public void onAttach(Activity activity) { super.onAttach(activity); // Try to use the Activity as a listener if (activity instanceof NetworkRequestListener) { mListener = (NetworkRequestListener) activity; } else { // You can decide if you want to mandate that the Activity implements your callback interface // in which case you should throw an exception if it doesn't: throw new IllegalStateException("Parent activity must implement NetworkRequestListener"); // or you could just swallow it and allow a state where nobody is listening } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Retain this Fragment so that it will not be destroyed when an orientation // change happens and we can keep our AsyncTask running setRetainInstance(true); } /** * The Activity can call this when it wants to start the task */ public void startTask(String url) { mTask = new NetworkTask(url); mTask.execute(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // If the AsyncTask finished when we didn't have a listener we can // deliver the result here if ((mResult != null) && (mListener != null)) { mListener.onRequestFinished(mResult); mResult = null; } } @Override public void onDestroy() { super.onDestroy(); // We still have to cancel the task in onDestroy because if the user exits the app or // finishes the Activity, we don't want the task to keep running // Since we are retaining the Fragment, onDestroy won't be called for an orientation change // so this won't affect our ability to keep the task running when the user rotates the device if ((mTask != null) && (mTask.getStatus == AsyncTask.Status.RUNNING)) { mTask.cancel(true); } } @Override public void onDetach() { super.onDetach(); // This is VERY important to avoid a memory leak (because mListener is really a reference to an Activity) // When the orientation change occurs, onDetach will be called and since the Activity is being destroyed // we don't want to keep any references to it // When the Activity is being re-created, onAttach will be called and we will get our listener back mListener = null; } private class NetworkTask extends AsyncTask<String, Integer, SomeObject> { @Override protected void onPreExecute() { if (mListener != null) { mListener.onRequestStarted(); } } @Override protected SomeObject doInBackground(String... urls) { // Make the network request ... // Whenever we want to update our progress: publishProgress(progress); ... return result; } @Override protected void onProgressUpdate(Integer... progress) { if (mListener != null) { mListener.onRequestProgressUpdate(progress[0]); } } @Override protected void onPostExecute(SomeObject result) { if (mListener != null) { mListener.onRequestFinished(result); } else { // If the task finishes while the orientation change is happening and while // the Fragment is not attached to an Activity, our mListener might be null // If you need to make sure that the result eventually gets to the Activity // you could save the result here, then in onActivityCreated you can pass it back // to the Activity mResult = result; } } } }
fonte
Dê uma olhada aqui .
Existe uma solução baseada em Timmmm's solução .
Mas eu melhorei:
Agora a solução é extensível - você só precisa estender
FragmentAbleToStartTask
Você pode continuar executando várias tarefas ao mesmo tempo.
E na minha opinião é tão fácil quanto startActivityForResult e receber o resultado
Você também pode interromper uma tarefa em execução e verificar se determinada tarefa está sendo executada
Desculpe pelo meu Inglês
fonte