Streamlining Android Apps: Eliminating Code Overhead by Jake Wharton

This talk will focus on techniques that both libraries and applications can implement to ensure their effect is in general without overhead.
It’s a really good stuff to Android developers and those Java developers whether they are doing android or not.

CPU

  • Do not nest multi-pass layouts
  • Lazily compute complex data when needed
  • Cache heavy computational results for re-use
  • Consider RenderScript for performance
  • Keep work off the main thread

Memory

  • Use object pools and caches to reduce churn
  • Be mindful of the overhead of enums
  • Do not allocate inside the draw path
  • Use specialized collections instead of JDK collection when appropriate (SparseArray)

I/O

  • Batch Operation with reasonable back-off policies
  • Use gzip or binary serialization format
  • Cache data offline with TTLs for reloading
  • Use JobScheduler API to batch across OS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MyDatabaseHelper extends SQLiteOpenHelper {
//version 1
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text, "
// version 3
+ "category_id integer)";

//version 2, added a table
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
public MyDatabaseHelper(Context context, String name,CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
case 2:
db.execSQL("alter table Book add column category_id integer");
default:
}
}
}

notice there is no key word ‘break’ inside the switch block, therefore, no matter which version that user installed, the db update will be ran properly, and keep the database up to date.

Sometimes we need our services to do the work but they often were killed by something like a app management tool, here’s some tricks might help.

Service Flag

Set to START_STICKY, after around 5 sec of service being killed, it will restart, and pass Intent again.

1
2
3
4
5
@Override  
public int onStartCommand(Intent intent, int flags, int startId) {
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}

Use startForeground

Use the startForeground(int, Notification) API to put the service in a foreground state, where the system considers it to be something the user is actively aware of and thus not a candidate for killing when low on memory. (It is still theoretically possible for the service to be killed under extreme memory pressure from the current foreground application, but in practice this should not be a concern.) [From official doc]

Twin Service

Create 2 services to protect each other. they will restart the other service when it was killed.

Evil Trick [Have not tried it yet]

Remain 1px size page in the foreground to stay in a foreground state when application goes to background.

Disguise App as system app [Android 4.0]

Install your apk into /system/app.
Make your app as a system app by changing the proper permissions in a rooted device.

White List

Contact manufacturer.

Listen To The Broadcast

1
2
3
4
5
6
7
8
<receiver android:name="<Name of your package>.<your class>" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
<action android:name="<Name of your package>.<your class>" />
</intent-filter>
</receiver>

BroadcastReceiver,

1
2
3
4
5
6
7
8
9
10
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
//start your sercice
startUploadService(context);
}
if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
//...
}
}

You could use Intent.ACTION_TIME_TICK as better result, this one broadcasts in every minute, however it’s a system broadcast, you can’t config it in manifest.xml, register it in your service or App extends application,

1
2
3
IntentFilter filter = newIntentFilter(Intent.ACTION_TIME_TICK);
MyBroadcastReceiver receiver = new MyBroadcastReceiver();
registerReceiver(receiver, filter);

In your receiver, let’s say it would look like MyBroadcastReceiver extends BroadcastReceiver, override onReceive() method,

1
2
3
4
5
6
7
8
9
10
11
12
13
boolean isServiceRunning = false;
if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
ActivityManager manager = (ActivityManager)ThisApp.getContext().getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service :manager.getRunningServices(Integer.MAX_VALUE)) {
if("so.xxxx.xxxxService".equals(service.service.getClassName())){
isServiceRunning = true;
}
}
if (!isServiceRunning) {
Intent i = new Intent(context, xxxService.class);
context.startService(i);
}
}


When I start a virtual device, why does the window remain black?


The offical Q&A will give you this

You are probably in either of the following situations:

  • Your network adapter may be misconfigured. In this case:

    1. Run VirtualBox.
    2. Open File > Preferences > Network (or VirtualBox > Preferences for Mac OS X).
    3. Edit the Host-only Network by clicking .
    4. Check that the adapter IPv4 address is in the same network (192.168.56.0/24 by default) as the DHCP server address, lower address bound and upper address bound. If not, your virtual device cannot start.
      You can also remove the Host-only Network by clicking . Genymotion will automatically recreate it at the next virtual device start.

    5. Your firewall may block the application. The Genymotion application must connect to the virtual device via the local network. If you have a firewall, make sure that you allowed connections to the Genymotion network, set to 192.168.56.0/24 by default.
      You can also check the log files to see whether an error occurred. To do so, please refer to How do I generate an archive containing Genymotion logs of a virtual device?. If there is no error, restart your virtual device directly from the VirtualBox application.


Have you met this problem when you use genymotion? I met this problem months ago, when I tried to make it work by following above, I failed.
and then I forgot to post my solution online as reference to other who might have the same problem. Here it is.

OS: windows 8.1 x64

