Activity and Intent(二)

Android 旋转屏幕--处理Activity与AsyncTask的最佳解决方案

一、概述

运行时变更就是设备在运行时发生变化(例如屏幕旋转、键盘可用性及语言)。发生这些变化,Android会重启Activity,这时就需要保存activity的状态及与activity相关的任务,以便恢复activity的状态。

为此,google提供了三种解决方案:

  1. 对于少量数据: 通过onSaveInstanceState(),保存有关应用状态的数据。 然后在 onCreate() 或 onRestoreInstanceState() 期间恢复 Activity 状态。
  2. 对于大量数据:用 Fragment 保留需要回复的对象。
  3. 自行处理配置变更,不重启Activity。

下面会逐一介绍三种情况,其实保存一些变量对象很简单,难的是当Activity创建异步线程去加载数据时,旋转屏幕时,怎么保存线程的状态。比如,在线程的加载过程中,旋转屏幕,就会存在问题:此时数据没有完成加载,onCreate重新启动时,会再次启动线程;而上个线程可能还在运行,并且可能会更新已经不存在的控件,造成错误。下面会一一解决这些问题。本文较长,主要是代码多,可以先下载demo,源码下载:http://download.csdn.net/detail/jycboy/9720486对比着看

 二、使用onSaveInstanceState,onRestoreInstanceState​

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
  * 使用onSaveInstanceState,onRestoreInstanceState;
  * 在这里不考虑没有加载完毕,就旋转屏幕的情况。
  * @author 超超boy
  *
  */
public  class  SavedInstanceStateActivity  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();
             //mLoadDataAsyncTas
         else
         {
             initAdapter();
         }
     }
 
     /**
      * 初始化适配器
      */
     private  void  initAdapter()
     {
         mAdapter =  new  ArrayAdapter<String>(
                 SavedInstanceStateActivity. 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( 3000 );
         catch  (InterruptedException e)
         {   e.printStackTrace();
         }
         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中对数据进行恢复,免去了不必要的再加载一遍。

运行结果:

12-24 20:13:41.814 1994-1994/? E/MainActivity: onCreate
12-24 20:13:46.124 1994-1994/? E/MainActivity: onSaveInstanceState
12-24 20:13:46.124 1994-1994/? E/MainActivity: onDestroy
12-24 20:13:46.154 1994-1994/? E/MainActivity: onCreate
12-24 20:13:46.164 1994-1994/? E/MainActivity: onRestoreInstanceState

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

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

效果图:

 

三、使用Fragment保留对象,恢复数据

果重启 Activity 需要恢复大量数据、重新建立网络连接或执行其他密集操作,依靠系统通过onSaveInstanceState() 回调为您保存的 Bundle,可能无法完全恢复 Activity 状态,因为它并非设计用于携带大型对象(例如位图),而且其中的数据必须先序列化,再进行反序列化,这可能会消耗大量内存并使得配置变更速度缓慢。 在这种情况下,如果 Activity 因配置变更而重启,则可通过保留 Fragment 来减轻重新初始化 Activity 的负担。此片段可能包含对您要保留的有状态对象的引用。

当 Android 系统因配置变更而关闭 Activity 时,不会销毁您已标记为要保留的 Activity 的片段。 您可以将此类片段添加到 Activity 以保留有状态的对象。

要在运行时配置变更期间将有状态的对象保留在片段中,请执行以下操作:

  1. 扩展 Fragment 类并声明对有状态对象的引用。
  2. 在创建片段后调用 setRetainInstance(boolean)
  3. 将片段添加到 Activity。
  4. 重启 Activity 后,使用 FragmentManager 检索片段。

例如,按如下方式定义片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  class  RetainedFragment  extends  Fragment {
 
     // data object we want to retain
     private  MyDataObject 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(MyDataObject data) {
         this .data = data;
     }
 
     public  MyDataObject getData() {
         return  data;
     }
}

注意:尽管您可以存储任何对象,但是切勿传递与 Activity 绑定的对象,例如,DrawableAdapterView 或其他任何与 Context 关联的对象。否则,它将使Activity无法被回收造成内存泄漏。(泄漏资源意味着应用将继续持有这些资源,但是无法对其进行垃圾回收,因此可能会丢失大量内存)

下面举一个实际的例子:

1.RetainedFragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
     }
}

