Friday, September 12, 2014

Foreground notification launches "App Info" screen

Recently I found one issue with Notifications that can be simply formulated as "Don't forget to use setSmallIcon while building a notification".

Otherwise, you won't get any notification if you launch it from NotificationManager. That's bad and not obvious at all, but still easily noticeable.

But if you show your notification (without called setSmallIcon) via Service.startForeground() , you'll get notification with not working PendingIntent - on click you'll see your "App Info" settings screen. setSmallIcon fixed the problem immediately. This behavior is completely not understandable for me, but I happy I fixed it.

I spent several hours to isolate the issue (just by many attempts to show notification different ways), so I hope it helps someone.

Wednesday, July 2, 2014

A little tip about equals() and hashCode()


Don't forget to check if equals() and hashCode() are overriden in any class (unless you use String or class that you're very familiar with) you use in collection!

And a little story about that...

Several days ago I tried to fix a memory leak somewhere in LruCache. LruCache provides a way to cache some objects while they're not exceeding a particular amount of memory. After that the oldest object in the cache is not being hold any more and available for garbage collector.

LruCache isn't a collection itself, but it should contain a map inside (and it does, have a look at the source code here) for key to value mapping (where the value is a cached object you need), so any class you use as key for LruCache should contain overridden equals() and hashCode().

But, unlucky for me, I used ResolveInfo as the key (and application icon as a value). Although it looks like a pretty simple data structure, it doesn't contain equals() and hashCode() overridden, I don't see the reason why - you can look here if you don't believe me.

So instead of caching it simply stored my icons and sometimes garbage collector couldn't do anything before OutOfMemoryError was throwed.

In my case, I replaced ResolveInfo to my own class that contains two fields: resolveInfo.activityInfo.packageName and resolveInfo.activityInfo.name and ultimately override equals() and hashCode(). Pair class is useful for such purposes too (and I actually copied equals() and hashCode() implementation from there) but it's always better to use more meaningful names.

Have a nice day!

P.S. Be careful to update to ADT 23.0.0 - it ruined my Eclipse and I needed to download the whole ADT Bundle for clean install. See here for details.

Monday, February 10, 2014

Joda Time Android issues

Everybody who was working with standard Java date, calendar and time api knows, how painful it could be with all these "umbrella" getters and setters like Calendar.set(int field, int amount), where you can set almost everything using appropriate params. Official documentation that recommends things like Calendar.get(Calendar.YEAR) - 1900 instead of Date.getYear() looks terrible as well.

After all of that, the idea to use Joda Time for date and time computing looks great - you got well designed and intuitive api with immutable classes (mutable versions are available too). The design of library makes you think in right terms like timezones, intervals, periods, durations and so on.

But this article isn't about advantages of Joda time. I would like to describe some issues that I faced when decided to use it in one of my android projects.

1. Slow loading (appropriate stackoverflow question). If you care about your app responsiveness (and I hope you do), you can notice that the very first Joda time class invocation can cause some visible lag of your app. In my case the lag was about 800 ms on old 2.3.6 devices like Nexus One. So I decided to call Joda initialization manually in background thread right after my app launch:

 // first joda time call can take time (100-900 ms on Nexus One), lets do  
 // it here in background thread for better performance  
 LocalTime.now().getMillisOfDay();  

It looks not very obvious, so don't forget to write some comments there.