I ran the vm with the VirtualBox and got this error:

init : cannot find ‘/system/bin/install-recovery.sh’ , disabling ‘flash_recovery’
IP Management : 192.168.56.101
failed to execute /sbin/v86d make sure that the v86d helper is
installed and executable Getting VBE info block failed (eax=0x4f00,err=-2)
vbe_init() failed with -22

I have tried

  • reinstall both genymotion and VirtualBox
  • upgrade and downgrade VirtualBox

Did not work.

After submited a ticket to genymotion team, I got a feedback:

Hello,

Running the VM from Virtualbox is not supported. You should use the “launchpad” that can be started by clicking on the Genymotion icon.

Regards,

Genymotion Support Team

Which provides no help. After a lot of google searches,
I find a solution, it does not relate to Genymotion directly,

Uninstall one of the Windows 8 patches, KB3045999.
This will affect to every user who is running virtual box under Win8.

If you have the same problem under windows as I did, please give it a shot.

From now on, I will try my best to build a English blog, after all, I am working in the U.S.

You are welcome if this helped you, and if there’s any question or problem, you can find me easily in the github.

HTML

1
<img src="url" style="width:0px;height:0px;">

Call with your script

1
<script type="text/javascript" src="url"></script>

Page does not support script?

1
2
3
<noscript>
<iframe src="url" height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>

Improve stability : Using combo “script + noscript”

整理转载自ticktick

在对应的activity中,重载函数onConfigurationChanged

1
2
3
4
@Override
public voidonConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}

在该函数中可以通过两种方法检测当前的屏幕状态:

  • 判断newConfig是否等于

    1
    2
    Configuration.ORIENTATION_LANDSCAPE,
    Configuration.ORIENTATION_PORTRAIT

    当然,这种方法只能判断屏幕是否为横屏,或者竖屏,不能获取具体的旋转角度.

  • 调用

    1
    this.getWindowManager().getDefaultDisplay().getRotation();

    该函数的返回值,有如下四种:

    1
    2
    3
    4
    Surface.ROTATION_0,
    Surface.ROTATION_90,
    Surface.ROTATION_180,
    Surface.ROTATION_270

    其中,Surface.ROTATION_0 表示的是手机竖屏方向向上,后面几个以此为基准依次以顺时针90度递增.

    Bug: 它只能一次旋转90度, 如果你突然一下子旋转180度, onConfigurationChanged函数不会被调用.

实时判断屏幕旋转的每一个角度

上面说的各种屏幕旋转角度的判断至多只能判断 0, 90 , 180, 270 四个角度,如果希望实时获取每一个角度的变化,则可以通过OrientationEventListener 来实现.

  • 创建一个类继承OrientationEventListener

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MyOrientationDetector extends OrientationEventListener{
    public MyOrientationDetector( Context context ) {
    super(context );
    }
    @Override
    public void onOrientationChanged(int orientation) {
    Log.i("MyOrientationDetector ","onOrientationChanged:"+orientation);
    }
    }
  • 开启和关闭监听
    可以在 activity 中创建MyOrientationDetector 类的对象,注意,监听的开启的关闭,是由该类的父类的 enable() 和 disable() 方法实现的.
    因此,可以在activity的
    onResume() 中调用MyOrientationDetector 对象的 enable方法,
    onPause() 中调用MyOrientationDetector 对象的 disable方法来完成功能.

  • 监测指定的屏幕旋转角度

    MyOrientationDetector类的onOrientationChanged 参数orientation是一个从0~359的变量,如果只希望处理四个方向,加一个判断即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    if(orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
    return; //手机平放时,检测不到有效的角度
    }
    //只检测是否有四个角度的改变
    if( orientation > 350 || orientation< 10 ) { //0度
    orientation = 0;
    }
    else if( orientation > 80 &&orientation < 100 ) { //90度
    orientation= 90;
    }
    else if( orientation > 170 &&orientation < 190 ) { //180度
    orientation= 180;
    }
    else if( orientation > 260 &&orientation < 280 ) { //270度
    orientation= 270;
    }
    else {
    return;
    }
    Log.i("MyOrientationDetector ","onOrientationChanged:"+orientation);

1
2
3
4
5
6
7
8
9
10
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate() {
context = getApplicationContext();
}
public static Context getContext() {
return context;
}
}

Changes in AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.networktest"
android:versionCode="1"
android:versionName="1.0" >

……
<application
android:name="com.example.networktest.MyApplication"
…… >

……
</application>
</manifest>

Access context wherever you needed, for example:

1
2
3
4
5
6
7
8
9
10
11
public static void sendHttpRequest(final String address,
final HttpCallbackListener listener)
{

…… //do something
if (!isNetworkAvailable()) {
Toast.makeText(MyApplication.getContext()
, "network is unavailable"
,Toast.LENGTH_SHORT).show();
return;
}
…… //do something
}