您的位置:首页 > 移动开发 > Android开发

Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案

2014-07-19 09:25 579 查看
Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案


标签:
Android屏幕旋转AsyncTaskProgressDialog

2014-07-19 09:25
37960人阅读 评论(46)
收藏
举报

本文章已收录于:


分类:
【android 进阶之路】(68)




作者同类文章X

【Android 精彩案例】(36)




作者同类文章X

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]
概述
难点
使用onSaveInstanceState和onRestoreInstanceState进行数据保存与恢复
使用Fragment来保存对象用于恢复数据
配置configChanges自己对屏幕旋转的变化进行处理
旋转屏幕的最佳实践


转载请表明出处:http://blog.csdn.net/lmj623565791/article/details/37936275

1、概述
众所周知,Activity在不明确指定屏幕方向和configChanges时,当用户旋转屏幕会重新启动。当然了,应对这种情况,Android给出了几种方案:

a、如果是少量数据,可以通过onSaveInstanceState()和onRestoreInstanceState()进行保存与恢复。

Android会在销毁你的Activity之前调用onSaveInstanceState()方法,于是,你可以在此方法中存储关于应用状态的数据。然后你可以在onCreate()或onRestoreInstanceState()方法中恢复。

b、如果是大量数据,使用Fragment保持需要恢复的对象。

c、自已处理配置变化。

注:getLastNonConfigurationInstance()已经被弃用,被上述方法二替代。

2、难点
假设当前Activity在onCreate中启动一个异步线程去夹在数据,当然为了给用户一个很好的体验,会有一个ProgressDialog,当数据加载完成,ProgressDialog消失,设置数据。

这里,如果在异步数据完成加载之后,旋转屏幕,使用上述a、b两种方法都不会很难,无非是保存数据和恢复数据。

但是,如果正在线程加载的时候,进行旋转,会存在以下问题:

a)此时数据没有完成加载,onCreate重新启动时,会再次启动线程;而上个线程可能还在运行,并且可能会更新已经不存在的控件,造成错误。

b)关闭ProgressDialog的代码在线程的onPostExecutez中,但是上个线程如果已经杀死,无法关闭之前ProgressDialog。

c)谷歌的官方不建议使用ProgressDialog,这里我们会使用官方推荐的DialogFragment来创建我的加载框,如果你不了解:请看
Android 官方推荐 : DialogFragment 创建对话框。这样,其实给我们带来一个很大的问题,DialogFragment说白了是Fragment,和当前的Activity的生命周期会发生绑定,我们旋转屏幕会造成Activity的销毁,当然也会对DialogFragment造成影响。

下面我将使用几个例子,分别使用上面的3种方式,和如何最好的解决上述的问题。

3、使用onSaveInstanceState()和onRestoreInstanceState()进行数据保存与恢复
代码:

[java]
view plain
copy

print?

package com.example.zhy_handle_runtime_change;  
  
import java.util.ArrayList;  
import java.util.Arrays;  
  
import android.app.DialogFragment;  
import android.app.ListActivity;  
import android.os.AsyncTask;  
import android.os.Bundle;  
import android.util.Log;  
import android.widget.ArrayAdapter;  
import android.widget.ListAdapter;  
/** 
 * 不考虑加载时,进行旋转的情况,有意的避开这种情况,后面例子会介绍解决方案 
 * @author zhy 
 * 
 */  
