总结Android App的一些技巧以及注意事项

我做Android App开发其实也就半年多时间,而且我是从Python转到Java,之前也没有系统学习过Java,下面内容是总结这半年多的经验,但难免理解不到位的地方,看到的各位请大胆直接在评论中指出。

1. App中可以直接写一个继承自Application的自定义类用作一个单例,可以存放一些全局的方法和变量。但请记住单例不能跨进程(我犯过这个错误)!在不同进程中Android会为每个进程创建一个Application对象,进程间通信还请使用AIDL或者Handler/Messenger模式。创建一个自定义的Application类很简单,只要继承自Application就可以了:

1
2
3
4
5
6
7
public class BaseApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // your code here
    }
}

然后在AndroidManifest.xml中将android:name指定为该自定义Application:

1
2
3
4
5
6
7
8
9
10
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.example" >
 
    <application android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" 
        android:name="BaseApplication">
 
     </application>
</manifest>

这样就大功告成了。在Activity中通过(BaseApplication) getApplication();的方法来访问。

2. 自定义一个Activity基类是一个很好的方法,通过把一些通用方法写入Activity可以减少那些真正展现在用户面前的Activity代码。通用方法往往是界面布局上的方法,比如设定ActionBar,设定FullScreen等。之前我写的几个App都没有使用这个方法,导致部分Activity类的代码特别长,看起来吃力。结合上面的Application自定义类,我们可以创建一个不错的BaseActivity:

1
2
3
4
5
6
7
8
9
public class BaseActivity extends ActionBarActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        application = (BaseApplication) getApplication();
    }
 
    protected BaseApplication application;
}

3. Activity与Service通信有好几种方法,不同的方法适合不同情况。如果Activity与Service在同进程内,那么直接使用Binder绑定的方法进行通信;如果Activity与Service在不同进程中,但Activity只需要向Service发送信息而不需要从Service中获取信息,那么使用Handler/Messenger的方法进行通信会比较方便;如果Activity需要从另一进程中的Service获取信息,那么必须使用AIDL的方法进行通信。

a. 同进程内使用Binder方法非常简单,只需要创建一个自定义的Binder类然后创建一个getService方法即可:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Binder example: MService.java
public class MService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
 
    private LocalBinder binder = new LocalBinder();
    public class LocalBinder extends Binder {
        public MService getService() {
            return MService.this;
        }
    }
}
 
 
// Binder example: MActivity.java
public class MActivity extends BaseActivity {
    @Override
    public void onStart() {
        super.onStart();
        bindService(new Intent(this, MService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
    }
 
    @Override
    public void onStop() {
        super.onStop();
        if (isMServiceBound) {
            unbindService(mServiceConnection);
            isMServiceBound = false;
        }
    }
 
    private boolean isMServiceBound = false;
    private MService mService;
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = ((MService.LocalBinder) service).getService();
            isMServiceBound = true;
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
            isMServiceBound = false;
        }
    }
}

b. 不同进程中使用Handler/Messenger模式。这种方法适用于由Activity向Service发送消息。当然也是可以实现反向发送消息,不过需要两边都实现一套Handler/Messenger,对于这种情况我更倾向使用AIDL的方法去实现。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// Handler/Messenger example: MService.java
public class MService extends Service {
    @Override
    public IBinder onBind() {
        return messenger.getBinder();
    }
 
    private void sayHello(int name) {
        Log.i("MService", "hello " + Integer.toString(name));
    }
 
