2

I have dynamic and quite large contents which needs to be populated on user interaction (click next / prev page). The dynamic contents displayed programmatically on myTableLayout (see xml below). Each time user clicked next/prev page then I call myTableLayout.removeAllViews(), do some stuff and repopulate with myTableLayout.addView(). On small contents it draws quite fast, but I notice two to three seconds lag if populating large contents.

I already saw an application that do same functionality as mine (also with quite large contents) but it really paging so fast even on low-end device.

Need some advice on how to increase the layout performance.

Thanks.

Note: Upper TableLayout used as title (header), LinearLayout below it is where the contents placed.

<TableLayout
    android:layout_width="fill_parent"
    android:layout_height="45dip"
    android:stretchColumns="0,2"
    android:shrinkColumns="0,2"
    >
        <TableRow>
            <LinearLayout
                android:id="@+id/llLeft"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
            >
                <TextView
                    android:id="@+id/tvLeft1"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                />
            </LinearLayout>

            <LinearLayout
                android:id="@+id/llCenter"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
            >
                <TextView
                    android:id="@+id/tvCenter1"
                    android:layout_width="wrap_content"
                    android:layout_height="fill_parent"
                />

                <TextView
                    android:id="@+id/tvCenter2"
                    android:layout_width="wrap_content"
                    android:layout_height="fill_parent"
                />
            </LinearLayout>

            <LinearLayout
                android:id="@+id/llRight"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
            >
                <TextView
                    android:id="@+id/tvRight1"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                />
            </LinearLayout>
    </TableRow>
</TableLayout>

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
>
    <com.MyScrollView
        android:id="@+id/myScrollView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        >

        <TableLayout
            android:id="@+id/**myTableLayout**"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:shrinkColumns="1"
            android:stretchColumns="1"
            />

    </com.MyScrollView>
</LinearLayout>

Here is the code that create the dynamic content. The content fetched from memory (ArrayList) so it should not be the problem.

private void gotoPage() {
    myTableLayout.removeAllViews();
    GlobalVar g = GlobalVar.getInstance();
    ArrayList<String> arrScripts = g.getDatasMap().get(SCRIPT_INDEX);
    ArrayList<String> arrTrans = g.getTranslationMap().get(SCRIPT_INDEX);
    Log.i(APP_NAME, "finished preparing data");

    try {

        // Data Title
        tvCenter1.setText(arrScripts.get(0));
        tvCenter2.setText(" (" + SCRIPT_INDEX + ")");

        int countScripts = arrScripts.size();
        for(int i=1; i<countScripts; i++) {

            // Row Script
            TableRow trScript = new TableRow(this);
            trScript.setPadding(0, 5, 0, 0);
            TextView tvIndex = new TextView(this);
            tvIndex.setGravity(Gravity.CENTER);
            tvIndex.setWidth(40);
            tvIndex.setText("" + i);

            TextView tvScript = new TextView(this);
            tvScript.setGravity(Gravity.RIGHT);
            tvScript.setTypeface(g.getFontTypeFace());
            tvScript.setTextSize(FONT_SIZE);
            tvScript.setPadding(0, 0, 5, 0);
            tvScript.setText(arrScripts.get(i));

            trScript.addView(tvIndex);
            trScript.addView(tvScript);

            TableRow trTrans = null;

            if(IS_USE_TRANSLATION) {
                // Row Translation
                trTrans = new TableRow(this);

                TextView tvBlank = new TextView(this);
                tvBlank.setPadding(0, 0, 0, 5);
                TextView tvTrans = new TextView(this);
                tvTrans.setPadding(0, 0, 0, 5);
                tvTrans.setText(arrTrans.get(i));

                trTrans.addView(tvBlank);
                trTrans.addView(tvTrans);
            }

            // Place for line separator
            View vSep = new View(this);
            ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
            p.height = 3;
            vSep.setLayoutParams(p);

            // Combine rows to table
            if(IS_USE_SCRIPT) myTableLayout.addView(trScript);
            if(IS_USE_TRANSLATION) myTableLayout.addView(trTrans);
            myTableLayout.addView(vSep);
        }
    } catch (Exception e) {
        Log.e(APP_NAME, e.getMessage());
    }
}

Here is the traceview result.

Yury
  • 20,618
  • 7
  • 58
  • 86
yuwono95
  • 237
  • 3
  • 11

5 Answers5

4

I agree with @amplify91 that it might be from generating the content, but one of the other things you might want to look at is the number of layouts you are using. The basic thought is "the fewer the better". You can check out some of googles layout tricks

Example: I see your header is a table with linearlayouts with textviews. Couldn't that be fixed by a Relative layout with the textviews as children?

You'll save some Views, and also some 'depth' in the layout tree.