public class SavedInstanceStateUsingActivity extends ListActivity  
{  
    private static final String TAG = "MainActivity";  
    private ListAdapter mAdapter;  
    private ArrayList<String> mDatas;  
    private DialogFragment mLoadingDialog;  
    private LoadDataAsyncTask mLoadDataAsyncTask;  
  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        Log.e(TAG, "onCreate");  
        initData(savedInstanceState);  
    }  
  
    /** 
     * 初始化数据 
     */  
    private void initData(Bundle savedInstanceState)  
    {  
        if (savedInstanceState != null)  
            mDatas = savedInstanceState.getStringArrayList("mDatas");  
  
        if (mDatas == null)  
        {  
            mLoadingDialog = new LoadingDialog();  
            mLoadingDialog.show(getFragmentManager(), "LoadingDialog");  
            mLoadDataAsyncTask = new LoadDataAsyncTask();  
            mLoadDataAsyncTask.execute();  
              
        } else  
        {  
            initAdapter();  
        }  
  
    }  
  
    /** 
     * 初始化适配器 
     */  
    private void initAdapter()  
    {  
        mAdapter = new ArrayAdapter<String>(  
                SavedInstanceStateUsingActivity.this,  
                android.R.layout.simple_list_item_1, mDatas);  
        setListAdapter(mAdapter);  
    }  
  
    @Override  
    protected void onRestoreInstanceState(Bundle state)  
    {  
        super.onRestoreInstanceState(state);  
        Log.e(TAG, "onRestoreInstanceState");  
    }  
  
    @Override  
    protected void onSaveInstanceState(Bundle outState)  
    {  
        super.onSaveInstanceState(outState);  
        Log.e(TAG, "onSaveInstanceState");  
        outState.putSerializable("mDatas", mDatas);  
  
    }  
  
    /** 
     * 模拟耗时操作 
     *  
     * @return 
     */  
    private ArrayList<String> generateTimeConsumingDatas()  
    {  
        try  
        {  
            Thread.sleep(2000);  
        } catch (InterruptedException e)  
        {  
        }  
        return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",  
                "onSaveInstanceState保存数据",  
                "getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",  
                "Spark"));  
    }  
  
    private class LoadDataAsyncTask extends AsyncTask<Void, Void, Void>  
    {  
        @Override  
        protected Void doInBackground(Void... params)  
        {  
            mDatas = generateTimeConsumingDatas();  
            return null;  
        }  
  
        @Override  
        protected void onPostExecute(Void result)  
        {  
            mLoadingDialog.dismiss();  
            initAdapter();  
        }  
    }  
  
    @Override  
    protected void onDestroy()  
    {  
        Log.e(TAG, "onDestroy");  
        super.onDestroy();  
    }  
  
}  



package com.example.zhy_handle_runtime_change;

import java.util.ArrayList;
import java.util.Arrays;

import android.app.DialogFragment;
import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
/**
* 不考虑加载时,进行旋转的情况,有意的避开这种情况,后面例子会介绍解决方案
* @author zhy
*
*/
public class SavedInstanceStateUsingActivity extends ListActivity
{
private static final String TAG = "MainActivity";
private ListAdapter mAdapter;
private ArrayList<String> mDatas;
private DialogFragment mLoadingDialog;
private LoadDataAsyncTask mLoadDataAsyncTask;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
initData(savedInstanceState);
}

/**
* 初始化数据
*/
private void initData(Bundle savedInstanceState)
{
if (savedInstanceState != null)
mDatas = savedInstanceState.getStringArrayList("mDatas");

if (mDatas == null)
{
mLoadingDialog = new LoadingDialog();
mLoadingDialog.show(getFragmentManager(), "LoadingDialog");
mLoadDataAsyncTask = new LoadDataAsyncTask();
mLoadDataAsyncTask.execute();

} else
{
initAdapter();
}

}

/**
* 初始化适配器
*/
private void initAdapter()
{
mAdapter = new ArrayAdapter<String>(
SavedInstanceStateUsingActivity.this,
android.R.layout.simple_list_item_1, mDatas);
setListAdapter(mAdapter);
}

@Override
protected void onRestoreInstanceState(Bundle state)
{
super.onRestoreInstanceState(state);
Log.e(TAG, "onRestoreInstanceState");
}

@Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
Log.e(TAG, "onSaveInstanceState");
outState.putSerializable("mDatas", mDatas);

}

/**
* 模拟耗时操作
*
* @return
*/
private ArrayList<String> generateTimeConsumingDatas()
{
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
}
return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",
"onSaveInstanceState保存数据",
"getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",
"Spark"));
}

private class LoadDataAsyncTask extends AsyncTask<Void, Void, Void>
{
@Override
protected Void doInBackground(Void... params)
{
mDatas = generateTimeConsumingDatas();
return null;
}

@Override
protected void onPostExecute(Void result)
{
mLoadingDialog.dismiss();
initAdapter();
}
}

@Override
protected void onDestroy()
{
Log.e(TAG, "onDestroy");
super.onDestroy();
}

}


界面为一个ListView,onCreate中启动一个异步任务去加载数据,这里使用Thread.sleep模拟了一个耗时操作;当用户旋转屏幕发生重新启动时,会onSaveInstanceState中进行数据的存储,在onCreate中对数据进行恢复,免去了不必要的再加载一遍。

运行结果:

当正常加载数据完成之后,用户不断进行旋转屏幕,log会不断打出:onSaveInstanceState->onDestroy->onCreate->onRestoreInstanceState,验证我们的确是重新启动了,但是我们没有再次去进行数据加载。

如果在加载的时候,进行旋转,则会发生错误,异常退出(退出原因:dialog.dismiss()时发生NullPointException,因为与当前对话框绑定的FragmentManager为null,又有兴趣的可以去Debug,这个不是关键)。

效果图:



4、使用Fragment来保存对象,用于恢复数据
如果重新启动你的Activity需要恢复大量的数据,重新建立网络连接,或者执行其他的密集型操作,这样因为配置发生变化而完全重新启动可能会是一个慢的用户体验。并且,使用系统提供的onSaveIntanceState()的回调中,使用Bundle来完全恢复你Activity的状态是可能是不现实的(Bundle不是设计用来携带大量数据的(例如bitmap),并且Bundle中的数据必须能够被序列化和反序列化),这样会消耗大量的内存和导致配置变化缓慢。在这样的情况下,当你的Activity因为配置发生改变而重启,你可以通过保持一个Fragment来缓解重新启动带来的负担。这个Fragment可以包含你想要保持的有状态的对象的引用。

当Android系统因为配置变化关闭你的Activity的时候,你的Activity中被标识保持的fragments不会被销毁。你可以在你的Activity中添加这样的fragements来保存有状态的对象。

在运行时配置发生变化时,在Fragment中保存有状态的对象

a) 继承Fragment,声明引用指向你的有状态的对象

b) 当Fragment创建时调用setRetainInstance(boolean)

c) 把Fragment实例添加到Activity中

d) 当Activity重新启动后,使用FragmentManager对Fragment进行恢复

代码:

首先是Fragment:

[java]
view plain
copy

print?

package com.example.zhy_handle_runtime_change;  
  
import android.app.Fragment;  
import android.graphics.Bitmap;  
import android.os.Bundle;  
  
public class RetainedFragment extends Fragment  
{  
    // data object we want to retain  
    private Bitmap data;  
    // this method is only called once for this fragment  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        // retain this fragment  
        setRetainInstance(true);  
    }  
  
    public void setData(Bitmap data)  
    {  
        this.data = data;  
    }  
  
    public Bitmap getData()  
    {  
        return data;  
    }  
}  



package com.example.zhy_handle_runtime_change;

import android.app.Fragment;
import android.graphics.Bitmap;
import android.os.Bundle;

public class RetainedFragment extends Fragment
{
// data object we want to retain
private Bitmap data;
// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}

public void setData(Bitmap data)
{
this.data = data;
}

public Bitmap getData()
{
return data;
}
}


比较简单,只需要声明需要保存的数据对象,然后提供getter和setter,注意,一定要在onCreate调用setRetainInstance(true);

然后是:FragmentRetainDataActivity

[java]
view plain
copy

print?

package com.example.zhy_handle_runtime_change;  
  
import android.app.Activity;  
import android.app.DialogFragment;  
import android.app.FragmentManager;  
import android.graphics.Bitmap;  
import android.graphics.Bitmap.Config;  
import android.os.Bundle;  
import android.util.Log;  
import android.widget.ImageView;  
  
import com.android.volley.RequestQueue;  
import com.android.volley.Response;  
import com.android.volley.toolbox.ImageRequest;  
import com.android.volley.toolbox.Volley;  
  
public class FragmentRetainDataActivity extends Activity  
{  
  
    private static final String TAG = "FragmentRetainDataActivity";  
    private RetainedFragment dataFragment;  
    private DialogFragment mLoadingDialog;  
    private ImageView mImageView;  
    private Bitmap mBitmap;  
  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        Log.e(TAG, "onCreate");  
  
        // find the retained fragment on activity restarts  
        FragmentManager fm = getFragmentManager();  
        dataFragment = (RetainedFragment) fm.findFragmentByTag("data");  
        // create the fragment and data the first time  
        if (dataFragment == null)  
        {  
            // add the fragment  
            dataFragment = new RetainedFragment();  
            fm.beginTransaction().add(dataFragment, "data").commit();  
        }  
        mBitmap = collectMyLoadedData();  
        initData();  
  
