Sometimes developer may be faced with below log message with extremely-slow-scrolling of List.


Log includes "Window is full: requested allocation 2195889 bytes, free space 2096720 bytes, window size 2097152 bytes"


Most case of this kind of issue can be seen when SQLite DB has blob-type field whose data is quite big, and Cursor that includes this field is used at Aadapter.

In this case, Cursor easily exceeds it's maximum Window size (at Android 4.0.3, this value is about 2 MB).

After reaching to maximum Window size, Cursor need to handle Window, and at worse, field size is quite big.
So, whenever move to another row of Cursor, Cursor should handle quite complex disk operation.

So, at this moment, scrolling list becomes very very slow.


To avoid this, main Cursor of Adapter would better not to have big-size field of DB Table.

Instead of it, try to read from DB whenever the field value is demanded.


Then, average time for scrolling list may be increased. But, even at worst cases, list can show reasonable scrolling performance.


When using Adapter (especially list), sometimes(actually very frequently) data of only one item(row) is changed.

In this case, usually, changing one-under-lying data is easy.

But, updating only one list item is different story.

To update that item to the screen, - changing look-and-feel regarding changed item -  easiest way is calling notifyDataSetChanged().

But, this re-bind views of all list items. This is wasteful.

To avoid this, we should get View of item associated, and then modify it directly.

At first glance, it is easy and "Item position to View" map seems to be useful. But, it's NOT.

Why?

AdapterView usually reuse Views. So, several positions become to share same view at "position to View" map.
That is, whenever try to update item at specific item, we need to check that that item has valid visible View or not.

But, this is not-easy at current(API 15) Android API.


My suggestion is using "View to position" map(henceforth VPMap).

At AdapterView, View is reused. So, Adapter doesn't have lot's of different Views.

So, key size of the VPMap is not large (usually at most 20~30).

And we can easily update recent valid visible position of View easily at getView() of Adapter.

See below sample code.

private final HashMap<View, Integer> mView2PosMap = new HashMap<View, Integer>();
...
public void
setItemActive(int pos) {
    if (pos == mActivePos)
        return;
    View v = Utils.findKey(mView2PosMap, mActivePos);
    if (null != v)
        setToInactive(v);
    v = Utils.findKey(mView2PosMap, pos);
    if (null != v)
        setToActive(v);

    mActivePos = pos;
}
...
@Override
public View
getView(int position, View convertView, ViewGroup parent) {
    View v;
    if (null != convertView)
        v = convertView;
    else
        v = UiUtils.inflateLayout(mContext, R.layout.row);

    mView2PosMap.put(v, position);
    ...
}
...
public static <k,v> K
findKey(HashMap<View, Integer> map, V value) {
    Iterator<k> iter = map.keySet().iterator();
    while(iter.hasNext()) {
        K key = iter.next();
        if (map.get(key).equals(value))
            return key;
    }
    return null;
}

Like above, with this VPMap we can easily find associated visible View and can update only one-updated-item-View without calling 'notifyDataSetChanged()'.


Sometimes developer may need to use MediaPlayer directly instead of using VideoView.

In this case, developer should keep belows in his/her mind.