2. Timezone changing. You might get surprised when you change your device timezone. Joda caches java's timezone and doesn't update it on system timezone change automatically. So you'll probably get logs with incorrect GMT offsets (as minimum) or incorrect result of LocalTime to millis conversion. So what you'll want to do in this case is to implement BroadcastReceiver that receives ACTION_TIMEZONE_CHANGED and manually sets default timezone to joda. Something like this (don't forget to register it in AndroidManifest.xml):

 public class TimezoneChangedReceiver extends BroadcastReceiver {  
     private static final String TAG = TimezoneChangedReceiver.class.getSimpleName();  
      
     @Override  
     public void onReceive(final Context context, final Intent i) {  
          String action = i.getAction();  
          Logger.d(TAG, "Going to handle action " + action);  
          if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {  
               Logger.d(TAG, "Going to update joda time zone!");  
               DateTimeZone.setDefault(DateTimeZone.forTimeZone(TimeZone.getDefault()));  
          }  
     }  
}  

3. Outdated timezones on some old devices. This is the most annoying issue I faced. Legacy devices like Nexus One with Android 2.3.6 have outdated timezones. Here in Minsk we have Further-eastern european time since September 2011 (see wiki) that is always UTC+03:00, so called "year-round daylight saving time". But it was Eastern european time (UTC+2 and UTC+3 with DST) until 2011. The same thing happened with Moscow standard time.

Nexus One doesn't know anything about further-eastern european time, but Joda Time library does, cause it uses its internal tz database with updated info about timezones. So the problem is that some functions will work unexpectedly for user. For example, if you call "LocalTime.now()", you might get time that differs from time that is displayed by device, cause the same unix-time means different local time in different timezones. Although Joda Time does all computations correctly, you might want to get offsets from the device, not from the updated tz-database. To do this you need to implement your own zone info provider (org.joda.time.tz.Provider inheritor). My implementation looks like this:

 /**  
  * This {@link Provider} is used for legacy devices (like Nexus One with 2.3.6)  
  * that have outdated timezones. {@link AndroidZoneInfoProvider} retrieves  
  * timezone offsets from device, not from updated tz-data. Although these  
  * timezones are incorrect, they are expectable for user and don't confuse him.  
  *   
  * For newer devices you probably don't want to use this class.  
  *   
  * @author Andrei Buneyeu  
  *   
  */  
 public class AndroidZoneInfoProvider implements Provider {  
      @SuppressWarnings("unused")  
      private static final String TAG = AndroidZoneInfoProvider.class.getSimpleName();  
      @Override  
      public DateTimeZone getZone(String id) {  
           final TimeZone timezone = TimeZone.getTimeZone(id);  
           if ("UTC".equalsIgnoreCase(id)) {  
                return DateTimeZone.UTC;  
           }  
           return new DateTimeZone(id) {  
                @Override  
                public long previousTransition(long instant) {  
                     return instant;  
                }  
                @Override  
                public long nextTransition(long instant) {  
                     return instant;  
                }  
                @Override  
                public boolean isFixed() {  
                     return !timezone.useDaylightTime();  
                }  
                @Override  
                public int getStandardOffset(long instant) {  
                     return timezone.getOffset(instant);  
                }  
                @Override  
                public int getOffset(long instant) {  
                     return timezone.getOffset(instant);  
                }  
                @Override  
                public String getNameKey(long instant) {  
                     return timezone.getDisplayName(Locale.US);  
                }  
                @Override  
                public boolean equals(Object object) {  
                     if (!(object instanceof DateTimeZone))  
                          return false;  
                     DateTimeZone dtz = (DateTimeZone) object;  
                     return dtz.getID().equals(getID());  
                }  
           };  
      }  
      @Override  
      public Set<String> getAvailableIDs() {  
           return new HashSet<String>(Arrays.asList(TimeZone.getAvailableIDs()));  
      }  
 }  

Of course there is no any guarantee that this implementation is absolutely bug-free (I'm not sure about correct implementation of nextTransition and prevTransition methods), but it seems to work properly. If you have any clarifications about that, feel free to post it in comments.




Thursday, May 16, 2013

Android Studio v0.1

Great news! Android Studio - a new Android IDE based on IntelliJ IDEA - was presented by Google about 14 hours ago.

NetBeans in variety of versions was my first IDE  probably (do you remember Java ME Platform SDK 3.0 that was actually NetBeans with deeply built-in plugins for J2ME?), but when I saw Eclipse I was impressed its power and expandability.

Eclipse is a great universal IDE that can do almost anything I want and I'll use it right now in projects after posting this text.

However, it's probably too universal to provide convenient ways for specific android developer actions. Layout editor built-in Eclipse was always slow and not very representative. It was always simpler and faster for me to edit xml-layouts manually. And slowness is not related only to Eclipse's layout editor, yeah. Eclipse is slow everywhere.

Hope we'll get something new and amazing with new Android Studio! It's on "early access preview" state now, so perhaps it's better to finish your current projects (and next one too) in Eclipse, but don't forget to check Studio's updates and look what's going on with it!

P.S. ADT was updated to 22.0 version too!

Thursday, June 28, 2012

ffmpeg causes app killing by Signal 7 (SIGBUS)

A Hacker's Craic: Slow queues and big maths
http://stackoverflow.com/questions/10739368/sigbus-crash-only-on-samsung-galaxy-s2/11244741#11244741


I had very similar issue - ffmpeg caused crash of my app by signal 7 (SIGBUS) after decoding several hundreds of frames. It was not very device specific - on some devices app crashed more often, on other less often.

It was very incomprehensible issue for me cause it could happen at any time of video encoding/decoding and I couldn't find any conditions that causes failures.
But I recompiled ffmpeg with `-malign-double` flag according to this useful article:
http://software.intel.com/en-us/blogs/2011/08/18/understanding-x86-vs-arm-memory-alignment-on-android/. And it helped! So my full "configure" line is (of course not all of these flags are useful for you):

> ./configure --target-os=linux --cross-prefix=arm-linux-androideabi-
> --disable-asm --arch=arm --cpu=cortex-a9 --sysroot=${SYSROOT} --enable-neon --disable-avdevice --enable-decoder=mjpeg --enable-demuxer=mjpeg --enable-parser=mjpeg --enable-demuxer=image2 --enable-muxer=mp4  --prefix=build/armeabi-v7a --extra-cflags='-DANDROID -I${NDK_ROOT}/sources/cxx-stl/system/include -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp' --extra-ldflags='-Wl,--fix-cortex-a8 -malign-double -L../android-libs -Wl,-rpath-link,../android-libs' --extra-cxxflags='-Wno-multichar -malign-double -fno-exceptions -fno-rtti'

Hope it'll help you too.

Wednesday, May 23, 2012

xhdpi

Не забывайте про xhdpi, как про ресурсы в папке drawable-xdpi (мне было бы неприятно видеть картинки в мыле на своем самом продвинутом телефоне), так и про внутренние вычисления в коде, если такие имеются. Все последние телефоны (такие как, например, HTC One X) имеют сверхвысокую плотность точек. Нам было досадно узнать, что на новых девайсах наше приложение работает не так как нужно, поскольку в нашей арифметике случая xhdpi просто не было предусмотрено.

Monday, April 2, 2012

synchronized(Boolean)


Читал чужой код, вижу блоки синхронизации по какой-то переменной, и вдруг понимаю, что тип этой переменной - Boolean. То есть смысл переменной в том, чтобы хранить какой-то флажок, а объектом переменная сделана для того, чтобы иметь возможность выполнить по ней синхронизацию. И тут мы проходим мимо здоровенных граблей, который могут нас больно ударить по самому больному месту. Дело в том, что существует всего два объекта Boolean - Boolean.FALSE и Boolean.TRUE. В двух абсолютно не связанных друг с другом кусках кода мы можем иметь по переменной типа Boolean. Эти переменные будут иметь абсолютно разный смысл, но в один момент они могут ссылаться на один объект. И когда мы войдем в блок синхронизации в одном месте программы, другой блок уже не сможет выполниться. И наоборот. Хотя логической связи между ними не будет вообще.

Формально, конечно, Boolean не синглетон. Конструктор у него public, и экземпляров может быть сколько угодно, то есть вместо Boolean.valueOf() мы можем использовать new Boolean(). Но это выглядит как еще больший хак - любой программист заменит конструктор на статический метод Boolean.valueOf(), как рекомендует официальная документация, и незаслуженно получит в лоб. Я не могу придумать ситуацию, где мог бы понадобиться конструктор Boolean и синхронизация по нему.

Как вывод - НИКОГДА не используйте синхронизацию для объектов типа Boolean и, обобщая, для оберток всех примитивных типов (поскольку некоторые объекты могут кэшироваться внутри класса).