        // the data is available in dataFragment.getData()  
    }  
  
    /** 
     * 初始化数据 
     */  
    private void initData()  
    {  
        mImageView = (ImageView) findViewById(R.id.id_imageView);  
        if (mBitmap == null)  
        {  
            mLoadingDialog = new LoadingDialog();  
            mLoadingDialog.show(getFragmentManager(), "LOADING_DIALOG");  
            RequestQueue newRequestQueue = Volley  
                    .newRequestQueue(FragmentRetainDataActivity.this);  
            ImageRequest imageRequest = new ImageRequest(  
                    "http://img.my.csdn.net/uploads/201407/18/1405652589_5125.jpg",  
                    new Response.Listener<Bitmap>()  
                    {  
                        @Override  
                        public void onResponse(Bitmap response)  
                        {  
                            mBitmap = response;  
                            mImageView.setImageBitmap(mBitmap);  
                            // load the data from the web  
                            dataFragment.setData(mBitmap);  
                            mLoadingDialog.dismiss();  
                        }  
                    }, 0, 0, Config.RGB_565, null);  
            newRequestQueue.add(imageRequest);  
        } else  
        {  
            mImageView.setImageBitmap(mBitmap);  
        }  
  
    }  
  
    @Override  
    public void onDestroy()  
    {  
        Log.e(TAG, "onDestroy");  
        super.onDestroy();  
        // store the data in the fragment  
        dataFragment.setData(mBitmap);  
    }  
  
    private Bitmap collectMyLoadedData()  
    {  
        return dataFragment.getData();  
    }  
  
}  



package com.example.zhy_handle_runtime_change;

import android.app.Activity;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;

import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.ImageRequest;
import com.android.volley.toolbox.Volley;

public class FragmentRetainDataActivity extends Activity
{

private static final String TAG = "FragmentRetainDataActivity";
private RetainedFragment dataFragment;
private DialogFragment mLoadingDialog;
private ImageView mImageView;
private Bitmap mBitmap;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e(TAG, "onCreate");

// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (RetainedFragment) fm.findFragmentByTag("data");
// create the fragment and data the first time
if (dataFragment == null)
{
// add the fragment
dataFragment = new RetainedFragment();
fm.beginTransaction().add(dataFragment, "data").commit();
}
mBitmap = collectMyLoadedData();
initData();

// the data is available in dataFragment.getData()
}

/**
* 初始化数据
*/
private void initData()
{
mImageView = (ImageView) findViewById(R.id.id_imageView);
if (mBitmap == null)
{
mLoadingDialog = new LoadingDialog();
mLoadingDialog.show(getFragmentManager(), "LOADING_DIALOG");
RequestQueue newRequestQueue = Volley
.newRequestQueue(FragmentRetainDataActivity.this);
ImageRequest imageRequest = new ImageRequest(
"http://img.my.csdn.net/uploads/201407/18/1405652589_5125.jpg",
new Response.Listener<Bitmap>()
{
@Override
public void onResponse(Bitmap response)
{
mBitmap = response;
mImageView.setImageBitmap(mBitmap);
// load the data from the web
dataFragment.setData(mBitmap);
mLoadingDialog.dismiss();
}
}, 0, 0, Config.RGB_565, null);
newRequestQueue.add(imageRequest);
} else
{
mImageView.setImageBitmap(mBitmap);
}

}

@Override
public void onDestroy()
{
Log.e(TAG, "onDestroy");
super.onDestroy();
// store the data in the fragment
dataFragment.setData(mBitmap);
}

private Bitmap collectMyLoadedData()
{
return dataFragment.getData();
}

}


这里在onCreate总使用了Volley去加载 了一张美女照片,然后在onDestroy中对Bitmap进行存储,在onCreate添加一个或者恢复一个Fragment的引用,然后对Bitmap进行读取和设置。这种方式适用于比较大的数据的存储与恢复。

注:这里也没有考虑加载时旋转屏幕,问题与上面的一致。

效果图:



5、配置configChanges,自己对屏幕旋转的变化进行处理
在menifest中进行属性设置:

[html]
view plain
copy

print?

<activity  
        android:name=".ConfigChangesTestActivity"  
        android:configChanges="screenSize|orientation" >  
    </activity>  



<activity
android:name=".ConfigChangesTestActivity"
android:configChanges="screenSize|orientation" >
</activity>
低版本的API只需要加入orientation,而高版本的则需要加入screenSize。

ConfigChangesTestActivity

[java]
view plain
copy

print?

package com.example.zhy_handle_runtime_change;  
  
import java.util.ArrayList;  
import java.util.Arrays;  
  