<NOTE : Gingerbread and ICS are a little different (ICS is more flexible. But, the more strict is the better.>

 - setDisplay Should be called after surface of SurfaceHolder is created.

 - video should be started after it's video size is known (after onVideoSizeChanged is called.)

 - setDisplay() should be called before prepare()


Let's summarize it.


onSurfaceCreate -> setDisplay() -> prepare()/prepareAsync()

: onVideoSizechanged

: onPrepared


=> Now, MediaPlayer is fully prepared to be started.


Tested and verified at Android 4.0.3


When using SlidingDrawer over SurfaceView, SlidingDrawer is cropped by SurfaceView while opening and closing even if SlidingDrawer is over SurfaceView.


Try with below layout.

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <SurfaceView android:id="@+id/surface" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> <SlidingDrawer ... />

</RelativeLayout>

In my opinion, that's because SufaceView holding the visible region and display on it.

And, in case of SlidingDrawer, opening and closing area is not known by WindowManager.
So, it may not be considered when holding region by SurfaceView.


Based on above hypothesis, I tried with below layout and confirmed that it works as expected.

The difference is just put a dummy View that covers all SurfaceView to let WindowManager know there is window over SurfaceView.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <SurfaceView
        android:id="@+id/surface"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
    <View
        android:id="@+id/touch_ground"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
    <SlidingDrawer
        ...
        />
</RelativeLayout>

I don't want to tell that this is a kind of bug of Android framework. It's just common-type of issue when things are in together.

I hope that this post is helpful to others.

In case that sqlite database is quite big, preparation for using cursor requires lots of time-consuming-operation.

So, developer may considering about asynchronous way - showing progress while preparing cursor in background.

Then, what is 'Really' time-consuming-operation in preparing cursor?

As my understanding, in Android sqlite cursor, the answer is first 'fillWindow' operation of SQLiteCursor.


Notable thing is, at sqlite cursor, creating cursor doesn't do any real (or practical) thing.

So, creating cursor is done very quickly.

By the way, Sqlite cursor do 'fillWindow' operation at the moment of first 'getCount()' call - See SQLiteCursor.java.

Therefore, to prepare cursor in background, not only creating cursor but also calling first 'getCount()' function are required.


Here is sample function for reference.

    public void
    reloadCursorAsync() {
        DiagAsyncTask.Worker worker = new DiagAsyncTask.Worker() {
            private Cursor newCursor;
            @Override
            public void onPostExecute(DiagAsyncTask task, Err result) {
                changeCursor(newCursor);
            }
            @Override
            public Err doBackgroundWork(DiagAsyncTask task, Object... objs) {
                newCursor = createCursor();
                newCursor.getCount();
                return Err.NO_ERR;
            }
        };
        new DiagAsyncTask(mContext, worker,
                          DiagAsyncTask.Style.SPIN,
                          R.string.loading, false)
        .execute();
    }



Android document says several advantages of android:targetSdkVersion. But, I faced following issue.


Here is my case.


android:minSdkVersion="10"

android:targetSdkVersion="15"

...

<activity ...

android:configChanges="orientation|keyboardHidden"

...


Since v3.2 ScreenSize is additionally required at configChanges to handle orientation change.

But, in this case, minSdkVersion is "10" so, ScreenSize cannot be used.

The problem is that targetSdkVersion is "15".

So, framework doesn't call onConfigurationChanged() callback when orientation is changed.

This is unavoidable and unexpected.


Eventually, I gave up using 'targetSdkVersion'.


I'm not sure there are any other workarounds for these cases, but after experiencing above case, I am trying to Not-To-Use targetSdkVersion option...

This post is history of registering Android App to Google Play.

I hope that this post is useful to developers who has a plan to register App to Google Play.



I registered one free App. to Google Play and after some time - about a-week-after? - I received below E-mail from Google.




This is a notification that your application, Youtube Music Player, with package ID free.yhc.youtube.musicplayer, has been removed from the Google Play Store.


REASON FOR REMOVAL: Violation of the intellectual property and impersonation or deceptive behavior provisions of the Content Policy.

All violations are tracked. Serious or repeated violations of any nature will result in the termination of your developer account, and investigation and possible termination of related Google accounts.

If your developer account is still in good standing, you may revise and upload a policy compliant version of your application as a new package name. Before uploading any new applications, please review the Developer Distribution Agreement and Content Policy.

If you feel we have made this determination in error, you can visit the Google Play Help Center article for additional information regarding this removal.

The Google Play Team




Issued application is the one below


YoutubeMusicPlayer-v1.3.0-2.apk


Main feature of this application is "making playlist of youtube videos and play those continuously without video (only audio) in background.


Actually at that moment, I didn't have any idea regarding what are problems.

So, some E-mails are additionally sent/received

Here is E-mail thread between Google and me.




Hi Google

I received below mail.

Could you describe details about the violations of application free.yhc.youtube.musicplayer.

Actually, I do not know what are issued points.

My developer account is yhcting77@gmail.com.

Thank you in advance and sorry for inconvenience.

YH Cho.




Hi Ting,

Thank you for your note.

We appreciate the opportunity to review your appeal regarding the removal
of your app from Google Play. After an investigation, we affirm our
initial decision and will not be reinstating your application at this
time.

If your developer account is still in good standing, and the nature of
your application allows for you to upload a new, compliant version of this
application to Google Play; please apply the following Content Policy
guidelines to future releases:

Google Play Android Developer Program Policy
(see "Impersonation or Deceptive Behavior"):
http://play.google.com/about/developer-content-policy.html

Regards,
The Google Play Team




Hi Google.


I'm sorry for continuously bothering you.
I reviewed "Google Play Android Developer Program Policy".
In my opinion at this moment, my violation is - concretely -  "Youtube-TM-like-application-icon and App name are used, and this may lead users to misunderstand this App. has some relation with Google.".
Am I right?
As my previous e-mail, please let me know What are issued points and problems concretely and that will be very helpful for me to update application to obey Google's policy.
I really would like to go with Google's Policy but to do it, at first I should know "What is real problem".

Thank you in advance and I'm sorry again for bothering you.
Regards.
YH Cho.




Hi Ting,

Thank you for your note. Your application's icon and title could be
misleading to some users and lead them to think that your app is
authorized by another organization. Please be aware of the "Impersonation
or Deceptive Behavior" section of the Content Policy as you publish your
applications:
http://www.android.com/us/developer-content-policy.html#impersonation



Regards,
The Google Play Team



As described as above E-mail thread, at this moment, I think app name and app icon is mainly issued points of policy violation.

So, I will register App with fix of known issued point - app icon and name.

But, I am not sure that icon and name are really all issued cases.

If there are any other issues I will update those here to leave history and data to help understanding regarding Google Play Policy.

Now, app is re-registered to Google Play. Here



Developer cannot guess when Java GC is run. So, in Android, Bitmap class has special interface 'recycle' to reuse memory as soon as possible when Bitmap is not used anymore.

Based on similar reason, we need to recycle ImageView.


But, here are some points to consider when recycling memory of ImageView.

* ImageView has drawable - most case it is instance of BitmapDrawable - regardless of it's input-resource-type - ex setImageResource(),  setImageURI() or setImageBitmap(). And, ImageView has BitmapDrawable not only the case of setImageBitmap but also some other cases - ex setImageResource.



* Sample code to recycle Bitmap of ImageView is something like below


        // true == (v instanceof ImageView)

        Drawable d = v.getDrawable();

        if (d instanceof BitmapDrawable) { // to make sure.

            BitmapDrawable bmd = (BitmapDrawable)drawable;

            Bitmap bitmap = bmd.getBitmap();

            bitmap.recycle();

        }


* setImageDrawable(), setImageResource() and setImageURI() compares new-input and current used one.

So, if new one is same with current one, ImageView doesn't do anything. (See ImageView.java for details.)


* ImageView doesn't provide any interface to compare that given one is same with current one or not.


Because of above 4 reasons, recycling memory of ImageView is not straight-forward.

Simplest way, in my opinion, is feed newly-created-Bitmap-instance to ImageView whenever changing drawable, and recycle old one.

Because drawable is newly-created-instance, we don't need to compare new one with old one.

So, blind recycling old one, is ok in this case.


Here is sample function.


    public static void

    setThumbnailImageView(ImageView v, byte[] imgdata) {

        Bitmap thumbnailBm;

        if (null != imgdata && imgdata.length > 0)

            thumbnailBm = BitmapFactory.decodeByteArray(imgdata, 0, imgdata.length);

        else

            thumbnailBm = BitmapFactory.decodeResource(Utils.getAppContext().getResources(),

                                                       R.drawable.ic_unknown_image);


        // Recycle old Bitmap

        Drawable drawable = v.getDrawable();

        if (drawable instanceof BitmapDrawable) { // to make sure.

            BitmapDrawable bmd = (BitmapDrawable)drawable;

            Bitmap bitmap = bmd.getBitmap();

            bitmap.recycle();

        }


        // change to new one.

        v.setImageBitmap(thumbnailBm);

    }

Most Android device is 32bit machine. So, many application assumes that host machine is 32bit system.
And in general, there is no difference developing Android-natvie-library between 64bit and 32bit build machine.

But, here is usual step for developing Android-native-library.
(1) Developing and verifying code at build machine.
(2) Porting to NDK build system.

Most developers turns on full-warning-option at compile to detect bugs at early stage.
But, building and verifying codes assuming 32bit host machine at 64bit machine always issues type casting warning due to different type size.
Especially, between pointer and integer.

For example, many Android JAVA application uses int - jint - as a type to contain native pointer with assumption of 32bit-host-system.
Building this code at 64bit build system to verify code issues type casting warning, even if code itself is built perfectly at NDK build system.
And it is worse that this code doesn't work at 64bit Android host machine, even though it is not popular.

To reduce this warnings (for easy-verifying of library code at build machine), in my opinion, using long - jlong - instead of jint as a type for containing native pointer is better unless memory space is extremely critical.
And to make compiler be happy, using macro can be a good choice.
Here is example of macro for type-casting between pointer and integer - jlong.
(This sample works well without warning at 32bit/64bit build/host system).

#define ptr2jlong(v) ((jlong)((intptr_t)(v)))
#define jlong2ptr(v) ((void*)((intptr_t)(v)))

This is just simple example for portability issues.
Making portable code is always very difficult...

To test Android kernel, keeping minimum number of user space process is very useful.
Actually, 'adbd' and 'ueventd' is enough on Android.
Here is the way how to make device have only minimum user space processes - adbd and ueventd.
Followings are file structure of ramdisk image.

[ Create ramdisk ]

let's make following directory structure in ramdisk.

/bin -> sbin
/sbin -+- busybox
       +- adbd
       +- ueventd -> ../init
       +- <...> -> busybox
/init
/init.rc
/default.prop

All are same with default android except that busybox is in /sbin and /bin is symbolic link to /sbin.
Let's look into one by one.

/init : same binary with default Android.
/bin : symbolic link to /sbin.
/default.prop : same with default Android. - adb and debugging is enabled.
/sbin/busybox : statically linked busybox.
/sbin/... : tools (symbolic link to busybox). Ex, sh -> busyboxls -> busybox.
/sbin/adbd :
Modified adbd. Original adbd uses /system/bin/sh as its terminal shell. But, this one uses /bin/sh.
To do this, value of SHELL_COMMAND at system/core/adb/service.c should be modified.
/init.rc :
Simplified one. Only adbd and ueventd is started.
One important note is, "DO NOT make empty section(ex. on fs)!". This will lead init process to error and system will restarted again and again.
Here is sample.

on early-init
    start ueventd

on init
    sysclktz 0
    export PATH /bin:/sbin:

#on fs

#on post-fs

#on post-fs-data

on boot
   start adbd

## Daemon processes to be run by init.
##
service ueventd /sbin/ueventd

service adbd /sbin/adbd

[ Make ramdisk image ]

Let's assume that current working directory is ramdisk directory.

find . | cpio -o -H newc | gzip > newramdisk.gz

This newly generated gzip file can be renamed directly to ramdisk.img.

[ Make boot image ]

mkbootimg tool is used. This can be easily found at out/host/<arch>/bin after building android from source.

mkbootimg --cmdline 'no_console_suspend=1 console=null' --kernel <zImage file> --ramdisk <ramdisk image> -o newboot

[ Verify ]

After flashing newly generated boot image, reboot device.
The only respond that device can do, is done at boot-loader stage. After that device doesn't anything.
After waiting some moments, try adb device. Then host PC can find the device and adb shell can be used.
Type adb shell ps. Then, you can check that only three user space process are running - initadbd and ueventd.

[ Debugging ]

Except for kernel, adbd and init may be required to be modified (As mentioned above, modified adbd is used.). Printing log is very helpful for debugging and using framebuffer console is simple way to do this.
Here is the step (ex. adbd).

* comment out xxxx in init.c
=> this removes /dev/console (framebuffer console)
* modify start_logging() and start_device_log() in adb.c.
=> use /dev/console as stdout and stderr file.

Now, log message of adbd will be shown on the framebuffer (that is, displayed at the panel.)

[ Something more ]

You may build your own Linux environment on the device by building file system and installing libraries etc.
In my case, I set up tools for development - ex. glibc, gcc, binutils etc, and compiled LTP(Linux Test Project) to test kernel.
Enjoy your minimum Android environment :-).

+ Recent posts