只是保持Bitmap对象的引用,你可以用Fragment保存多个对象。

2.FragmentRetainDataActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
public  class  FragmentRetainDataActivity  extends  Activity
{
 
     private  static  final  String TAG =  "FragmentRetainData" ;
     private  RetainedFragment dataFragment;
     private  DialogFragment mLoadingDialog;
     private  ImageView mImageView;
     private  Bitmap mBitmap;
     BitmapWorkerTask bitmapWorkerTask;
 
     @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();
         }
         // the data is available in dataFragment.getData()
         mBitmap = dataFragment.getData();
         initView();
     }
 
     /**
      * 初始化控件
      */
     private  void  initView()
     {
         mImageView = (ImageView) findViewById(R.id.id_imageView);
         if (mBitmap !=  null )
         mImageView.setImageBitmap(mBitmap);
         //图片为空时,加载图片;有时候即使dataFragment!=null时,图片也不一定就加载完了,比如在加载的过程中,旋转屏幕,此时图片就没有加载完
         else {
             mLoadingDialog =  new  LoadingDialog();
             mLoadingDialog.show(getFragmentManager(),  "LOADING_DIALOG" );
             bitmapWorkerTask =  new  BitmapWorkerTask( this );
             bitmapWorkerTask.execute( "http://images2015.cnblogs.com/blog/747969/201612/747969-20161222164357995-1098775233.jpg" );
         }
     }
 
     /**
      * 异步下载图片的任务。
      * 设置成静态内部类是为了防止内存泄漏
      * @author guolin
      */
     private  static  class  BitmapWorkerTask  extends  AsyncTask<String, Void, Bitmap> {
 
         //图片的URL地址
         private  String imageUrl;
         //保存外部activity的弱引用
         private  WeakReference<Context> weakReference;
         public  BitmapWorkerTask(Context context) {
             weakReference =  new  WeakReference<>(context);
         }
 
         @Override
         protected  Bitmap doInBackground(String... params) {
             imageUrl = params[ 0 ];
             //为了演示加载过程,阻塞2秒
             try
             {Thread.sleep( 2000 );
             catch  (InterruptedException e)
             {   e.printStackTrace();
             }
             return  downloadUrlToStream(imageUrl);
         }
 
         @Override
         protected  void  onPostExecute(Bitmap bitmap) {
             super .onPostExecute(bitmap);
             if (bitmap != null ){
             FragmentRetainDataActivity retainDataActivity= (FragmentRetainDataActivity) weakReference.get();
             //调用回调方法
             retainDataActivity.onLoaded(bitmap);
             }
         }
 
         /**
          * 建立HTTP请求,并获取Bitmap对象。
          * 修改了下
          * @param urlString
          *            图片的URL地址
          * @return 解析后的Bitmap对象
          */
         private  Bitmap downloadUrlToStream(String urlString) {
             HttpURLConnection urlConnection =  null ;
             Bitmap bitmap =  null ;
             try  {
                 final  URL url =  new  URL(urlString);
                 urlConnection = (HttpURLConnection) url.openConnection();
                 if (urlConnection.getResponseCode()==HttpURLConnection.HTTP_OK){   //连接成功
                     InputStream is =  urlConnection.getInputStream();
                     bitmap = BitmapFactory.decodeStream(is);
                     is.close();
                     return  bitmap;
                 } else {
                     return  null ;
                 }
 
             catch  ( final  IOException e) {
                 e.printStackTrace();
             finally  {
                 if  (urlConnection !=  null ) {
                     urlConnection.disconnect();
                 }
             }
             return  null ;
         }
 
     }
     //加载完毕的回掉
     public  void  onLoaded(Bitmap bitmap){
         mBitmap = bitmap;
         mLoadingDialog.dismiss();
         mImageView.setImageBitmap(mBitmap);
         // load the data from the web
         dataFragment.setData(mBitmap);
         Log.e(TAG,  "onLoaded" );
     }
     public  void  onPause(){
         super .onPause();
         Log.e(TAG,  "onPause" );
         if (getFragmentManager() !=  null  && mLoadingDialog !=  null )
             mLoadingDialog.dismiss();
     }
     @Override
     public  void  onDestroy()
     {
         super .onDestroy();
         Log.e(TAG,  "onDestroy" );
         if (bitmapWorkerTask != null )
         bitmapWorkerTask.cancel( true );
         // store the data in the fragment
         dataFragment.setData(mBitmap);
     }
 
}

这里边用BitmapWorkerTask异步下载图片,downloadUrlToStream封装了下载图片的代码;

BitmapWorkerTask用弱引用保持外部Activity对象防止内存泄漏,下载完毕后用onLoaded回调方法更新UI。

通过检查dataFragment、mBitmap判断是否已经加载过,加载过直接用就可以。

效果图:

在gif里可以看到,如果未加载完毕就旋转屏幕,它会重新启动异步线程去下载,这种效果并不好,我们会在最后解决这个问题。

四、自行处理配置变更

如果应用在特定配置变更期间无需更新资源,并且因性能限制您需要尽量避免重启,则可声明 Activity 将自行处理配置变更,这样可以阻止系统重启 Activity。

要声明由 Activity 处理配置变更,需设置清单文件manifest:

1
2
3
< activity  android:name=".MyActivity"
           android:configChanges="orientation|keyboardHidden"
           android:label="@string/app_name">

"orientation" 和 "keyboardHidden",分别用于避免因屏幕方向和可用键盘改变而导致重启)。您可以在该属性中声明多个配置值,方法是用管道 | 字符分隔这些配置值。

注意:API 级别 13 或更高版本的应用时,若要避免由于设备方向改变而导致运行时重启,则除了 "orientation" 值以外,您还必须添加 "screenSize" 值。 也就是说,您必须声明 android:configChanges="orientation|screenSize"

当其中一个配置发生变化时,MyActivity 不会重启。相反,MyActivity 会收到对 onConfigurationChanged() 的调用。向此方法传递Configuration 对象指定新设备配置。您可以通过读取 Configuration 中的字段,确定新配置,然后通过更新界面中使用的资源进行适当的更改。

例如,以下 onConfigurationChanged() 实现检查当前设备方向:

1
2
3
4
5
6
7
8
9
10
11
@Override
public  void  onConfigurationChanged(Configuration newConfig) {
     super .onConfigurationChanged(newConfig);
 
     // Checks the orientation of the screen
     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();
     }
}

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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。