import android.app.DialogFragment;  
import android.app.ListActivity;  
import android.content.res.Configuration;  
import android.os.AsyncTask;  
import android.os.Bundle;  
import android.util.Log;  
import android.widget.ArrayAdapter;  
import android.widget.ListAdapter;  
import android.widget.Toast;  
  
/** 
 * @author zhy 
 *  
 */  
public class ConfigChangesTestActivity extends ListActivity  
{  
    private static final String TAG = "MainActivity";  
    private ListAdapter mAdapter;  
    private ArrayList<String> mDatas;  
    private DialogFragment mLoadingDialog;  
    private LoadDataAsyncTask mLoadDataAsyncTask;  
  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        Log.e(TAG, "onCreate");  
        initData(savedInstanceState);  
    }  
  
    /** 
     * 初始化数据 
     */  
    private void initData(Bundle savedInstanceState)  
    {  
  
        mLoadingDialog = new LoadingDialog();  
        mLoadingDialog.show(getFragmentManager(), "LoadingDialog");  
        mLoadDataAsyncTask = new LoadDataAsyncTask();  
        mLoadDataAsyncTask.execute();  
  
    }  
  
    /** 
     * 初始化适配器 
     */  
    private void initAdapter()  
    {  
        mAdapter = new ArrayAdapter<String>(ConfigChangesTestActivity.this,  
                android.R.layout.simple_list_item_1, mDatas);  
        setListAdapter(mAdapter);  
    }  
  
    /** 
     * 模拟耗时操作 
     *  
     * @return 
     */  
    private ArrayList<String> generateTimeConsumingDatas()  
    {  
        try  
        {  
            Thread.sleep(2000);  
        } catch (InterruptedException e)  
        {  
        }  
        return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",  
                "onSaveInstanceState保存数据",  
                "getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",  
                "Spark"));  
    }  
  
    /** 
     * 当配置发生变化时,不会重新启动Activity。但是会回调此方法,用户自行进行对屏幕旋转后进行处理 
     */  
    @Override  
    public void onConfigurationChanged(Configuration newConfig)  
    {  
        super.onConfigurationChanged(newConfig);  
  
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)  
        {  
            Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();  
        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)  
        {  
            Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();  
        }  
  
    }  
  
    private class LoadDataAsyncTask extends AsyncTask<Void, Void, Void>  
    {  
        @Override  
        protected Void doInBackground(Void... params)  
        {  
            mDatas = generateTimeConsumingDatas();  
            return null;  
        }  
  
        @Override  
        protected void onPostExecute(Void result)  
        {  
            mLoadingDialog.dismiss();  
            initAdapter();  
        }  
    }  
  
    @Override  
    protected void onDestroy()  
    {  
        Log.e(TAG, "onDestroy");  
        super.onDestroy();  
    }  
  
}  



package com.example.zhy_handle_runtime_change;

import java.util.ArrayList;
import java.util.Arrays;

import android.app.DialogFragment;
import android.app.ListActivity;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.Toast;

/**
* @author zhy
*
*/
public class ConfigChangesTestActivity extends ListActivity
{
private static final String TAG = "MainActivity";
private ListAdapter mAdapter;
private ArrayList<String> mDatas;
private DialogFragment mLoadingDialog;
private LoadDataAsyncTask mLoadDataAsyncTask;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
initData(savedInstanceState);
}

/**
* 初始化数据
*/
private void initData(Bundle savedInstanceState)
{

mLoadingDialog = new LoadingDialog();
mLoadingDialog.show(getFragmentManager(), "LoadingDialog");
mLoadDataAsyncTask = new LoadDataAsyncTask();
mLoadDataAsyncTask.execute();

}

/**
* 初始化适配器
*/
private void initAdapter()
{
mAdapter = new ArrayAdapter<String>(ConfigChangesTestActivity.this,
android.R.layout.simple_list_item_1, mDatas);
setListAdapter(mAdapter);
}

/**
* 模拟耗时操作
*
* @return
*/
private ArrayList<String> generateTimeConsumingDatas()
{
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
}
return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",
"onSaveInstanceState保存数据",
"getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",
"Spark"));
}

/**
* 当配置发生变化时,不会重新启动Activity。但是会回调此方法,用户自行进行对屏幕旋转后进行处理
*/
@Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);

if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
{
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
{
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}

}