Nanne
  • 64,065
  • 16
  • 119
  • 163
  • as the traceview result, header didn't contribute significant performance factor. Its not inside the loop (created once only). – yuwono95 Feb 21 '11 at 07:31
  • True, but the basics do still apply to other views. You could use the hierarchy-viewer to check out how the final views are generated, and then check if you can do some "budget cuts" on the final result. You have a linearlayout with a scrollview with a table with rows with textviews if I see it correctly? Maybe you can cut back on some of them, make one relativelayout with some textviews? – Nanne Feb 21 '11 at 10:19
  • seems I can't set the layout params programmatically : [RelativeLayout doc](http://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html) . There are no "related method" for given xml attributes. Or I might be wrong? – yuwono95 Feb 21 '11 at 22:38
  • Apart from the fact that I tought you'd put only one `RelativeLayout` in the xml, and then add the `TextView`s in code, you could just Use the constructor of the layoutParams, or call "addrule": they are both in that manual, but [here](http://stackoverflow.com/questions/2383847/android-layout-with-listview-and-buttons) is a random example (the question has nothing to do with it, but there is a relative layout and some addrules in that code ;) – Nanne Feb 22 '11 at 06:59
  • I've get rid TableLayout and TableRow totally and implement my case using RelativeLayout. Noted significant performance increase. Thanks. – yuwono95 Mar 03 '11 at 08:54
2

To put it simply, get rid of TableLayout and TableRow and TextView altogether!

If you collect profiling data from Eclipse, you'll see that most of the CPU/real time is spent in the addView() method from the TabletLayout and also the TextView. calls!

Each time I've replaced a TableLayout and TextView with custom implementations it resulted in a HUGE performance gain!

TextView should be replaced by custom View, TableLayout by custom ListView.

EDIT: I have created a few custom views depending on my needs. To put it simply I've overwritten the View class with onDraw() and onMeasure() methods:

In below code, the painter is created once in the static constructor, so are textBaseline and textHeight as I'm using a single font size for all those custom "text" views.

@Override
protected void onDraw(Canvas canvas) 
{
    final int width = getWidth();
    final float size = painter.measureText(innerText);

    canvas.drawText(innerText, width - size - 5, textBaseline, painter);

    if (underline)
    {
        canvas.drawLine(0, canvas.getHeight() - 1, canvas.getWidth(), canvas.getHeight() - 1, painter);
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
{
    int w = MeasureSpec.getSize(widthMeasureSpec);
    int h = MeasureSpec.getSize(heightMeasureSpec);

    if (innerText != null && painter != null)
    {
        switch (MeasureSpec.getMode(widthMeasureSpec))
        {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                w = (int)( (painter.measureText(innerText) + 5) );
                break;
        }
    }
    switch (MeasureSpec.getMode(heightMeasureSpec))
    {
        case MeasureSpec.EXACTLY:
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.UNSPECIFIED:
            h = (int)( textHeight );
            break;
    }

    setMeasuredDimension(w, h);
}
3c71
  • 4,313
  • 31
  • 43
1

I think you should surround your code by Debug.startMethodTracing("xx") and Debug.stopMethodTracing() calls and then use traceview to find out what is going on. Everything else is just guesswork.

Heiko Rupp
  • 30,426
  • 13
  • 82
  • 119
  • Here is the trace result: [link](http://i.imgur.com/LkB6t.gif) . I saw consistent pattern when 660 views added iteratively by the method. The big green spot there is from Dalvik GC. So the 2.76 seconds is really come from the iteratively added views. Suggestion? – yuwono95 Feb 21 '11 at 07:27
0

If all you want to do is add text in a nicely formatted table format (all columns auto-sized etc), don't use a TableLayout at all and just use something like java.util.Formatter to format text into a table shape. This requires some pre-work like computing the max string length of each column, but in the end resulted in large performance gains as you basically reduce your view usage to a single TextView (~2 seconds to load activity down to almost instant).

For example:

// Compute max string length of each column
for (Entry entry : entries) {
    if (entry.column1.length() > column1MaxLength) {
        column1MaxLength = entry.column1.length();
    }
    ...
    if (entry.columnN.length() > columnNMaxLength) {
        columnNMaxLength = entry.columnN.length();
    }
}

// Formatter internally stores output string in StringBuilder
Formatter formatter = new Formatter();

// Construct table header
formatter.format("%1$" + column1MaxLength + "s", "Column 1 Title");
...
formatter.format("%1$" + columnNMaxLength + "s", "Column N Title");

// Add each entry into table
for (Entry entry : entries) {
    formatter.format("%1$" + column1MaxLength + "s", entry.column1.value);
    ...
    formatter.format("%1$" + columnNMaxLength + "s", entry.columnN.value);
}

// Get string representing formatted table from Formatter and put into TextView
textView.setText(formatter.toString());
Johnson Wong
  • 4,233
  • 1
  • 15
  • 6
0

I would say that the delay isn't in building the layout from XML, but from generating your dynamic content. What kind of content is it? Could you post some of your code? If you're accessing it from a website you may want to pre-fetch it. If you're creating when you change pages, it could be in how you're creating it.

Amplify91
  • 2,690
  • 5
  • 24
  • 32