Question

I have a Fragment that I use to display a lot of heterogeneous content that's fetched from my web server. I'm using an AsyncTaskLoader to fetch the data, and once I get a response, I perform some logic to determine what content to display. While the AsyncTaskLoader is running, I have an AnimationDrawable taking up the entire display. After I finish loading the content, I call stop() on the AnimationDrawable, set it to View.GONE, and set the main LinearLayout (which has all my content) to visible.

<?xml version="1.0" encoding="UTF-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:fillViewport="true">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/animation_layout"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_gravity="center_vertical"
        android:gravity="center"
        android:orientation="vertical" >

        <ImageView
            android:id="@+id/progressDialog"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:scaleType="center" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/main_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:orientation="vertical" />

</LinearLayout>
</ScrollView>

Here's how I initialize the loader and animation, and display my content.

@Override
public void onStart(){
  super.onStart();
  LoaderManager lm = getLoaderManager();
  lm.initLoader(LOADER_PROFILE, getArguments(), this);
  loadingAnimation.start();
  animationLayout.setVisibility(LinearLayout.VISIBLE);
  mainLayout.setVisibility(ViewGroup.GONE);
}
@Override
public void onLoadFinished(Loader<Response> responseLoader, Response response) {
  LayoutInflater inflater = LayoutInflater.from(getActivity());

  for (Module m : response.modules) {
    switch (m.Type) {
      case Type1:
        mainLayout.addView(Type1Module.getView(inflater, m));
        break;
      case Type2:
        mainLayout.addView(Type2Module.getView(inflater, m));
        break;
      case Type3:
        mainLayout.addView(Type3Module.getView(inflater, m));
        break;
      case Type4:
        mainLayout.addView(Type4Module.getView(inflater, m));
        break;
      case Type5:
        mainLayout.addView(Type5Module.getView(inflater, m));
        break;
  }
  loadingAnimation.stop();
  animationLayout.setVisibility(LinearLayout.GONE);
  mainLayout.setVisibility(ViewGroup.VISIBLE);
}

In each of the Module's static getView() methods, I either inflate a layout and assign different TextViews and ImageViews depending on the data, or create an instance of one of several custom Views I've created (pie graphs, charts, etc) which require a little bit of math. In my testing this takes anywhere from half a second to 1.5 seconds.

What I'm seeing happen is that when my onLoadFinished starts, once I start creating views and adding them to the mainLayout, the AnimationDrawable freezes on whatever frame it was on. It remains frozen until I reach the end of the method when I stop the animation and switch the layouts.

Now, I know this is happening because I'm doing all my logic on the UI thread, and therefor blocking the AnimationDrawable. The problem is, most of the individual logic I'm doing is deciding view parameters (text, color, typefaces, whether to display things at all), which must be done on the UI thread. My question is has anyone experienced the same problem of having too much work to do on the UI thread blocking animation, and have you come up with a solution?

NOTE: I initially implemented this as a ListView with a custom BaseAdapter. I created an array of the TypeXModule objects (which all implemented getView()), and just called them in the adapter as needed. The content would load nearly instantaneously (being that only 3 or 4 modules fit on the screen at a time, only 3 or 4 had their getView() called). However, using a ListView had the side-effect of slightly stuttering when scrolling, to the point where you can't fling the entire list to the top or bottom. I decided to try a ScrollView because 1) all views would be instantiated at once, hence no stuttering, and 2) the modules' Views are all dissimilar enough that I couldn't really take advantage of the View recycling ListViews perform.

Was it helpful?

Solution 2

Since I wasn't able to eliminate the animation freeze entirely, I ended up going a slightly different route. Instead of stopping the animation and toggling the layouts after I finish created all the modules' Views, I stop the animation and toggle the layouts after the first module is loaded, then load each subsequent module incrementally. I used a separate HandlerThread so I wasn't triggered the modules all on the main UI thread.

private ArrayList<Modules> modules;
private HandlerThread mThread;
private ModuleThreadHandler mThreadHandler;

@Override
public void onStart(){
  super.onStart();
  LoaderManager lm = getLoaderManager();
  lm.initLoader(LOADER_PROFILE, getArguments(), this);
  loadingAnimation.start();
  animationLayout.setVisibility(LinearLayout.VISIBLE);
  mainLayout.setVisibility(ViewGroup.GONE);
}

@Override
public void onLoadFinished(Loader<Response> responseLoader, Response response) {
  modules = response.modules;
  mThread = new HandlerThread("mThread");
  mThread.start();
  Looper mThreadLooper = mThread.getLooper();
  mThreadHandler = new ModuleThreadHandler(mThreadLooper);
  mThreadHandler.obtainMessage(0).sendToTarget();
}  

private class ModuleThreadHandler extends Handler {
  public ModuleThreadHandler(Looper looper) {
    super(looper);
  }

  @Override
  public void handleMessage(Message msg) {
    fragmentHandler.obtainMessage(msg.what + 1).sendToTarget();
  }
}

private Handler fragmentHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    if (msg.what < modules.size()) {
      Module m = modules.get(msg.what);
      switch (m.Type) {
        case Type1:
          mainLayout.addView(Type1Module.getView(inflater, m));
          break;
        case Type2:
          mainLayout.addView(Type2Module.getView(inflater, m));
          break;
        case Type3:
          mainLayout.addView(Type3Module.getView(inflater, m));
          break;
        case Type4:
          mainLayout.addView(Type4Module.getView(inflater, m));
          break;
        case Type5:
          mainLayout.addView(Type5Module.getView(inflater, m));
          break;
      }
    }
    if (msg.what == 0) {
      loadingAnimation.stop();
      animationLayout.setVisibility(LinearLayout.GONE);
      mainLayout.setVisibility(ViewGroup.VISIBLE);
    }
    if ((msg.what + 1) < modules.size()) {
      pThreadHandler.obtainMessage(msg.what).sendToTarget();
    } else {
      pThread.quit();
    }
  }
};