private class LoadDataAsyncTask extends AsyncTask<Void, Void, Void>
{
@Override
protected Void doInBackground(Void... params)
{
mDatas = generateTimeConsumingDatas();
return null;
}

@Override
protected void onPostExecute(Void result)
{
mLoadingDialog.dismiss();
initAdapter();
}
}

@Override
protected void onDestroy()
{
Log.e(TAG, "onDestroy");
super.onDestroy();
}

}

对第一种方式的代码进行了修改,去掉了保存与恢复的代码,重写了onConfigurationChanged;此时,无论用户何时旋转屏幕都不会重新启动Activity,并且onConfigurationChanged中的代码可以得到调用。从效果图可以看到,无论如何旋转不会重启Activity.

效果图:



6、旋转屏幕的最佳实践
下面要开始今天的难点了,就是处理文章开始时所说的,当异步任务在执行时,进行旋转,如果解决上面的问题。

首先说一下探索过程:

起初,我认为此时旋转无非是再启动一次线程,并不会造成异常,我只要即使的在onDestroy里面关闭上一个异步任务就可以了。事实上,如果我关闭了,上一次的对话框会一直存在;如果我不关闭,但是activity是一定会被销毁的,对话框的dismiss也会出异常。真心很蛋疼,并且即使对话框关闭了,任务关闭了;用户旋转还是会造成重新创建任务,从头开始加载数据。

下面我们希望有一种解决方案:在加载数据时旋转屏幕,不会对加载任务进行中断,且对用户而言,等待框在加载完成之前都正常显示:

当然我们还使用Fragment进行数据保存,毕竟这是官方推荐的:

OtherRetainedFragment

[java]
view plain
copy

print?

package com.example.zhy_handle_runtime_change;  
  
import android.app.Fragment;  
import android.os.Bundle;  
  
/** 
 * 保存对象的Fragment 
 *  
 * @author zhy 
 *  
 */  
public class OtherRetainedFragment extends Fragment  
{  
  
    // data object we want to retain  
    // 保存一个异步的任务  
    private MyAsyncTask data;  
  
    // this method is only called once for this fragment  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        // retain this fragment  
        setRetainInstance(true);  
    }  
  
    public void setData(MyAsyncTask data)  
    {  
        this.data = data;  
    }  
  
    public MyAsyncTask getData()  
    {  
        return data;  
    }  
  
      
}  



package com.example.zhy_handle_runtime_change;

import android.app.Fragment;
import android.os.Bundle;

/**
* 保存对象的Fragment
*
* @author zhy
*
*/
public class OtherRetainedFragment extends Fragment
{

// data object we want to retain
// 保存一个异步的任务
private MyAsyncTask data;

// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}

public void setData(MyAsyncTask data)
{
this.data = data;
}

public MyAsyncTask getData()
{
return data;
}

}


和上面的差别不大,唯一不同的就是它要保存的对象编程一个异步的任务了,相信看到这,已经知道经常上述问题的一个核心了,保存一个异步任务,在重启时,继续这个任务。

[java]
view plain
copy

print?

package com.example.zhy_handle_runtime_change;  
  
import java.util.ArrayList;  
import java.util.Arrays;  
import java.util.List;  
  
import android.os.AsyncTask;  
  
public class MyAsyncTask extends AsyncTask<Void, Void, Void>  
{  
    private FixProblemsActivity activity;  
    /** 
     * 是否完成 
     */  
    private boolean isCompleted;  
    /** 
     * 进度框 
     */  
    private LoadingDialog mLoadingDialog;  
    private List<String> items;  
  
    public MyAsyncTask(FixProblemsActivity activity)  
    {  
        this.activity = activity;  
    }  
  
    /** 
     * 开始时,显示加载框 
     */  
    @Override  
    protected void onPreExecute()  
    {  
        mLoadingDialog = new LoadingDialog();  
        mLoadingDialog.show(activity.getFragmentManager(), "LOADING");  
    }  
  
    /** 
     * 加载数据 
     */  
    @Override  
    protected Void doInBackground(Void... params)  
    {  
        items = loadingData();  
        return null;  
    }  
  
    /** 
     * 加载完成回调当前的Activity 
     */  
    @Override  
    protected void onPostExecute(Void unused)  
    {  
        isCompleted = true;  
        notifyActivityTaskCompleted();  
        if (mLoadingDialog != null)  
            mLoadingDialog.dismiss();  
    }  
  
    public List<String> getItems()  
    {  
        return items;  
    }  
  
