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.
Related
I have a ProgressBar in the layout of a Fragment that I want to set to Visible at the beggining of an OnClick method of a Button, and then back to Gone at the end of the method:
public class ProfileTab extends Fragment {
private Button update;
private ProgressBar wait;
which I initialize in OnCreateView:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.profile_tab_layout, container, false);
update= v.findViewById(R.id.Update);
wait = v.findViewById(R.id.WaitUpdateInfo);
//wait.setVisibility(View.VISIBLE);
//wait.setVisibility(View.GONE);
update.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
wait.setVisibility(View.VISIBLE);
/*
Code to execute during OnCLick
*/
wait.setVisibility(View.GONE);
}
});
I have checked and I am sure the visibility of wait never changes during the OnClick method. However, it DOES change during the OnCreateView if I uncomment the pertinent code.
Also, trying this same scenario in a regular Activity (a button and a ProgressBar, initialized and assigned the OnClick method in the Activity's OnCreate), the visibility is modified both in the Oncreate method AND on the execution of OnClick, so I can only guess there is some behaviour relative to Fragments that I'm not managing to catch...
OnClick runs on the Main UI thread. Your view will not be updated until this logic has completed. You update the wait view in OnClick, but that change will not be visible until the OnClick method has completed, and the UI is updated. At the end of your OnClick method, nothing has changed with the wait view (remains GONE, as it was at the start of the method). Try running your logic off of the main thread. AsyncTask is designed for this paradigm https://developer.android.com/reference/android/os/AsyncTask:
private class DoWork extends AsyncTask<Void, Void, Void>{
#Override
protected void onPreExecute() {
super.onPreExecute();
wait.setVisibility(View.VISIBLE);
}
#Override
protected Void doInBackground(Void... voids) {
/*
Code to execute while wait is visible
*/
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
wait.setVisibility(View.GONE);
}
}
...
update.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DoWork doWork = new DoWork();
doWork.execute();
}});
I'm new in android. I have a code which is consisted ViewPager and TabLayout. And it has 4 Fragments. On my Fragment4 code, I want to use CircleProgressView which I got in GitHub. To use that code, I should use AsyncTask. And it has "MainActivity.this.runOnUiThread(new Runnable){}". But this code occurred error. The message is "MainActivity is not an enclosing class." I think it means MainActivity class could not be referred. How can I call "MainActivity.this.runOnUiThread()"? Please tell me how to fix code.
Here is a part of my code.
public class Fragment4 extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment4, container, false);
...
return rootView;
}
#Override
public void onStart() {
super.onStart();
}
private class LongOperation extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
mCircleView.setValue(0);
mCircleView.spin();
}
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
mCircleView.setValueAnimated(42);
}
}
}
MainActivity.this is just a way to get the Activity context, however there are multiple other ways to obtain this context.
Use getActivity() to get the context from the current Activity
Use getApplicationContext(), which is a different context which is the same for all activities but should work here too.
If you use API 23 or above you can use getContext() from within a Fragment to get the current context.
Use getActivity() instead of MainActivity.this
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mCircleView.setValue(0);
mCircleView.spin();
}
});
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.
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(2131296513, class xyz.ScrollDetectableListView) with Adapter(class android.widget.HeaderViewListAdapter)]
I am getting above exception sometimes while scrolling through the dynamic listview and then clicking on item.I researched a lot but unable to find the exact reason that why i am getting this error sometimes and how it can be resolved?
private ScrollDetectableListView mFListView;
public FAdapter mFAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_feed_view, container, false);
View headerView = getActivity().getLayoutInflater().inflate(R.layout.view_feed_header, null);
try{
mFListView = (ScrollDetectableListView) rootView.findViewById(R.id.feed_list_view);
mFContainer = (SwipeRefreshLayout) rootView.findViewById(R.id.feed_container);
mFListView.addHeaderView(headerView);
mFListView.setEmptyView(rootView.findViewById(R.id.empty_view));
mFContainer.setColorSchemeResources(R.color.green, R.color.pink, R.color.fbcolor,
R.color.instagramcolor, R.color.googlecolor, R.color.flickrcolor);
mFView = getActivity().getLayoutInflater().inflate(R.layout.view_footer, null);
ImageView rotateImageView = (ImageView) mFooterView.findViewById(R.id.spinner);
Animation rotation = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate);
rotation.setFillAfter(false);
rotateImageView.startAnimation(rotation);
mFContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh()
{
initializeFListView();
}
});
initializeFListView();
mProgressDialog.setVisibility(View.VISIBLE);
mHActivity.setDataChangedListener(new DataChangedListener() {
#Override
public void onDataChanged() {
mFContainer.setRefreshing(true);
mProgressDialog.setVisibility(View.GONE);
initializeFListView();
}
});
}catch(Exception e){}
return rootView;
}
public void initializeFListView()
{
FApi.getTrending(getActivity(), xyz, new APIResponseListener() {
#Override
public void onResponse(Object response) {
setFeedAdapter((List<Video>) response);
}
#Override
public void onError(VolleyError error) {
if (error instanceof NoConnectionError) {
String errormsg = getResources().getString(R.string.no_internet_error_msg);
Toast.makeText(getActivity(), errormsg, Toast.LENGTH_LONG).show();
}
}
});
}
private void setFAdapter(List<Video> response)
{try {
List<Video> videos = response;
mFAdapter = new FAdapter(getActivity(), videos, mProfileClickListener, mCommentClickListener);
mFListView.setOnScrollListener(new EndlessScrollListenerFeedView(getActivity(), mFListView, mFView, mFAdapter, videos, mFType, ""));
mFListView.setAdapter(mFAdapter);
mProgressDialog.setVisibility(View.GONE);
if (mFContainer.isRefreshing()) {
mFContainer.setRefreshing(false);
}
if (mFAdapter.getCount() < mCount) {
mFView.setVisibility(View.GONE);
mFListView.removeFooterView(mFooterView);
}
}catch(Exception e){}
}
}
My suggestion try to set ur list adapter on UI Thread,,,
private void setFAdapter(List<Video> response)
{
try {
List<Video> videos = response;
mFAdapter = new FAdapter(getActivity(), videos, mProfileClickListener, mCommentClickListener);
mFListView.setOnScrollListener(new EndlessScrollListenerFeedView(getActivity(), mFListView, mFView, mFAdapter, videos, mFType, ""));
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
mFListView.setAdapter(mFAdapter);
}
});
mProgressDialog.setVisibility(View.GONE);
if (mFContainer.isRefreshing()) {
mFContainer.setRefreshing(false);
}
if (mFAdapter.getCount() < mCount) {
mFView.setVisibility(View.GONE);
mFListView.removeFooterView(mFooterView);
}
}catch(Exception e){}
}
}
Keep one singleton class object in hand. So that you can synchronize two thread on it. Care to be taken to not to block the ui thread.
Reduce number of interfaces to only one method to start preparing data for your list and only one method to call your notifydatasetchanged/setAdapter on list.
Means there should be only one method like prepareData() which will be executed by a background thread. synchronise this method on your singleton object.
MyListAdaper adapter = null;
// Call this from a background thread
public void prepareData() {
synchronized (SingleTonProvider.getInstance()) {
List<AnyDataTypeYouWant> data = null;
// populate data here by your application logic.
adapter = new MyListAdaper(data);
}
}
And have only one method to refresh list.
// Also Call this from a background thread only
public void refreshList() {
synchronized (SingleTonProvider.getInstance()) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mFListView.setAdapter(adapter);
}
});
}
}
have no other code on any place to prepare data and set data on list.
Call the methods I mentioned from a background thread only.
I just gave general solution to your problem. You have to work on your specific case by yourself.
I created a View component for android. The component has its own thread to make background work. I start the thread in the View constructor.
Where should I stop the thread? Is onDetachedFromWindow a correct place to make it?
I would do it the following way provided the Thread has to be active during the time the View has a Surface and uses it for drawing:
public class MyView extends View {
class MyThread extends Thread {
private boolean mActive = true;
public void run() {
while (mActive) {
doThings();
}
}
public void terminate() {
mActive = false;
}
private void doThings() {
// the things your thread should do
}
}
private MyThread mBackgroundOperation = null;
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mBackgroundOperation = new MyThread();
mBackgroundOperation.start();
}
protected void onDetachedFromWindow() {
mBackgroundOperation.terminate();
mBackgroundOperation = null;
super.onDetachedFromWindow();
}
}
If this is not the case (if the Thread's lifecycle is not directly dependant of the use of the View's Surface for drawing) I would think twice about handling it inside the View, and would maybe consider moving it to the Activity that uses this View instead. "Maybe" because this is a difficult thing to determine without knowing what this Thread actually does.
stop() method is deprecated. Check this:
http://developer.android.com/reference/java/lang/Thread.html#stop%28java.lang.Throwable%29
you should get your work done and leave it, Android is smart enough to take care of it..
The best time to stop a view updating is probably when it's no longer visible. Your activity's onStop() method will be called when this happens, and from there you could call a method you write in your custom view to shutdown its update thread.
// Activity.java
public void onStop() {
myThreadedView.shutdown();
... // rest of onStop() here
}
// ThreadedView.java
public void shutdown() {
myViewThread.shutdown();
}
// ViewThread.java
bool stop = false;
public void shutdown() {
stop = true;
}
public void run() {
while (!stop) {
updateView();
}
}