    private final Messenger messenger = new Messenger(new IncomingHandler());
    public final static int MESSAGE_SAY_HELLO = 0;
    private class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            swtich (msg.what) {
                case MESSAGE_SAY_HELLO:
                    sayHello(msg.arg1);
                    break;
                defaule:
                    super.handleMessage(msg);
            }
        }
    }
}
 
 
// Handler/Messenger example: MActivity.java
public class MainActivity extends BaseActivity {
    @Override
    public void onStart() {
        super.onStart();
        bindService(new Intent(this, MService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
    }
 
    @Override
    publicvoid onStop() {
        if (isMServiceBound) {
            unbindService(mServiceConnection);
            isMServiceBound = false;
        }
    }
 
    private void sayHello() {
        try {
            // 这里偷懒了,关于使用Message传递其他类型数据的方法请参考官方文档
            mService.send(Message.obtain(null, MService.MESSAGE_SAY_HELLO, 123, 0));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
 
    private boolean isMServiceBound = false;
    private Messenger mService;
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            isMServiceBound = true;
            sayHello();
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
            isMServiceBound = false;
        }
    }
}

Handler/Messenger还可以在很多情形下使用,我想我应该避免使用Broadcast而更多的使用Handler/Messenger模式才对,因为这种模式相比广播更加安全而且效率更加高。

c. AIDL是Android中实现跨线程访问的推荐方法,AIDL的全程是Android Interface Definition Language,它的语法就是一些Java的声明语法,所以很容易理解。在Android Studio中AIDL文件都是放在app/src/main/aidl/中相对应于java代码目录结构的文件夹下。比如我有一个app/src/main/java/com/example/example/service/MService.java文件,那么这个文件对应的AIDL文件就在app/src/main/aidl/com/example/example/service/MService.aidl。需要注意的是在Android Studio中AIDL的文件在编译后如果再进行修改的话,修改部分不会再次被编译,所以只能删除原有文件后重新创建才可以,这点可能在后续的Studio版本中改进,至于Eclipse的情况因为我不用就不清楚了。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// AIDL example: MServiceAidl.aidl
interface MServiceAidl {
    // 定义相应的接口
    void sayHello(String name);
}
 
// AIDL example: MService.java
public class MService extends Service {
    @Override
    public IBinder onBind() {
        return binder;
    }
 
    private void doSayHello(String name) {
        Log.i("MService", "hello " + name);
    }
 
    private final MServiceAidl.Stub binder = new MServiceAidl.Stub() {
        public sayHello(String name) {
            doSayHello(name);
        }
    }
}
 
// AIDL example: MActivity.java
public class MActivity extends BaseActivity {
    @Override
    public void onStart() {
        super.onStart();
        bindService(new Intent(this, MService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
    }
 
    @Override
    public void onStop() {
        super.onStop();
        if (isMServiceBound) {
            unbindService(mServiceConnection);
            isMServiceBound = false;
        }
    }
 
    private boolean isMServiceBound = false;
    private MServiceAidl mService;
    privaet ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            isMServiceBound = true;
            mService.sayHello("world");
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
            isMServiceBound = false;
        }
    }
}

修复丢失的Linux EFI引导项

也不知道是什么原因,安装在电脑上的Fedora突然不见了。如果是以前的MBR引导方式,出现这种情况往往是因为引导器被覆盖为Windows,只需要重新安装GRUB引导器即可。但现在使用的是EFI方式引导,所以修复的方法也就不一样了。
我遇到的是EFI中Fedora引导项消失的问题,在修复的尝试中发现EFI分区中还是有Fedora的Grub,所以只需要创建Fedora的EFI引导项即可。我使用了一张LiveCD引导了一个Linux,然后加载了Linux分区:

mount /dev/sda4 /mnt    // 加载root根目录
mount /dev/sda3 /mnt/boot    // 加载boot目录

如果没有grub-efi需要安装grub-efi包:

yum install grub2-efi

创建Fedora EFI引导项,并且将loader指向正确的引导器位置:

efibootmgr -c --disk /dev/sda --part 2 -l \\EFI\\FEDORA\\GRUBX64.EFI -L "Fedora Linux"    // --part 2是指向EFI的分区区号,-l \\EFI\\FEDORA\\GRUBX64.EFI是指向这个盘中的GRUB EFI引导器。 这两个参数应该根据实际情况修正,否则虽然出现了EFI引导项,但会显示为Drive not presented错误.

然后重起就可以看到Fedora的引导项了。

Notification使用RemoteView后点击通知自动隐藏展开的通知栏

当Notification使用RemoteView之后,用户点击Notification后系统不会自动隐藏展开的通知栏.这个时候需要通过Java的反射特性使用StatusBarManager类中被隐藏的方法collapse来手动隐藏通知栏.
首先需要获取权限:

1
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />

然后通过反射的方法调用collapse收起展开的通知栏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void collapseStatusBar(Context context) {
    try {
        Object service = context.getSystemService("statusbar");
        Class<?> statusBarManager = Class.forName("android.app.StatusBarManager");
        Method collapse;
        if (service != null) {
            if (Build.VERSION.SDK_INT <= 16) {
                collapse = statusBarManager.getMethod("collapse");
            } else {
                // api17开始这个方法的名字变了
                collapse = statusBarManager.getMethod("collapsePanels");
            }
            collapse.setAccessible(true);
            collapse.invoke(service);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

点击Notice时调用这个方法就可以让展开的通知栏自动收起了.

Service中使用全局Dialog

Service中默认情况下是没法使用Dialog的. 但可以通过申请权限的方式让Service也可以使用Dialog.
首先在AndroidManifest.xml中声明权限:

1
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

然后在Service中创建Dialog并且将Dialog的Windows Type设定为WindowManager.LayoutParams.TYPE_SYSTEM_ALERT即可:

1
2
3
4
5
6
7
8
9
10
class TestService extends Service {
// ...省略无关代码
 
private void showDialog() {
    Dialog dialog = new Dialog(this);    // 其他Dialog也一样, 因为都是从Dialog类派生出来的
    dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    dialog.setMessage("在Service中使用全局Dialog");
    dialog.show();
}
}

在Service中使用Toast

Android中Service没法直接使用Toast显示一个提示, 通过以下的方法可以在Service中全局使用Toast显示提示.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class TestService extends Service {
//... 省略无关代码
 
private Handler toastHandler = new Handler();
private Runnable toastRunnable = new Runnable() {
    @Override
    public void run() {
        Toast.makeText(TestService.this, "在Service中使用Toast显示提示.", Toast.LENGTH_SHORT).show();
    }
}
 
@Override
public void onCreate() {
    super.onCreate();
 
    // 调用显示Toast
    toastHandler.post(toastRunnable);
}
}