bli*_*uff 84 android progressdialog android-asynctask android-fragments android-support-library
我正在使用智能手机/平板电脑应用程序,只使用一个APK,并根据屏幕大小根据需要加载资源,最佳设计选择似乎是通过ACL使用Fragments.
这个应用程序一直工作正常,直到现在只是基于活动.这是一个模拟类,用于处理活动中的AsyncTasks和ProgressDialogs,以便在屏幕旋转或通信中发生配置更改时使它们工作.
我不会改变清单以避免重新创建活动,有很多原因我不想这样做,但主要是因为官方文档说它不是推荐的,而且我已经管理了这么远,所以请不要推荐路线.
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
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码工作正常,我有大约10,000个用户没有抱怨,所以将这个逻辑复制到新的基于片段的设计似乎合乎逻辑,但是,当然,它不起作用.
这是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
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我不能使用,onRetainNonConfigurationInstance()因为它必须从Activity而不是Fragment调用,同样如此getLastNonConfigurationInstance().我在这里读过一些类似的问题而没有答案.
据我所知,它可能需要一些解决方法才能将这些东西正确地组织成片段,据说,我希望保持相同的基本设计逻辑.
在配置更改期间保留AsyncTask的正确方法是什么,如果它仍在运行,则显示progressDialog,同时考虑到AsyncTask是Fragment的内部类,而Fragment本身调用AsyncTask.execute ()?
hac*_*bod 75
碎片实际上可以使这更容易.只需使用Fragment.setRetainInstance(boolean)方法,即可在配置更改中保留您的片段实例.请注意,这是docs中Activity.onRetainnonConfigurationInstance()的推荐替代品.
如果由于某种原因你真的不想使用保留的片段,你可以采取其他方法.请注意,每个片段都有Fragment.getId()返回的唯一标识符.您还可以通过Fragment.getActivity().isChangingConfigurations()查看是否正在拆除片段以进行配置更改.因此,点在哪里,你会决定停止您的AsyncTask(中的onStop()或的onDestroy()最有可能的),例如,您可以检查配置是否正在发生变化,如果是坚持在静态SparseArray片段的标识符下,然后在你的onCreate()或onStart()中查看你是否在稀疏数组中有一个AsyncTask.
Tim*_*mmm 66
我想你会喜欢我下面详细介绍的非常全面和有效的例子.
根据Brad Larson的要求,我已经复制了下面的大部分链接解决方案.自从我发布以来,我也被指出了AsyncTaskLoader.我不确定它是否完全适用于同样的问题,但无论如何你应该检查一下.
AsyncTask进度对话框和设备旋转.我终于把一切都搞定了.我的代码具有以下功能:
Fragment的布局随方向而变化.AsyncTask可以在其中做一些工作.DialogFragment显示进度条中的任务进度(不仅仅是一个不确定的微调器).我认为在其他任何地方都找不到工作的组合.
基本思路如下.有一个MainActivity类包含一个片段 - MainFragment.MainFragment具有不同的水平和垂直方向布局,并且setRetainInstance()是错误的,因此布局可以更改.这意味着,当设备方向改变时,都MainActivity和MainFragment被完全破坏并重新创建.
另外,我们MyTask(扩展AsyncTask)完成所有工作.我们无法存储它,MainFragment因为它会被销毁,谷歌已经弃用了类似的东西setRetainNonInstanceConfiguration().无论如何,这并不总是可用,并且最好是一个丑陋的黑客.相反,我们将存储MyTask在另一个片段中,一个DialogFragment名为TaskFragment.这个片段将已setRetainInstance()设置为true,以便在设备旋转该片段不被破坏,而MyTask被保留.
最后,我们需要告诉TaskFragment谁在完成时告知谁,我们setTargetFragment(<the MainFragment>)在创建它时使用它.当设备旋转并被MainFragment销毁并创建一个新实例时,我们使用它FragmentManager来查找对话框(基于其标签)并执行setTargetFragment(<the new MainFragment>).这就是它.
我需要做的另外两件事:首先取消对话框取消时的任务,然后将dismiss消息设置为null,否则在旋转设备时对话框会被奇怪地解除.
我不会列出布局,它们非常明显,您可以在下面的项目下载中找到它们.
这非常简单.我在此活动中添加了回调,因此它知道任务何时完成,但您可能不需要.主要是我只想显示片段活动回调机制,因为它非常整洁,你可能以前没有看过它.
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?
}
}
Run Code Online (Sandbox Code Playgroud)
它很长但值得!
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();
}
}
Run Code Online (Sandbox Code Playgroud)
// 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);
}
}
Run Code Online (Sandbox Code Playgroud)
// 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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是源代码和APK.对不起,ADT坚持要先添加支持库才能让我创建一个项目.我相信你可以删除它.
net*_*ein 13
我的第一个建议是避免使用内部AsyncTasks,你可以阅读我询问的问题及答案:Android:AsyncTask建议:私有类还是公共类?
在那之后我开始使用非内部和......现在我看到了很多好处.
第二个是,在Application类中保留正在运行的AsyncTask的引用- http://developer.android.com/reference/android/app/Application.html
每次启动AsyncTask时,在Application上设置它,当它完成时将其设置为null.
当一个片段/活动开始时,您可以检查是否有任何AsyncTask正在运行(通过检查它是否在应用程序上为null),然后将内部引用设置为您想要的任何内容(活动,片段等,以便您可以进行回调).
这将解决您的问题:如果您在任何确定的时间只运行1个AsyncTask,您可以添加一个简单的引用:
AsyncTask<?,?,?> asyncTask = null;
Run Code Online (Sandbox Code Playgroud)
另外,在Aplication中有一个HashMap,引用它们.
进度对话框可以遵循完全相同的原则.
| 归档时间: |
|
| 查看次数: |
55562 次 |
| 最近记录: |