但是自行处理配置变可能导致备用资源的使用更为困难,因此不到万不得已不用,大部分情况推荐用Fragment。

效果图:

五、旋转屏幕时如果任务线程未执行完,如何保存恢复

解决上边提到的问题,在未加载完毕的情况下,旋转屏幕,保存任务线程,重启activity不重新执行异步线程。那么他的难点就是保存任务线程,不销毁它,用上边三个方法都可以实现,推荐用1,2种方法。下边就主要介绍用第二种方法Fragment,那么你也肯定想到了,用Fragment保存AsyncTask就可以了。

代码如下:

1. OtherRetainedFragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
  * 保存对象的Fragment
  * @author 超超boy
  *
  */
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;
     }

和之前差不多就是保存对象是MyAsyncTask。

2.MyAsyncTask:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
public  class  MyAsyncTask  extends  AsyncTask<Void, Void, Void>
{
     //保存外部activity的弱引用
     private  WeakReference<Context> weakReference;
     public  MyAsyncTask(Context context) {
         weakReference =  new  WeakReference<>(context);
     }
     /**
      * 是否完成
      */
     private  boolean  isCompleted;
     /**
      * 进度框
      */
     private  LoadingDialog mLoadingDialog;
     private  List<String> items;
 
     /**
      * 开始时,显示加载框
      */
     @Override
     protected  void  onPreExecute()
     {
         mLoadingDialog =  new  LoadingDialog();
         FixProblemsActivity activity = (FixProblemsActivity) weakReference.get();
         if (activity !=  null )
         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(Context activity)
     {
         if (activity ==  null ){
             mLoadingDialog.dismiss();
             return ;
         }
         weakReference =  new  WeakReference<>(activity);
         // 设置为当前的Activity
         FixProblemsActivity fActivity = (FixProblemsActivity) weakReference.get();
         // 开启一个与当前Activity绑定的等待框
         if  (activity !=  null  && !isCompleted)
         {
             mLoadingDialog =  new  LoadingDialog();
             mLoadingDialog.show(fActivity.getFragmentManager(),  "LOADING" );
         }
         // 如果完成,通知Activity
         if  (isCompleted)
         {
             notifyActivityTaskCompleted();
         }
     }
 
     /**
      * 在Activity不可见时,关闭dialog
      */
     public  void  dialogDismiss(){
         if (mLoadingDialog !=  null ){
             mLoadingDialog.dismiss();
         }
     }
     private  void  notifyActivityTaskCompleted()
     {
         if  ( null  != weakReference.get())
         {
             ((FixProblemsActivity) weakReference.get()).onTaskCompleted();
         }
     }
 
}

主要在onCreate方法中执行一些逻辑判断,如果没有开启任务(第一次进入),开启任务;如果已经开启了,调用setActivity(this);

在onPause中关闭dialog,防止内存泄漏。

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

效果图:

 

 



Activity之概览屏幕(Overview Screen)

概览屏幕

概览屏幕(也称为最新动态屏幕、最近任务列表或最近使用的应用)是一个系统级别 UI,其中列出了最近访问过的 Activity 和任务。 用户可以浏览该列表并选择要恢复的任务,也可以通过滑动清除任务将其从列表中移除。 对于 Android 5.0 版本(API 级别 21),包含不同文档的同一 Activity 的多个实例可能会以任务的形式显示在概览屏幕中。前边是文档中说的,比较虚,下面来一个gif你就懂啦。

 可以看到左边的任务视图是两个重叠在一块的,这也就是概览屏幕。

通常,您应该允许系统定义任务和 Activity 在概览屏幕中的显示方法,并且无需修改此行为。不过,应用可以确定 Activity 在概览屏幕中的显示方式和时间。 您可以使用 ActivityManager.AppTask 类来管理任务,使用 Intent 类的 Activity 标志来指定某 Activity 添加到概览屏幕或从中移除的时间。 此外,您也可以使用 <activity> 属性在清单文件中设置该行为。

将任务添加到概览屏幕


通过使用 Intent 类的标志添加任务,您可以更好地控制某文档在概览屏幕中打开或重新打开的时间和方式。 使用 <activity> 属性时,您可以选择始终在新任务中打开文档,或选择对文档重复使用现有任务。

使用 Intent 标志添加任务

为 Activity 创建新文档时,可调用 ActivityManager.AppTask 类的 startActivity() 方法,以向其传递启动 Activity 的 Intent。 要插入逻辑换行符以便系统将 Activity 视为新任务显示在概览屏幕中,可在启动 Activity 的 Intent 的 addFlags() 方法中传递 FLAG_ACTIVITY_NEW_DOCUMENT 标志。

FLAG_ACTIVITY_NEW_DOCUMENT 标志取代了 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 标志,后者自 Android 5.0(API 级别 21)起已弃用。

如果在创建新文档时设置 FLAG_ACTIVITY_MULTIPLE_TASK 标志,则系统始终会以目标 Activity 作为根创建新任务。此设置允许同一文档在多个任务中打开。以下代码演示了主 Activity 如何执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  void  createNewDocument(View view) {
       final  Intent newDocumentIntent = newDocumentIntent();
       if  (useMultipleTasks) {
           newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
       }
       startActivity(newDocumentIntent);
   }
 