This has the nice (IMO) side-effect of a "loading" effect as the modules load rapidly in succession. I found this to load just as fast as using a ListView. The only downside of this approach is that, while the modules are loading, scrolling is choppy (as there is work being done on the UI thread). However, once all modules are loaded, scrolling is very smooth (which was the main goal in the first place).

OTHER TIPS

try this, the swutch has 4 cases: the first two dont work, the case 2 with separate Thread seems to be the fastest

final AnimationDrawable dr = new AnimationDrawable();
dr.setBounds(0, 0, 100 ,100);
Drawable frame;
Resources res = getResources();
frame = res.getDrawable(R.drawable.t6);
dr.addFrame(frame, 200);
frame = res.getDrawable(R.drawable.t7);
dr.addFrame(frame, 200);
frame = res.getDrawable(R.drawable.t8);
dr.addFrame(frame, 200);
frame = res.getDrawable(R.drawable.t9);
dr.addFrame(frame, 200);
frame = res.getDrawable(R.drawable.t10);
dr.addFrame(frame, 200);

final TextView tv = new TextView(this);
tv.setTextSize(72);
tv.setTextColor(0xffeeeeee);
tv.setGravity(Gravity.CENTER);
tv.setBackgroundDrawable(dr);

FrameLayout fl = new FrameLayout(this);
fl.addView(tv);
fl.setBackgroundColor(0xffaaaaaa);
setContentView(fl);

final int STEPS = 200;
final Handler h = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        if (msg.what % 50 == 0) {
            Log.d(TAG, "handleMessage msg: " + msg);
        }
        tv.setText(Integer.toString(msg.what));
        if (msg.arg1 == 0) {
            take20msofCPU();
        }
        if (msg.what < STEPS) {
            obtainMessage(msg.what + 1).sendToTarget();
        }
    }
};
OnClickListener l = new OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.d(TAG, "onClick ");
        h.removeCallbacksAndMessages(null);
        dr.stop();
        dr.start();
        int caseNumber = 2;
        switch (caseNumber) {
            case 0:
                Log.d(TAG, "onClick simple loop: no no");
                for (int i = 0; i < STEPS; i++) {
                    take20msofCPU();
                }
                break;

            case 1:
                Log.d(TAG, "onClick send all Messages at once: no no");
                for (int i = 0; i < STEPS; i++) {
                    h.obtainMessage(STEPS+i).sendToTarget();
                }
                break;

            case 2:
                Log.d(TAG, "onClick simple loop in a separate Thread + Handler notification");
                new Thread() {
                    public void run() {
                        for (int i = 0; i < STEPS; i++) {
                            take20msofCPU();
                            // we take 20 ms here so dont do that in the Handler
                            // alse msg.what is >= STEPS so it doesnt kick itself
                            h.obtainMessage(STEPS+i, 1, 0, null).sendToTarget();
                        }
                    };
                }.start();
                break;

            default:
                Log.d(TAG, "onClick send a Message one by one");
                h.obtainMessage(0).sendToTarget();
                break;
        }
    }
};
tv.setOnClickListener(l);

void take20msofCPU() {
    // simulate hard task: you can do some looping that do something or just sleep
    try {
        Thread.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top