내 문제를 나타내는 작은 테스트 응용 프로그램을 만들었습니다. ActionBarSherlock을 사용하여 (Sherlock) Fragments로 탭을 구현하고 있습니다.
내 코드 :
TestActivity.java
public class TestActivity extends SherlockFragmentActivity {
private ActionBar actionBar;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupTabs(savedInstanceState);
}
private void setupTabs(Bundle savedInstanceState) {
actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
addTab1();
addTab2();
}
private void addTab1() {
Tab tab1 = actionBar.newTab();
tab1.setTag("1");
String tabText = "1";
tab1.setText(tabText);
tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class));
actionBar.addTab(tab1);
}
private void addTab2() {
Tab tab1 = actionBar.newTab();
tab1.setTag("2");
String tabText = "2";
tab1.setText(tabText);
tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class));
actionBar.addTab(tab1);
}
}
TabListener.java
public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener {
private final SherlockFragmentActivity mActivity;
private final String mTag;
private final Class<T> mClass;
public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction ft) {
SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
// Check if the fragment is already initialized
if (preInitializedFragment == null) {
// If not, instantiate and add it to the activity
SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.attach(preInitializedFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
if (preInitializedFragment != null) {
// Detach the fragment, because another one is being attached
ft.detach(preInitializedFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// User selected the already selected tab. Usually do nothing.
}
}
MyFragment.java
public class MyFragment extends SherlockFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
}
return null;
}
@Override
protected void onPostExecute(Void result){
getResources().getString(R.string.app_name);
}
}.execute();
}
}
Thread.sleep
데이터 다운로드를 시뮬레이션 하는 부분을 추가했습니다 . 의 코드는의 onPostExecute
사용을 시뮬레이션하는 것입니다 Fragment
.
가로와 세로 사이에서 화면을 매우 빠르게 회전하면 onPostExecute
코드 에서 예외가 발생 합니다.
java.lang.IllegalStateException : Fragment MyFragment {410f6060}이 활동에 첨부되지 않았습니다
MyFragment
그 동안 새로운 것이 만들어 져서 AsyncTask
완료 되기 전에 활동에 첨부 되었기 때문이라고 생각합니다 . 코드 onPostExecute
는 첨부되지 않은 호출합니다 MyFragment
.
그러나 어떻게 해결할 수 있습니까?
답변
나는 매우 간단한 대답을 찾았습니다. isAdded()
:
true
프래그먼트가 현재 활동에 추가 된 경우 리턴 합니다.
@Override
protected void onPostExecute(Void result){
if(isAdded()){
getResources().getString(R.string.app_name);
}
}
에 연결되지 않은 상태에서 onPostExecute
호출 되지 않도록하려면 일시 중지 또는 중지시 를 취소하는 것 입니다. 그러면 더 이상 필요하지 않을 것입니다. 그러나이 점검을 유지하는 것이 좋습니다.Fragment
Activity
AsyncTask
Fragment
isAdded()
답변
문제는 getResources (). getString ()을 사용하여 자원 (이 경우 문자열)에 액세스하려고합니다. 그러면 활동에서 자원을 가져 오려고합니다. Fragment 클래스의 다음 소스 코드를 참조하십시오.
/**
* Return <code>getActivity().getResources()</code>.
*/
final public Resources getResources() {
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
return mHost.getContext().getResources();
}
mHost
활동을 보유한 개체입니다.
활동이 첨부되지 않았기 때문에 getResources () 호출에서 예외가 발생합니다.
받아 들여진 솔루션 IMHO는 문제를 숨기고 갈 길이 아닙니다. 올바른 방법은 응용 프로그램 컨텍스트와 같이 항상 존재하는 다른 곳에서 리소스를 얻는 것입니다.
youApplicationObject.getResources().getString(...)
답변
나는 두 가지 다른 시나리오에 직면했다.
1) 어쨌든 비동기 작업을 끝내고 싶을 때 : 내 onPostExecute가 수신 된 데이터를 저장 한 다음 리스너를 호출하여 뷰를 업데이트한다고 가정하면보다 효율적으로 작업이 완료되어 사용자가 왔을 때 데이터를 준비하려고합니다. 뒤. 이 경우 나는 보통 이것을한다 :
@Override
protected void onPostExecute(void result) {
// do whatever you do to save data
if (this.getView() != null) {
// update views
}
}
2) 뷰를 업데이트 할 수있을 때만 비동기 작업을 끝내고 싶을 때 : 여기에서 제안하는 경우 작업은 뷰만 업데이트하고 데이터 저장소는 필요하지 않으므로 뷰가있는 경우 작업을 마칠 수있는 단서가 없습니다. 더 이상 보여지지 않습니다. 나는 이것을한다:
@Override
protected void onStop() {
// notice here that I keep a reference to the task being executed as a class member:
if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true);
super.onStop();
}
나는 조각 대신 활동에서 작업을 시작하는 것을 포함하여 (아마도) 더 복잡한 방법을 사용하지만이 문제는 발견하지 못했습니다.
누군가를 도와주세요! 🙂
답변
코드 문제는 휴면 스레드 중에 화면을 회전 할 때 AsyncTask를 사용하는 방식입니다.
Thread.sleep(2000)
AsyncTask가 여전히 작동하고 있기 때문에 조각이 다시 빌드되기 전에 (회전 할 때)와 동일한 AsyncTask 인스턴스 (회전 후)가 onPostExecute ()에서 실행될 때 onDestroy ()에서 AsyncTask 인스턴스를 올바르게 취소하지 않았기 때문입니다. 이전 프래그먼트 인스턴스 (잘못된 인스턴스)가있는 getResources ()가있는 리소스 :
getResources().getString(R.string.app_name)
이는 다음과 같습니다.
MyFragment.this.getResources().getString(R.string.app_name)
따라서 최종 솔루션은 화면을 회전 할 때 프래그먼트가 다시 빌드되기 전에 AsyncTask 인스턴스를 관리하고 (이것이 여전히 작동하는 경우 취소), 전환 중에 취소 된 경우 부울 플래그를 사용하여 재구성 후 AsyncTask를 다시 시작하십시오.
public class MyFragment extends SherlockFragment {
private MyAsyncTask myAsyncTask = null;
private boolean myAsyncTaskIsRunning = true;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!=null) {
myAsyncTaskIsRunning = savedInstanceState.getBoolean("myAsyncTaskIsRunning");
}
if(myAsyncTaskIsRunning) {
myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("myAsyncTaskIsRunning",myAsyncTaskIsRunning);
}
@Override
public void onDestroy() {
super.onDestroy();
if(myAsyncTask!=null) myAsyncTask.cancel(true);
myAsyncTask = null;
}
public class MyAsyncTask extends AsyncTask<Void, Void, Void>() {
public MyAsyncTask(){}
@Override
protected void onPreExecute() {
super.onPreExecute();
myAsyncTaskIsRunning = true;
}
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {}
return null;
}
@Override
protected void onPostExecute(Void result){
getResources().getString(R.string.app_name);
myAsyncTaskIsRunning = false;
myAsyncTask = null;
}
}
}
답변
그것들은 이것에 대한 트릭 솔루션이며 활동으로 인한 조각 유출입니다.
따라서 getResource 또는 Fragment에서 액세스하는 활동 컨텍스트에 따라 다른 경우 항상 다음과 같이 활동 상태 및 조각 상태를 확인합니다.
Activity activity = getActivity();
if(activity != null && isAdded())
getResources().getString(R.string.no_internet_error_msg);
//Or any other depends on activity context to be live like dailog
}
}
답변
if (getActivity() == null) return;
경우에 따라 작동합니다. 코드 실행을 중단하고 앱이 충돌하지 않도록하십시오.
답변
나는 같은 문제에 직면했다. 나는 Erick이 참조 한대로 리소스를 얻기 위해 단일 톤 인스턴스를 추가한다.
MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name);
당신은 또한 사용할 수 있습니다
getActivity().getResources().getString(R.string.app_name);
이것이 도움이되기를 바랍니다.