   private  Intent newDocumentIntent() {
       boolean  useMultipleTasks = mCheckbox.isChecked();
       final  Intent newDocumentIntent =  new  Intent( this , NewDocumentActivity. class );
       newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
       newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
       return  newDocumentIntent;
   }
 
   private  static  int  incrementAndGet() {
       Log.d(TAG,  "incrementAndGet(): "  + mDocumentCounter);
       return  mDocumentCounter++;
   }
}

使用 FLAG_ACTIVITY_NEW_DOCUMENT 标志启动的 Activity 必须具有在清单文件中设置的 android:launchMode="standard" 属性值(默认)。

当主 Activity 启动新 Activity 时,系统会搜遍现有任务,看看是否有任务的 Intent 与 Activity 的 Intent 组件名称和 Intent 数据相匹配。 如果未找到任务或者 Intent 包含 FLAG_ACTIVITY_MULTIPLE_TASK 标志,则会以该 Activity 作为其根创建新任务。如果找到的话,则会将该任务转到前台并将新 Intent 传递给 onNewIntent()。新 Activity 将获得 Intent 并在概览屏幕中创建新文档,如下例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected  void  onCreate(Bundle savedInstanceState) {
     super .onCreate(savedInstanceState);
     setContentView(R.layout.activity_new_document);
     mDocumentCount = getIntent()
             .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER,  0 );
     mDocumentCounterTextView = (TextView) findViewById(
             R.id.hello_new_document_text_view);
     setDocumentCounterText(R.string.hello_new_document_counter);
}
 
@Override
protected  void  onNewIntent(Intent intent) {
     super .onNewIntent(intent);
     /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity
     is reused to create a new document.
      */
     setDocumentCounterText(R.string.reusing_document_counter);
}

使用 Activity 属性添加任务

此外,Activity 还可以在其清单文件中指定始终通过使用 <activity> 属性 android:documentLaunchMode 进入新任务。 此属性有四个值,会在用户使用该应用打开文档时产生以下效果:

" intoExisting"
该 Activity 会对文档重复使用现有任务。这与 设置  FLAG_ACTIVITY_MULTIPLE_TASK 标志、但设置  FLAG_ACTIVITY_NEW_DOCUMENT 标志所产生的效果相同,如上文的 使用 Intent 标志添加任务中所述。
" always"
该 Activity 为文档创建新任务,即便文档已打开也是如此。使用此值与同时设置  FLAG_ACTIVITY_NEW_DOCUMENT 和  FLAG_ACTIVITY_MULTIPLE_TASK标志所产生的效果相同。
" none"
该 Activity 不会为文档创建新任务。概览屏幕将按其默认方式对待此 Activity:为应用显示单个任务,该任务将从用户上次调用的任意 Activity 开始继续执行。
" never"
该 Activity 不会为文档创建新任务。设置此值会替代  FLAG_ACTIVITY_NEW_DOCUMENT 和  FLAG_ACTIVITY_MULTIPLE_TASK 标志的行为(如果在 Intent 中设置了其中一个标志),并且概览屏幕将为应用显示单个任务,该任务将从用户上次调用的任意 Activity 开始继续执行。

注:对于除 none 和 never 以外的值,必须使用 launchMode="standard" 定义 Activity。如果未指定此属性,则使用 documentLaunchMode="none"

移除任务


