I have 9 custom views (extending the View class each of them) in one of my game window that are causing a freeze in my UI thread, when I press the "Play" button, the app freezes (when inflating the layout in "onCreateView", i'm using Fragments) until the game window is generated, something very very ugly.
I'm trying to do this in a separate thread but all are problems, Android doesn't allow me to create new views out of the main (UI) thread.
I tried so many things but I can't get it, could anyone tell me how to achieve this?
Thank you very much
For very cpu intensive drawing you can use.
http://developer.android.com/reference/android/view/SurfaceView.html
One of the purposes of this class is to provide a surface in which a
secondary thread can render into the screen.
I solved it by manually inflating the layout in an AsyncTask. I call the AsyncTask from the "Play Window" where I show a "loading" view and in "onPostExecute" I create the "Game Window" (Fragment) and replace it.
Some dummy code (PlayFragment is the previous screen of GameFragment):
"Play button click" (PlayFragment):
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_play:
Bundle bundle = new Bundle();
bundle.putSerializable(ARG_SELECTED_GAME, selectedGame);
rlLoadingGame.setVisibility(View.VISIBLE);
GameFragment gameFragment = GameFragment.newInstance(selectedGame);
gameFragment.loadGame(activity, bundle);
break;
}
}
loadGame method (GameFragment):
public void loadGame(Activity activity, Bundle bundle) {
this.activity = activity;
if (bundle != null) {
currentGame = (Game) bundle.getSerializable(ARG_SELECTED_GAME);
}
new GenerateGame(bundle).execute();
}
GenerateGame AsyncTask (GameFragment):
class GenerateGame extends AsyncTask<Void, Void>, View> {
private Bundle bundle;
public GenerateGame(Bundle bundle) {
this.bundle = bundle;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
// do your stuff
}
#Override
protected View doInBackground(Void... params) {
View child = activity.getLayoutInflater().inflate(R.layout.game_fragment_layout, null);
// do all your heavy load stuff
return child;
}
#Override
protected void onPostExecute(View layout) {
super.onPostExecute(layout);
// initialize and set all UI elements
replaceFragment(layout, bundle);
}
}
replaceFragment method (GameFragment):
private void replaceFragment(View newView, Bundle bundle) {
fragmentLayout = newView;
// call to fragment manager replace/add or required method passing this as Fragment to replace and the bundle if needed
}
onCreateView (GameFragment):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (fragmentLayout != null) {
return fragmentLayout;
} else {
return null;
}
}
This is the first approach so it can be refactored and so many things can be done in a better way but it is up to you.
Related
Well, i have 3 swipe tabs and in each tab i have called a asynctask in the fragments onCreateView() method. Its showing result in normal case but the problem is when i rotate the screen. Basically it closes forcefully when a asynctask is going on and before completing the task-i rotate the screen.
So, i have tried to cancel the asynctask in onPause and call it again in onCreateView(). Below is the code inside a fragment:
public class AllFragment extends Fragment
{
SearchBusBeans searchBusObj;
ArrayList<SearchBusBeans> searchBusObj_arr;
SearchingBusesAdapter myAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{ .....
....
searchBusObj_arr = new ArrayList<SearchBusBeans>();
new GetSearchedBusesAsync().execute();
}
#Override
public void onPause() {
super.onPause();
GetSearchedBusesAsync asyncObj = new GetSearchedBusesAsync();
if(asyncObj.getStatus() == AsyncTask.Status.RUNNING){
asyncObj.cancel(true);
}
}
private class GetSearchedBusesAsync extends AsyncTask<Void, Void, Void>
{
#Override
protected void onPreExecute() { super.onPreExecute(); }
#Override
protected Void doInBackground(Void... params)
{
if(isCancelled())
this.cancel(true);
if(!isCancelled())
{...........
......do my codes.......
searchBusObj = new SearchBusBeans();
searchBusObj.setname(j_name);
searchBusObj_arr.add(searchBusObj);
}
return null;
}
#Override
protected void onPostExecute(Void result)
{
super.onPostExecute(result);
pBar.setVisibility(View.GONE);
myAdapter = new SearchingBusesAdapter(getActivity().getApplicationContext(), searchBusObj_arr);
gridView.setAdapter(myAdapter);
}
} //end of asynctask
} //end of fragment
Now as you can see that i have cancelled the asynctask in onPause. and inside doInBackground() i have done my code in condition-if(!isCancelled()). So, shouldn't the asynctask exit and not go to onPostExecute()?
Where am i going wrong? and how to do the cancellation and execution properly?
You can set your fragment to retain its state through a rotation. This will allow your AsyncTask to keep running.
public class AllFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
Override the onCreate method of the fragment and call setRetainInstance(true);. This keeps the fragment alive so that it doesn't get destroyed and then recreated during a configuration change.
There is another approach to stop the execution of an AsyncTask (on any event,not necessary on rotation):
1) Save your AsyncTask in a variable when you call .execute()
mTask = new YourAsyncTask().execute();
2) Then, when you need to stop the task call .cancel()
if(mTask != null) {
mTask.cancel(true);
}
This approach works for me perfectly and it is usable almost for any scenario. Hope it helps!
I've an AppCompatActivity that uses the NavigationDrawer pattern, managing some fragments. In one of these, that has no setRetainInstance(true), I show a DialogFragment with a ProgressDialog inside and an AsyncTask with this code:
SavingLoader savingLoader = SavingLoader.newInstance(savingLoaderMaxValue);
savingLoader.show(getChildFragmentManager(), SAVING_LOADER_TAG);
new MyAsyncTask().execute();
Where the SavingLoader class is this one:
public class SavingLoader extends DialogFragment {
private static final String MAX_VALUE_TAG = "MAX_VALUE_TAG";
private static final String PROGRESS_VALUE_TAG = "PROGRESS_VALUE_TAG";
public static SavingLoader newInstance(int max_value){
SavingLoader s = new SavingLoader();
Bundle args = new Bundle();
args.putInt(MAX_VALUE_TAG, max_value);
s.setArguments(args);
return s;
}
private ProgressDialog dialog;
public SavingLoader(){}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setCancelable(false);
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState){
dialog = new ProgressDialog(getActivity(), getTheme());
dialog.setTitle(getString(R.string.dialog_title_saving));
dialog.setMessage(getString(R.string.dialog_message_saving));
dialog.setIndeterminate(false);
int max = (savedInstanceState == null ?
getArguments().getInt(MAX_VALUE_TAG) : savedInstanceState.getInt(MAX_VALUE_TAG));
if (max >= 1){
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setProgress((savedInstanceState == null ?
0 : savedInstanceState.getInt(PROGRESS_VALUE_TAG)));
dialog.setMax(max);
} else dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
return dialog;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(MAX_VALUE_TAG, dialog.getMax());
outState.putInt(PROGRESS_VALUE_TAG, dialog.getProgress());
}
public int getProgress(){
return dialog.getProgress();
}
public int getMax(){
return dialog.getMax();
}
public void incrementProgressBy(int value){
if (dialog.getProgress() + value <= dialog.getMax())
dialog.incrementProgressBy(value);
}
}
In the onPostExecute() method I need to perform some UI update so here's my problem: if I start the dialog and the AsyncTask (like above) and I don't rotate my phone, all works as expected. Same thing if I rotate phone AFTER the onPostExecute() method. But if I rotate my phone WHILE the AsyncTask is still running, when it completes and reach the onPostExecute() method it gives me the IllegalStateException saying that the fragment hosting the AsyncTask and the Dialogfragment is no longer attached to the activity. So I tried to override both the onAttach() and the onDetach() methods (with a simple System.out.println) of my fragment, to see when the onPostExecute() gets called. The result is that when I rotate my phone, I always got this output:
onDetach
onAttach
... (if I rotate more my phone)
onPostExecute
So shouldn't the fragment be attached when the AsyncTask completes? Thank you all for your time and attention.
I've finally managed to solve this problem by stop using AsyncTask and using LoaderManager + AsyncTaskLoader following this article. In short, your fragment must implement the LoaderCallbacks interface and manage the AsyncTaskLoader. A skeleton fragment could be something like this:
public class MyFragment extends Fragment implements LoaderManager.LoaderCallbacks {
#Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
// Inflate here your view as you usually do and find your components
// For example imagine to have a button tha will fire the task
Button b = (Button) view.findViewById(R.id.my_button);
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Use this to start task for the first time
getLoaderManager().initLoader(0, null, this);
// .. or this for restart the task, details in
// the provided article
// getLoaderManager().restartLoader(0, null, this);
}
});
// Get fragments load manager
LoaderManager lm = getLoaderManager();
if (lm.getLoader(0) != null) {
// Reconnect to an existing loader
lm.initLoader(0, null, this);
}
// Return your view here
return view;
}
// LoaderCallbacks methods to override
#Override
public Loader onCreateLoader(int id, Bundle args) {
// Create an instance of the loader passing the context
MyTaskLoader loader = new MyTaskLoader(getActivity());
loader.forceLoad();
return loader;
}
#Override
public void onLoadFinished(Loader loader, Object data) {
// Use this callback as you would use the AsyncTask "onPostExecute"
}
#Override
public void onLoaderReset(Loader loader) {}
// Now define the loader class
private static class MyTaskLoader extends AsyncTaskLoader {
public MyTaskLoader(Context context){
super(context);
}
#Override
public Object loadInBackground() {
// Do here your async work
}
}
}
I have called an Activity method that updates UI from a Fragment and I get a silent CalledFromWrongThreadException that tells me Only the original thread that created a view hierarchy can touch its views.
My Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
createDynamicView();
}
public void createDynamicView() {
// Create some View and attach that to a ViewGroup dynamically as below:
RelativeLayout rl = new RelativeLayout(this);
// ...
TextView textView = new TextView(this);
// ...
rl.addView(textView, layout_params1);
layout.addView(rl, layout_params2);
}
// Method called from Fragment
#Override
public void updateLayout() {
View v = layout.getChaildAt(index); // v is a TextView
// This line throws a silent Exception:
// android.view.ViewRootImpl$CalledFromWrongThreadException:
// Only the original thread that created a view hierarchy can touch its views
v.setBackgroundColor(Color.WHITE);
}
Inside fragment:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
parent = (SettingsInterface) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement SettingsInterface");
}
}
public void updateLayout() {
parent.updateLayout();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
webView = (WebView) view.findViewById(R.id.webView1);
// ...
textWebView.addJavascriptInterface(new JSInterface(getActivity()), "Android");
}
public class JSInterface {
#JavascriptInterface
public void updateLayout() {
updateLayout();
}
}
If I put that line inside a runOnUiThread block as below:
runOnUiThread(new Runnable() {
#Override
public void run() {
v.setBackgroundColor(Color.WHITE);
}
});
I will not get exception but this could may run after first UI update.
I want to know that does fragment run in a separate thread than UI thread?
and why this exception is silent?
Fragments and activities are both running on the main UI thread. Your problem is you are calling your updateLayout() method from a JS thread which is NOT your UI thread. And since you cannot modify the UI components from any other thread than the main UI thread, you will get that exception.
Solution: like you said, use runOnUiThread.
We use viewpager fragment in it, we want to show every single page when viewpager show a fragment and load data , because it saves memory a lot, but we can not show a sign for loading when data loading, we replace a fragment when asynctask's onPreExecute calls and we replace again when calls onPostExecute, but there is some thing wrong, which is the best way to show loading sign when data loading?
public class TestFragment extends Fragment {
private FragmentManager manager = null;
private class TestTask extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
getFragmentManager().beginTransaction()
.replace(R.id.content_container, new LoadingFragment())
.commit();
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... arg0) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
getFragmentManager().beginTransaction()
.replace(R.id.content_container, new ContentFragment())
.commit();
super.onPostExecute(result);
}
}
public TestFragment(FragmentManager supportFragmentManager) {
this.manager = supportFragmentManager;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new TestTask().execute();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.empty_page, null);
}
}
Another easy way if you use the action bar in your app is calling supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); in the FragmentActivity right after super.onCreate(savedInstanceState); and from the child fragment ((ActionBarActivity)getActivity()).setSupportProgressBarIndeterminateVisibility(visibility); will show or hide a progress bar in the action bar of your hosting activity depending on your progress in the AsyncTask.
Note: I'm using support appcompat, but I'm pretty sure the methods are the same if you use ActionBarSherlock.
add an progressbar to the layout of the fragment but defining it invisible and centering it both vertically and horizontally. then in the onPreExecute set the visibility of all other views to Visibility.GONE and set the progressbar to VISIBLE. reverse this in the onPostExecute() method!
I been running into an issue with loaders lately. I created a small project that reproduces the issue https://github.com/solcott/loaders-orientation-change
I have a simple Activity that adds a fragment
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
if(mainFragment == null){
Log.w(getClass().getSimpleName(), "creating new Fragment");
mainFragment = new MainFragment();
getFragmentManager().beginTransaction().add(R.id.main_fragment, mainFragment).commit();
}
}
}
In my fragment I start a Loader that just returns an integer that is displayed on the screen. There is also a button that starts a new Activity:
public class MainFragment extends Fragment implements LoaderCallbacks<Integer> {
private static int NEXT_VAL = 0;
TextView text1;
Button button1;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(1, null, this);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
text1 = (TextView) view.findViewById(android.R.id.text1);
button1 = (Button) view.findViewById(android.R.id.button1);
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(getActivity(), NextActivity.class));
}
});
}
#Override
public Loader<Integer> onCreateLoader(int id, Bundle args) {
AsyncTaskLoader<Integer> loader = new AsyncTaskLoader<Integer>(
getActivity()) {
#Override
public Integer loadInBackground() {
return NEXT_VAL++;
}
};
loader.forceLoad();
return loader;
}
#Override
public void onLoadFinished(Loader<Integer> loader, Integer data) {
text1.setText(data.toString());
}
#Override
public void onLoaderReset(Loader<Integer> loader) {
// TODO Auto-generated method stub
}
}
Up to this point everything works fine I can change the orientation and the integer that is displayed doesn't change. I can even start the new activity and then hit the back button and the integer displayed doesn't change.
However, when I navigate to the new Activity, change orientation and press the back button the integer is incremented. I would expect it to behave the same way that it did without the orientation change.
Calling setRetainInstance(true) in Fragment.onCreate() make it even worse. onLoadComplete is never called and the integer is not displayed at all.
Has anyone else run into this issue and found a workaround?
Same question as asked here: Loader unable to retain itself during certain configuration change
This is a damn annoying problem with AsyncTask loaders. Although a solution is proposed in that thread, I think the general consensus is to avoid AsyncTaskLoaders.