    private List<String> loadingData()  
    {  
        try  
        {  
            Thread.sleep(5000);  
        } catch (InterruptedException e)  
        {  
        }  
        return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",  
                "onSaveInstanceState保存数据",  
                "getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",  
                "Spark"));  
    }  
  
    /** 
     * 设置Activity,因为Activity会一直变化 
     *  
     * @param activity 
     */  
    public void setActivity(FixProblemsActivity activity)  
    {  
        // 如果上一个Activity销毁,将与上一个Activity绑定的DialogFragment销毁  
        if (activity == null)  
        {  
            mLoadingDialog.dismiss();  
        }  
        // 设置为当前的Activity  
        this.activity = activity;  
        // 开启一个与当前Activity绑定的等待框  
        if (activity != null && !isCompleted)  
        {  
            mLoadingDialog = new LoadingDialog();  
            mLoadingDialog.show(activity.getFragmentManager(), "LOADING");  
        }  
        // 如果完成,通知Activity  
        if (isCompleted)  
        {  
            notifyActivityTaskCompleted();  
        }  
    }  
  
    private void notifyActivityTaskCompleted()  
    {  
        if (null != activity)  
        {  
            activity.onTaskCompleted();  
        }  
    }  
  
}  



package com.example.zhy_handle_runtime_change;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import android.os.AsyncTask;

public class MyAsyncTask extends AsyncTask<Void, Void, Void>
{
private FixProblemsActivity activity;
/**
* 是否完成
*/
private boolean isCompleted;
/**
* 进度框
*/
private LoadingDialog mLoadingDialog;
private List<String> items;

public MyAsyncTask(FixProblemsActivity activity)
{
this.activity = activity;
}

/**
* 开始时,显示加载框
*/
@Override
protected void onPreExecute()
{
mLoadingDialog = new LoadingDialog();
mLoadingDialog.show(activity.getFragmentManager(), "LOADING");
}

/**
* 加载数据
*/
@Override
protected Void doInBackground(Void... params)
{
items = loadingData();
return null;
}

/**
* 加载完成回调当前的Activity
*/
@Override
protected void onPostExecute(Void unused)
{
isCompleted = true;
notifyActivityTaskCompleted();
if (mLoadingDialog != null)
mLoadingDialog.dismiss();
}

public List<String> getItems()
{
return items;
}

private List<String> loadingData()
{
try
{
Thread.sleep(5000);
} catch (InterruptedException e)
{
}
return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",
"onSaveInstanceState保存数据",
"getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",
"Spark"));
}

/**
* 设置Activity,因为Activity会一直变化
*
* @param activity
*/
public void setActivity(FixProblemsActivity activity)
{
// 如果上一个Activity销毁,将与上一个Activity绑定的DialogFragment销毁
if (activity == null)
{
mLoadingDialog.dismiss();
}
// 设置为当前的Activity
this.activity = activity;
// 开启一个与当前Activity绑定的等待框
if (activity != null && !isCompleted)
{
mLoadingDialog = new LoadingDialog();
mLoadingDialog.show(activity.getFragmentManager(), "LOADING");
}
// 如果完成,通知Activity
if (isCompleted)
{
notifyActivityTaskCompleted();
}
}

private void notifyActivityTaskCompleted()
{
if (null != activity)
{
activity.onTaskCompleted();
}
}

}


异步任务中,管理一个对话框,当开始下载前,进度框显示,下载结束进度框消失,并为Activity提供回调。当然了,运行过程中Activity不断的重启,我们也提供了setActivity方法,onDestory时,会setActivity(null)防止内存泄漏,同时我们也会关闭与其绑定的加载框;当onCreate传入新的Activity时,我们会在再次打开一个加载框,当然了因为屏幕的旋转并不影响加载的数据,所有后台的数据一直继续在加载。是不是很完美~~

主Activity:

[java]
view plain
copy

print?

package com.example.zhy_handle_runtime_change;  
  
import java.util.List;  
  
import android.app.FragmentManager;  
import android.app.ListActivity;  
import android.os.Bundle;  
import android.util.Log;  
import android.widget.ArrayAdapter;  
import android.widget.ListAdapter;  
  
public class FixProblemsActivity extends ListActivity  
{  
    private static final String TAG = "MainActivity";  
    private ListAdapter mAdapter;  
    private List<String> mDatas;  
    private OtherRetainedFragment dataFragment;  
    private MyAsyncTask mMyTask;  
  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        Log.e(TAG, "onCreate");  
  