默认情况下,在 Activity 结束后,文档任务会从概览屏幕中自动移除。 您可以使用 ActivityManager.AppTask 类、Intent 标志或 <activity> 属性替代此行为。

通过将 <activity> 属性 android:excludeFromRecents 设置为 true,您可以始终将任务从概览屏幕中完全排除。

您可以通过将 <activity> 属性 android:maxRecents 设置为整型值,设置应用能够包括在概览屏幕中的最大任务数。默认值为 16。达到最大任务数后,最近最少使用的任务将从概览屏幕中移除。 android:maxRecents 的最大值为 50(内存不足的设备上为 25);小于 1 的值无效。

使用 AppTask 类移除任务

在于概览屏幕创建新任务的 Activity 中,您可以通过调用 finishAndRemoveTask() 方法指定何时移除该任务以及结束所有与之相关的 Activity。

1
2
3
4
5
6
7
private  Intent newDocumentIntent() {
     final  Intent newDocumentIntent =  new  Intent( this , NewDocumentActivity. class );
     newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
       android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
     newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
     return  newDocumentIntent;
}

要达到同样的效果,请将 <activity> 属性 android:autoRemoveFromRecents 设置为 false。文档 Activity 的默认值为 true,常规 Activity 的默认值为 false。如前所述,使用此属性替代 FLAG_ACTIVITY_RETAIN_IN_RECENTS 标志。

demo的代码

1.DocumentCentricActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package  com.example.documentcentricapps;
 
import  android.app.Activity;
import  android.content.Intent;
import  android.os.Build;
import  android.os.Bundle;
import  android.os.PersistableBundle;
import  android.util.Log;
import  android.view.View;
import  android.widget.CheckBox;
 
public  class  DocumentCentricActivity  extends  Activity {
 
     private  final  static  String TAG =  "DocumentCentricActivity" ;
 
     public  final  static  String KEY_EXTRA_NEW_DOCUMENT_COUNTER =  "KEY_EXTRA_NEW_DOCUMENT_COUNTER" ;
 
     private  static  int  mDocumentCounter =  0 ;
 
     private  CheckBox mCheckbox;
 
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_document_centric_main);
         mCheckbox = (CheckBox) findViewById(R.id.multiple_task_checkbox);
     }
 
     @Override
     public  void  onPostCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
         super .onPostCreate(savedInstanceState, persistentState);
         // Restore state from PersistableBundle
         if  (persistentState !=  null ) {
             if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                 mDocumentCounter = persistentState.getInt(KEY_EXTRA_NEW_DOCUMENT_COUNTER);
             }
         }
     }
 
     @Override
     public  void  onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
         /*
         To maintain activity state across reboots the system saves and restore critical information for
         all tasks and their activities. Information known by the system includes the activity stack order,
         each task’s thumbnails and each activity’s and task's Intents. For Information that cannot be retained
         because they contain Bundles which can’t be persisted a new constrained version of Bundle,
         PersistableBundle is added. PersistableBundle can store only basic data types. To use it
         in your Activities you must declare the new activity:persistableMode attribute in the manifest.
          */
         if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
             outPersistentState.putInt(KEY_EXTRA_NEW_DOCUMENT_COUNTER, mDocumentCounter);
         }
         super .onSaveInstanceState(outState, outPersistentState);
     }
 
     public  void  createNewDocument(View view) {
         boolean  useMultipleTasks = mCheckbox.isChecked();
         final  Intent newDocumentIntent = newDocumentIntent();
         if  (useMultipleTasks) {
             /*
             When {@linkIntent#FLAG_ACTIVITY_NEW_DOCUMENT} is used with {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
             the system will always create a new task with the target activity as the root. This allows the same
             document to be opened in more than one task.
              */
             newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
         }
         startActivity(newDocumentIntent);
     }
 
 
     /**
      * Returns an new {@link Intent} to start {@link NewDocumentActivity} as a new document in
      * overview menu.
      *
      * To start a new document task {@link Intent#FLAG_ACTIVITY_NEW_DOCUMENT} must be used. The
      * system will search through existing tasks for one whose Intent matches the Intent component
      * name and the Intent data. If it finds one then that task will be brought to the front and the
      * new Intent will be passed to onNewIntent().
      *
      * Activities launched with the NEW_DOCUMENT flag must be created with launchMode="standard".
      */
     private  Intent newDocumentIntent() {
         final  Intent newDocumentIntent =  new  Intent( this , NewDocumentActivity. class );
         newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
         newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
         return  newDocumentIntent;
     }
 
     private  static  int  incrementAndGet() {
         Log.d(TAG,  "incrementAndGet(): "  + mDocumentCounter);
         return  mDocumentCounter++;
     }
 
}

 2.NewDocumentActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public  class  NewDocumentActivity  extends  Activity {
 
     private  TextView mDocumentCounterTextView;
     private  int  mDocumentCount;
 
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_new_document);
         mDocumentCount = getIntent()
                 .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER,  0 );
         mDocumentCounterTextView = (TextView) findViewById(
                 R.id.hello_new_document_text_view);
         setDocumentCounterText(R.string.hello_new_document_counter);
     }
 
     @Override
     protected  void  onNewIntent(Intent intent) {
         super .onNewIntent(intent);
         /* If {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} has not been used this Activity
         will be reused.
          */
         setDocumentCounterText(R.string.reusing_document_counter);
     }
 
     public  void  onRemoveFromOverview(View view) {
         // It is good pratice to remove a document from the overview stack if not needed anymore.
         if  (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
             finishAndRemoveTask();
         }
     }
 
     public  void  setDocumentCounterText( int  resId) {
         mDocumentCounterTextView
                 .setText(String.format(getString(resId), String.valueOf(mDocumentCount)));
     }
 
}

 上边只是主要的代码,完整的demo下载:http://download.csdn.net/detail/jycboy/9715602

 

 


 本文转载自:http://www.cnblogs.com/jycboy/p/overview_screen.html      超超boy

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园的建设目标是通过数据整合、全面共享,实现校园内教学、科研、管理、服务流程的数字化、信息化、智能化和多媒体化,以提高资源利用率和管理效率,确保校园安全。 智慧校园的建设思路包括构建统一支撑平台、建立完善管理体系、大数据辅助决策和建设校园智慧环境。通过云架构的数据中心与智慧的学习、办公环境,实现日常教学活动、资源建设情况、学业水平情况的全面统计和分析,为决策提供辅助。此外,智慧校园还涵盖了多媒体教学、智慧录播、电子图书馆、VR教室等多种教学模式,以及校园网络、智慧班牌、校园广播等教务管理功能,旨在提升教学品质和管理水平。 智慧校园的详细方案设计进一步细化了教学、教务、安防和运维等多个方面的应用。例如,在智慧教学领域,通过多媒体教学、智慧录播、电子图书馆等技术,实现教学资源的共享和教学模式的创新。在智慧教务方面,校园网络、考场监控、智慧班牌等系统为校园管理提供了便捷和高效。智慧安防系统包括视频监控、一键报警、阳光厨房等,确保校园安全。智慧运维则通过综合管理平台、设备管理、能效管理和资产管理,实现校园设施的智能化管理。 智慧校园的优势和价值体现在个性化互动的智慧教学、协同高效的校园管理、无处不在的校园学习、全面感知的校园环境和轻松便捷的校园生活等方面。通过智慧校园的建设,可以促进教育资源的均衡化,提高教育质量和管理效率,同时保障校园安全和提升师生的学习体验。 总之,智慧校园解决方案通过整合现代信息技术,如云计算、大数据、物联网和人工智能,为教育行业带来了革命性的变革。它不仅提高了教育的质量和效率,还为师生创造了一个更加安全、便捷和富有智慧的学习与生活环境。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值