Android屏幕旋轉(zhuǎn) 處理Activity與AsyncTask的最佳解決方案
一、概述
運行時變更就是設備在運行時發(fā)生變化(例如屏幕旋轉(zhuǎn)、鍵盤可用性及語言)。發(fā)生這些變化,Android會重啟Activity,這時就需要保存activity的狀態(tài)及與activity相關的任務,以便恢復activity的狀態(tài)。
為此,google提供了三種解決方案:
- 對于少量數(shù)據(jù): 通過onSaveInstanceState(),保存有關應用狀態(tài)的數(shù)據(jù)。 然后在 onCreate() 或 onRestoreInstanceState() 期間恢復 Activity 狀態(tài)。
- 對于大量數(shù)據(jù):用 Fragment 保留需要回復的對象。
- 自行處理配置變更,不重啟Activity。
下面會逐一介紹三種情況,其實保存一些變量對象很簡單,難的是當Activity創(chuàng)建異步線程去加載數(shù)據(jù)時,旋轉(zhuǎn)屏幕時,怎么保存線程的狀態(tài)。比如,在線程的加載過程中,旋轉(zhuǎn)屏幕,就會存在問題:此時數(shù)據(jù)沒有完成加載,onCreate重新啟動時,會再次啟動線程;而上個線程可能還在運行,并且可能會更新已經(jīng)不存在的控件,造成錯誤。下面會一一解決這些問題。本文較長,主要是代碼多,可以先下載demo,源碼下載:http://download.csdn.net/detail/jycboy/9720486對比著看。
二、使用onSaveInstanceState,onRestoreInstanceState
代碼如下:
/**
* 使用onSaveInstanceState,onRestoreInstanceState;
* 在這里不考慮沒有加載完畢,就旋轉(zhuǎn)屏幕的情況。
* @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);
}
/**
* 初始化數(shù)據(jù)
*/
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
//在這里保存數(shù)據(jù),好用于返回
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保存大量數(shù)據(jù)",
"onSaveInstanceState保存數(shù)據(jù)",
"getLastNonConfigurationInstance已經(jīng)被棄用", "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中啟動一個異步任務去加載數(shù)據(jù),這里使用Thread.sleep模擬了一個耗時操作;當用戶旋轉(zhuǎn)屏幕發(fā)生重新啟動時,會onSaveInstanceState中進行數(shù)據(jù)的存儲,在onCreate中對數(shù)據(jù)進行恢復,免去了不必要的再加載一遍。
運行結果:
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
當正常加載數(shù)據(jù)完成之后,用戶不斷進行旋轉(zhuǎn)屏幕,log會不斷打出:onSaveInstanceState->onDestroy->onCreate->onRestoreInstanceState,驗證Activity重新啟動,但是我們沒有再次去進行數(shù)據(jù)加載。
如果在加載的時候,進行旋轉(zhuǎn),則會發(fā)生錯誤,異常退出(退出原因:dialog.dismiss()時發(fā)生NullPointException,因為與當前對話框綁定的FragmentManager為null,在這里這個不是關鍵)。
效果圖:

三、使用Fragment保留對象,恢復數(shù)據(jù)
果重啟 Activity 需要恢復大量數(shù)據(jù)、重新建立網(wǎng)絡連接或執(zhí)行其他密集操作,依靠系統(tǒng)通過onSaveInstanceState() 回調(diào)為您保存的 Bundle,可能無法完全恢復 Activity 狀態(tài),因為它并非設計用于攜帶大型對象(例如位圖),而且其中的數(shù)據(jù)必須先序列化,再進行反序列化,這可能會消耗大量內(nèi)存并使得配置變更速度緩慢。 在這種情況下,如果 Activity 因配置變更而重啟,則可通過保留 Fragment 來減輕重新初始化 Activity 的負擔。此片段可能包含對您要保留的有狀態(tài)對象的引用。
當 Android 系統(tǒng)因配置變更而關閉 Activity 時,不會銷毀您已標記為要保留的 Activity 的片段。 您可以將此類片段添加到 Activity 以保留有狀態(tài)的對象。
要在運行時配置變更期間將有狀態(tài)的對象保留在片段中,請執(zhí)行以下操作:
- 擴展 Fragment 類并聲明對有狀態(tài)對象的引用。
- 在創(chuàng)建片段后調(diào)用 setRetainInstance(boolean)。
- 將片段添加到 Activity。
- 重啟 Activity 后,使用 FragmentManager 檢索片段。
例如,按如下方式定義片段:
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 綁定的對象,例如,Drawable、Adapter、View 或其他任何與 Context 關聯(lián)的對象。否則,它將使Activity無法被回收造成內(nèi)存泄漏。(泄漏資源意味著應用將繼續(xù)持有這些資源,但是無法對其進行垃圾回收,因此可能會丟失大量內(nèi)存)
下面舉一個實際的例子:
1.RetainedFragment
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:
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時,圖片也不一定就加載完了,比如在加載的過程中,旋轉(zhuǎn)屏幕,此時圖片就沒有加載完
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");
}
}
/**
* 異步下載圖片的任務。
* 設置成靜態(tài)內(nèi)部類是為了防止內(nèi)存泄漏
* @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();
//調(diào)用回調(diào)方法
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對象防止內(nèi)存泄漏,下載完畢后用onLoaded回調(diào)方法更新UI。
通過檢查dataFragment、mBitmap判斷是否已經(jīng)加載過,加載過直接用就可以。
效果圖:

在gif里可以看到,如果未加載完畢就旋轉(zhuǎn)屏幕,它會重新啟動異步線程去下載,這種效果并不好,我們會在最后解決這個問題。
四、自行處理配置變更
如果應用在特定配置變更期間無需更新資源,并且因性能限制您需要盡量避免重啟,則可聲明 Activity 將自行處理配置變更,這樣可以阻止系統(tǒng)重啟 Activity。
要聲明由 Activity 處理配置變更,需設置清單文件manifest:
<activity android:name=".MyActivity" android:configChanges="orientation|keyboardHidden" android:label="@string/app_name">
"orientation" 和 "keyboardHidden",分別用于避免因屏幕方向和可用鍵盤改變而導致重啟)。您可以在該屬性中聲明多個配置值,方法是用管道 | 字符分隔這些配置值。
注意:API 級別 13 或更高版本的應用時,若要避免由于設備方向改變而導致運行時重啟,則除了 "orientation" 值以外,您還必須添加 "screenSize" 值。 也就是說,您必須聲明 android:configChanges="orientation|screenSize"。
當其中一個配置發(fā)生變化時,MyActivity 不會重啟。相反,MyActivity 會收到對onConfigurationChanged() 的調(diào)用。向此方法傳遞Configuration 對象指定新設備配置。您可以通過讀取Configuration 中的字段,確定新配置,然后通過更新界面中使用的資源進行適當?shù)母摹?/p>
例如,以下onConfigurationChanged() 實現(xiàn)檢查當前設備方向:
@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();
}
}
示例代碼:
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);
}
/**
* 初始化數(shù)據(jù)
*/
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保存大量數(shù)據(jù)",
"onSaveInstanceState保存數(shù)據(jù)",
"getLastNonConfigurationInstance已經(jīng)被棄用", "RabbitMQ", "Hadoop",
"Spark"));
}
/**
* 當配置發(fā)生變化時,不會重新啟動Activity。但是會回調(diào)此方法,用戶自行進行對屏幕旋轉(zhuǎn)后進行處理
*/
@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();
}
}
這種方法使用簡單,在回調(diào)方法onConfigurationChanged執(zhí)行你的操作就可以,不會重啟Activity。
但是自行處理配置變可能導致備用資源的使用更為困難,因此不到萬不得已不用,大部分情況推薦用Fragment。
效果圖:

五、旋轉(zhuǎn)屏幕時如果任務線程未執(zhí)行完,如何保存恢復
解決上邊提到的問題,在未加載完畢的情況下,旋轉(zhuǎn)屏幕,保存任務線程,重啟activity不重新執(zhí)行異步線程。那么他的難點就是保存任務線程,不銷毀它,用上邊三個方法都可以實現(xiàn),推薦用1,2種方法。下邊就主要介紹用第二種方法Fragment,那么你也肯定想到了,用Fragment保存AsyncTask就可以了。
代碼如下:
1. OtherRetainedFragment
/**
* 保存對象的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:
public class MyAsyncTask extends AsyncTask<Void, Void, Void>
{
//保存外部activity的弱引用
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
private FixProblemsActivity activity;
/**
* 是否完成
*/
private boolean isCompleted;
/**
* 進度框
*/
private LoadingDialog mLoadingDialog;
private List<String> items;
/**
* 開始時,顯示加載框
*/
@Override
protected void onPreExecute()
{
mLoadingDialog = new LoadingDialog();
activity = (FixProblemsActivity) weakReference.get();
if(activity != null)
mLoadingDialog.show(activity.getFragmentManager(), "LOADING");
}
/**
* 加載數(shù)據(jù)
*/
@Override
protected Void doInBackground(Void... params)
{
items = loadingData();
return null;
}
/**
* 加載完成回調(diào)當前的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保存大量數(shù)據(jù)",
"onSaveInstanceState保存數(shù)據(jù)",
"getLastNonConfigurationInstance已經(jīng)被棄用", "RabbitMQ", "Hadoop",
"Spark"));
}
/**
* 設置Activity,因為Activity會一直變化
*
* @param activity
*/
public void setActivity(Context activity)
{ weakReference = new WeakReference<>(activity);
// 設置為當前的Activity
this.activity = (FixProblemsActivity) activity;
// 開啟一個與當前Activity綁定的等待框
if (activity != null && !isCompleted)
{
mLoadingDialog = new LoadingDialog();
mLoadingDialog.show(this.activity.getFragmentManager(), "LOADING");
}
// 如果完成,通知Activity
if (isCompleted)
{
notifyActivityTaskCompleted();
}
}
/**
* 在Activity不可見時,關閉dialog
*/
public void dialogDismiss(){
if(mLoadingDialog != null){
mLoadingDialog.dismiss();
}
}
private void notifyActivityTaskCompleted()
{
if (null != activity)
{
activity.onTaskCompleted();
}
}
}
- 和之前一樣保留Activity的虛引用,防止內(nèi)存泄漏。
- setActivity()方法用于任務未完成時,重啟activity相應的創(chuàng)建一個新的dialog。
- dialogDismiss用于在在Activity不可見時,關閉dialog,防止以前的dialog造成內(nèi)存泄漏。
- 當任務完成時,調(diào)用activity的回調(diào)方法onTaskCompleted更新UI。
3.FixProblemsActivity
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)
{ //與新的Activity進行綁定
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 onDestroy()
{
Log.e(TAG, "onDestroy");
super.onDestroy();
}
@Override
//在這里關閉Dialog,否則容易造成內(nèi)存泄漏
protected void onPause() {
super.onPause();
mMyTask.dialogDismiss();
}
/**
* 回調(diào)方法,更新UI
* 這里如果在加載的過程中按下返回鍵返回主Activity時,會出現(xiàn)異常,setAdapter on a null object reference。因為activity被銷毀,
* 要解決這個問題,可以監(jiān)聽返回鍵事件做相應處理。
*/
public void onTaskCompleted()
{
mDatas = mMyTask.getItems();
mAdapter = new ArrayAdapter<String>(FixProblemsActivity.this,
android.R.layout.simple_list_item_1, mDatas);
setListAdapter(mAdapter);
}
}
主要在onCreate方法中執(zhí)行一些邏輯判斷,如果沒有開啟任務(第一次進入),開啟任務;如果已經(jīng)開啟了,調(diào)用setActivity(this);
在onPause中關閉dialog,防止內(nèi)存泄漏。
設置了等待5秒,足夠旋轉(zhuǎn)三四個來回了~~~~可以看到雖然在不斷的重啟,但是絲毫不影響任務的運行和加載框的顯示~~~~
效果圖:

寫著寫著就這么晚了,本以為會寫的很快。。中間Androidstudio的錄制視頻的功能還不好使啦,逼得我在手機上下了個。。。好分析到此結束。
查閱資料時的一些參考文檔:
https://developer.android.google.cn/guide/topics/resources/runtime-changes.html#HandlingTheChange
http://blog.csdn.net/lmj623565791/article/details/37936275
https://developer.android.google.cn/guide/components/activities.html
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
- Android實現(xiàn)屏幕旋轉(zhuǎn)四個方向準確監(jiān)聽
- Android如何監(jiān)聽屏幕旋轉(zhuǎn)
- Android屏幕旋轉(zhuǎn)之橫屏豎屏切換的實現(xiàn)
- Android6.0開發(fā)中屏幕旋轉(zhuǎn)原理與流程分析
- Android webview旋轉(zhuǎn)屏幕導致頁面重新加載問題解決辦法
- 詳解Android中Runtime解決屏幕旋轉(zhuǎn)問題(推薦)
- Android實現(xiàn)屏幕旋轉(zhuǎn)方法總結
- Android開發(fā) 旋轉(zhuǎn)屏幕導致Activity重建解決方法
- Android實現(xiàn)簡單旋轉(zhuǎn)動畫
- Android App獲取屏幕旋轉(zhuǎn)角度的方法
相關文章
Android中通過RxJava進行響應式程序設計的入門指南
響應式編程在Android中的運用是非常犀利的,比如在異常處理和調(diào)度器方面,這里我們將從生命周期等方面來講解Android中通過RxJava進行響應式程序設計的入門指南:2016-06-06
android開發(fā)環(huán)境遇到adt無法啟動的問題分析及解決方法
開始研究android開發(fā),搭建開發(fā)環(huán)境的時候就出了問題,真是束手無策2013-02-02
RecyclerView嵌套RecyclerView滑動卡頓的解決方法
這篇文章主要為大家詳細介紹了RecyclerView嵌套RecyclerView滑動卡頓的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12
Android實現(xiàn)聯(lián)動下拉框二級地市聯(lián)動下拉框功能
這篇文章主要介紹了Android實現(xiàn)聯(lián)動下拉框二級地市聯(lián)動下拉框功能,本文給大家分享思路步驟,給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2018-12-12
Android 自動化測試經(jīng)驗分享 UiObejct.getFromParent()的使用方法
本篇文章對Android中UiObejct.getFromParent()的使用進行了詳細的分析介紹。需要的朋友參考下2013-05-05
Android實現(xiàn)iPhone晃動撤銷輸入功能 Android仿微信搖一搖功能
這篇文章主要為大家詳細介紹了Android實現(xiàn)iPhone晃動撤銷輸入功能,Android仿微信搖一搖功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07