        // find the retained fragment on activity restarts  
        FragmentManager fm = getFragmentManager();  
        dataFragment = (OtherRetainedFragment) fm.findFragmentByTag("data");  
  
        // create the fragment and data the first time  
        if (dataFragment == null)  
        {  
            // add the fragment  
            dataFragment = new OtherRetainedFragment();  
            fm.beginTransaction().add(dataFragment, "data").commit();  
        }  
        mMyTask = dataFragment.getData();  
        if (mMyTask != null)  
        {  
            mMyTask.setActivity(this);  
        } else  
        {  
            mMyTask = new MyAsyncTask(this);  
            dataFragment.setData(mMyTask);  
            mMyTask.execute();  
        }  
        // the data is available in dataFragment.getData()  
    }  
  
  
    @Override  
    protected void onRestoreInstanceState(Bundle state)  
    {  
        super.onRestoreInstanceState(state);  
        Log.e(TAG, "onRestoreInstanceState");  
    }  
  
  
    @Override  
    protected void onSaveInstanceState(Bundle outState)  
    {  
        mMyTask.setActivity(null);  
        super.onSaveInstanceState(outState);  
        Log.e(TAG, "onSaveInstanceState");  
    }  
  
    @Override  
    protected void onDestroy()  
    {  
        Log.e(TAG, "onDestroy");  
        super.onDestroy();  
  
    }  
    /** 
     * 回调 
     */  
    public void onTaskCompleted()  
    {  
        mDatas = mMyTask.getItems();  
        mAdapter = new ArrayAdapter<String>(FixProblemsActivity.this,  
                android.R.layout.simple_list_item_1, mDatas);  
        setListAdapter(mAdapter);  
    }  
  
}  



package com.example.zhy_handle_runtime_change;

import java.util.List;

import android.app.FragmentManager;
import android.app.ListActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;

public class FixProblemsActivity extends ListActivity
{
private static final String TAG = "MainActivity";
private ListAdapter mAdapter;
private List<String> mDatas;
private OtherRetainedFragment dataFragment;
private MyAsyncTask mMyTask;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");

// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (OtherRetainedFragment) fm.findFragmentByTag("data");

// create the fragment and data the first time
if (dataFragment == null)
{
// add the fragment
dataFragment = new OtherRetainedFragment();
fm.beginTransaction().add(dataFragment, "data").commit();
}
mMyTask = dataFragment.getData();
if (mMyTask != null)
{
mMyTask.setActivity(this);
} else
{
mMyTask = new MyAsyncTask(this);
dataFragment.setData(mMyTask);
mMyTask.execute();
}
// the data is available in dataFragment.getData()
}

@Override
protected void onRestoreInstanceState(Bundle state)
{
super.onRestoreInstanceState(state);
Log.e(TAG, "onRestoreInstanceState");
}

@Override
protected void onSaveInstanceState(Bundle outState)
{
mMyTask.setActivity(null);
super.onSaveInstanceState(outState);
Log.e(TAG, "onSaveInstanceState");
}

@Override
protected void onDestroy()
{
Log.e(TAG, "onDestroy");
super.onDestroy();

}
/**
* 回调
*/
public void onTaskCompleted()
{
mDatas = mMyTask.getItems();
mAdapter = new ArrayAdapter<String>(FixProblemsActivity.this,
android.R.layout.simple_list_item_1, mDatas);
setListAdapter(mAdapter);
}

}


在onCreate中,如果没有开启任务(第一次进入),开启任务;如果已经开启了,调用setActivity(this);

在onSaveInstanceState把当前任务加入Fragment

我设置了等待5秒,足够旋转三四个来回了~~~~可以看到虽然在不断的重启,但是丝毫不影响加载数据任务的运行和加载框的显示~~~~

效果图:



可以看到我在加载的时候就三心病狂的旋转屏幕~~但是丝毫不影响显示效果与任务的加载~~

最后,说明一下,其实不仅是屏幕旋转需要保存数据,当用户在使用你的app时,忽然接到一个来电,长时间没有回到你的app界面也会造成Activity的销毁与重建,所以一个行为良好的App,是有必要拥有恢复数据的能力的~~。

查阅资料时的一些参考文档:
http://developer.android.com/guide/topics/resources/runtime-changes.html http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/
有任何问题,欢迎留言 

源码点击下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: