Android——实用小技巧 一、获取全局Context——解决Toast找不到可用Contex的尴尬 二、Inten传递对象  三、定制自己的Log工具 四、eclipse调试Android程序  五、编写测试用例  六、在服务中Toast

Application类,当应用程序启动的时候,系统将会对这个类初始化,可以定制一个Application类,管理程序全局状态信息,如Context

定制Application

 1 package com.example.contexttest;
 2 
 3 import android.app.Application;
 4 import android.content.Context;
 5 public class MyApplication extends Application {
 6     private static Context context;
 7 
 8     @Override
 9     public void onCreate() {
10         context = getApplicationContext();
11     }
12     
13     public static Context getContext(){
14         return context;
15     }
16     
17 }

使用全局Context

 1 package com.example.contexttest;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.InputStream;
 5 import java.io.InputStreamReader;
 6 import java.net.HttpURLConnection;
 7 import java.net.URL;
 8 
 9 import android.widget.Toast;
10 
11 public class HttpUtil {
12     public static void sendHttp(final String address,
13             final HttpListener listener) {
14         // 在这里我们判断一下网络是否可用,如果不可用,则弹出提示
15         // 问题来了,Toast需要传递Context,在哪里去找一个Context?——通过自定义application
16         if(!isNetWorkAvailable()){
17             Toast.makeText(MyApplication.getContext(), "network is unavailable", Toast.LENGTH_SHORT).show();
18         }
19         
20         new Thread(new Runnable() {
21             HttpURLConnection connection = null;
22 
23             @Override
24             public void run() {
25                 try {
26                     // 获取HttpURLConnection对象
27                     URL url = new URL(address);
28                     connection = (HttpURLConnection) url.openConnection();
29                     
30                     // 设置请求方式和延迟
31                     connection.setRequestMethod("GET");
32                     connection.setConnectTimeout(8000);
33                     connection.setReadTimeout(8000);
34                     connection.setDoInput(true);
35                     connection.setDoOutput(true);
36                     
37                     // 请求数据,疯狂的读
38                     StringBuilder response = new StringBuilder();
39                     InputStream in = connection.getInputStream();
40                     BufferedReader bufr = new BufferedReader(new InputStreamReader(in));
41                     String line = null;
42                     while((line=bufr.readLine())!=null){
43                         response.append(line);
44                     }
45                     
46                     // 读完之后通过监听器操作数据
47                     if(listener!=null){
48                         listener.onFinish(response.toString());
49                     }
50                     
51                     
52                 } catch (Exception e) {
53                     listener.onError(e);
54                 } finally {
55                     if (connection != null) {
56                         connection.disconnect();
57                     }
58                 }
59             }
60         }).start();
61     }
62 }

有时候会java.lang.ClassCastException: android.app.Application cannot be cast to  XXXX.xxApplication的错误

需要在AndroidManifest中注册 一下application,在原有的application中添加一项

<application
        android:name="XXXX.xxApplication"

二、Inten传递对象

intent除了可以传递常见的数据类型如int,boolean等等,但是不能直接传递对象,要传递对象,通常有两种做法

1.序列化——Serializable

序列化很简单,只需要让类实现Serializable接口就可以了,并且这个接口一个方法都没有!!!仅仅相当于是添加一个标记一样!

 1 package com.example.intenttest;
 2 
 3 import java.io.Serializable;
 4 
 5 public class People implements Serializable {
 6     private String Name;
 7 
 8     public People() {
 9     }
10 
11     public People(String name) {
12         super();
13         Name = name;
14     }
15 
16     public String getName() {
17         return Name;
18     }
19 
20 }

传入:

1                 Intent intent = new Intent(MainActivity.this,SecondActivity.class);
2                 People people = new People("秋香");
3                 intent.putExtra("people", people);
4                 startActivity(intent);

取出:

People people = (People) getIntent().getSerializableExtra("people");
textView.setText("Serializable:"+people.getName()+" ");

Serializable方式会将整个对象序列化,效率上会比parcelable稍低

2.Parcelable方式

 1 package com.example.intenttest;
 2 
 3 import android.os.Parcel;
 4 import android.os.Parcelable;
 5 
 6 public class Dog implements Parcelable {
 7     private String name;
 8     private int age;
 9 
10     public Dog() {}
11     public Dog(String name, int age) {
12         super();
13         this.name = name;
14         this.age = age;
15     }
16 
17     public String getName() {
18         return name;
19     }
20 
21     public int getAge() {
22         return age;
23     }
24 
25     /**
26      * 返回0即可
27      */
28     @Override
29     public int describeContents() {
30         // TODO Auto-generated method stub
31         return 0;
32     }
33 
34     /**
35      * 将字段一一写出
36      */
37     @Override
38     public void writeToParcel(Parcel dest, int flags) {
39         dest.writeString(name);
40         dest.writeInt(age);
41 
42     }
43 
44     /**
45      * Parcelable方式必须提供一个CREATOR常量,传入泛型类名
46      */
47     public static final Parcelable.Creator<Dog> CREATOR = new Creator<Dog>() {
48 
49         /**
50          * 指定数组大小
51          */
52         @Override
53         public Dog[] newArray(int size) {
54             // TODO Auto-generated method stub
55             return new Dog[size];
56         }
57 
58         /**
59          * 按照写入的顺序一一读取
60          */
61         @Override
62         public Dog createFromParcel(Parcel source) {
63             // TODO Auto-generated method stub
64             Dog dog = new Dog();
65             dog.name = source.readString();
66             dog.age = source.readInt();
67             return dog;
68         }
69     };
70 }

存入:

1         Intent intent = new Intent(MainActivity.this,SecondActivity.class);
2         Dog dog = new Dog("旺财", 8);
3         intent.putExtra("dog", dog);
4         startActivity(intent);

取出:

1 Dog dog = getIntent().getParcelableExtra("dog");
2 textView.setText("Parcelable:"+dog.getName()+"::"+dog.getAge()+"
");

Parcelable稍微复杂一点,不过效率稍微高一点,更推荐使用

 三、定制自己的Log工具

为了是正式发布的软件不输出Log,开发测试时才输出Log,可以定义一个LogUtil工具类来控制!!

很简单,很强大!!

 1 package com.example.utils;
 2 
 3 import android.util.Log;
 4 
 5 /**
 6  * 
 7  * 在开发阶段,可以将Level设置成1,这样就和Log功能一样.<br/>
 8  * 在发布时,Level设置成NOTHING,这样就不会有任何Log了.<br/>
 9  */
10 public class LogUtil {
11     public static final int VERBOSE = 1;
12     public static final int DEBUG = 2;
13     public static final int INFO = 3;
14     public static final int WARN = 4;
15     public static final int ERROR = 5;
16     public static final int NOTHING = 6;
17     public static final int LEVEL = VERBOSE;
18 
19     /**
20      * 打印全部
21      */
22     public static void v(String tag, String msg) {
23         if (LEVEL <= VERBOSE) {
24             Log.v(tag, msg);
25         }
26     }
27 
28     public static void d(String tag, String msg) {
29         if (LEVEL <= DEBUG) {
30             Log.d(tag, msg);
31         }
32     }
33 
34     public static void i(String tag, String msg) {
35         if (LEVEL <= INFO) {
36             Log.i(tag, msg);
37         }
38     }
39 
40     public static void w(String tag, String msg) {
41         if (LEVEL <= WARN) {
42             Log.w(tag, msg);
43         }
44     }
45 
46     public static void e(String tag, String msg) {
47         if (LEVEL <= ERROR) {
48             Log.e(tag, msg);
49         }
50     }
51 
52 }

四、eclipse调试Android程序

1.设置断点——在需要调试的开始行双击——取消也是双击

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

2.将程序跑起来,到需要调试的位置为止

3.进入DDMS

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

4.选中测试机器的包名进程——最下面一行,点击上方绿色小蜘蛛,成功后包名前面会多出一个小蜘蛛

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

5.继续执行程序,就会出现Debug了

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

  

6.F6逐行执行

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

7.查看变量值

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

8.停止调试——点击红框按钮即可

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

这样做的好处在于,可以随时进入程序调试。所以并没用Debug as 来执行程序,而是直接run as

 五、编写测试用例

必要性在于:当开发的项目规模大的时候,测试用例格外重要,每当修改增加了某功能后,都应该把测试用例跑一遍!!!

测试用例也不过是一段代码而已,一般一个用例测试一个小单元的功能。

1.创建测试工程

File->New->Other->Android Test Project

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

工程名直接为要测试的工程加个Test后缀即可,路径也是该工程目录下新建tests文件即可

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

选择已存在的该工程,finish即可  

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

创建成功之后,就会多出一个工程,这就是测试工程了!!

在这个工程里面编写测试用例即可!

  Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

2.编写测试用例进行单元测试

下面定义一个AndroidControllerTest 类,用于对BroadcastBestPractice项目中AndroidController类测试

  需要继承AndroidTestCast方法

  需要重写setUp和tearDown方法,可对测试用例执行初始化和资源回收操作

  要对AndroidController的addActivity方法测试,只需要创建一个方法,testaddActivity(对!就是在这个方法前面加一个test即可!)

  在测试方法中,通过断言assert,来判断期望的值和结果是否一致,一致则跑通,不一致,则说明程序有需要修改的地方

AndroidController.java

 1 /**
 2  * 活动控制器 1.添加活动 2.删除活动 3.销毁所有活动
 3  */
 4 public class ActivityController {
 5     public static List<Activity> activities = new ArrayList<Activity>();
 6 
 7     public static void addActivity(Activity activity) {
 8             activities.add(activity);
 9     }
10 
11     public static void removeActivity(Activity activity) {
12 
13         activities.remove(activity);
14     }
15 
16     public static void finishAll() {
17         for (Activity activity : activities) {
18             activity.finish();
19         }
20     }
21 }

AndroidControllerTest.java

 1 package com.example.broadcastbestpractic.test;
 2 
 3 import com.example.broadcastbestpractic.ActivityController;
 4 import com.example.broadcastbestpractic.LoginActivity;
 5 
 6 import android.test.AndroidTestCase;
 7 /**
 8  * 
 9  * BroadcastBestPractice中的AndroidController类测试用例.<br/>
10  * 需要重写setUp和tearDown方法
11  */
12 public class AndroidControllerTest extends AndroidTestCase {
13 
14     /**
15      * 测试用例调用前执行,可以在这里进行初始化操作
16      */
17     @Override
18     protected void setUp() throws Exception {
19         // TODO Auto-generated method stub
20         super.setUp();
21     }
22     
23     /**
24      * 在需要测试的方法前面加上test,系统就会自动测试该方法.</br>
25      * 通过assert进行断言.<br/>
26      * Run as Android JUit Test运行测试用例.<br/>
27      */
28     public void testaddActivity(){
29         // 断言activity结合的数量初始为0
30         assertEquals(0, ActivityController.activities.size());
31         LoginActivity loginActivity = new LoginActivity();
32         ActivityController.addActivity(loginActivity);
33         // 断言经过addActivity方法操作之后,活动个数增加成为1,被认为是正确的
34         assertEquals(1, ActivityController.activities.size());
35         
36     }
37     
38 
39     /**
40      * 测试用例执行完成时执行,可以在这里进行资源的释放
41      */
42     @Override
43     protected void tearDown() throws Exception {
44         // TODO Auto-generated method stub
45         super.tearDown();
46     }
47     
48 }

Run as Android JUit Test , 发现能够跑通,说明满足测试用例(绿色

Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

然后,对AndroidControllerTest.java这个类进行一下修改

 1     public void testaddActivity(){
 2         // 断言activity结合的数量初始为0
 3         assertEquals(0, ActivityController.activities.size());
 4         LoginActivity loginActivity = new LoginActivity();
 5         ActivityController.addActivity(loginActivity);
 6         // 断言经过addActivity方法操作之后,活动个数增加成为1,被认为是正确的
 7         assertEquals(1, ActivityController.activities.size());
 8         
 9         ActivityController.addActivity(loginActivity);
10         // 断言再次执行addActivity时,活动个数仍然是1,即不重复加载
11         assertEquals(1, ActivityController.activities.size());
12         
13     }

在此Run一下,发现不能跑通,

Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

提示期望是1,但是结果是2

Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
二、Inten传递对象
 三、定制自己的Log工具
四、eclipse调试Android程序
 五、编写测试用例
 六、在服务中Toast

发现AndroidController不符合测试用例

修改AndroidController的代码

1         if (!activities.contains(activity)) {
2             activities.add(activity);
3         }

然后在Run一下,发现能够跑通了!!

 六、在服务中Toast

 直接写则:RuntimeException:Can't creat handler inside thread that has not called Looper.prepare()

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 输出当前时间
        new Thread(new Runnable() {

            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(LongRunningService.this, "executed at:"
                                + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), Toast.LENGTH_SHORT).show();
                Looper.loop();  
            }
        }).start();

完整:

 1 public class MainActivity extends Activity {
 2 
 3     @Override
 4     protected void onCreate(Bundle savedInstanceState) {
 5         super.onCreate(savedInstanceState);
 6         setContentView(R.layout.activity_main);
 7         Intent service = new Intent(this, LongRunningService.class);
 8         startService(service);
 9     }
10 
11 }
MainActivity
 1 public class LongRunningService extends Service {
 2 
 3     @Override
 4     public IBinder onBind(Intent intent) {
 5         return null;
 6     }
 7 
 8     @Override
 9     public int onStartCommand(Intent intent, int flags, int startId) {
10         // 输出当前时间
11         new Thread(new Runnable() {
12 
13             @Override
14             public void run() {
15                 Looper.prepare();
16                 Toast.makeText(LongRunningService.this, "executed at:"
17                                 + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), Toast.LENGTH_SHORT).show();
18                 Looper.loop();  
19             }
20         }).start();
21         // 定时打开广播接收器——10s一次
22         AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
23         /*
24          * type还可以用RTC,RTC_WAKEUP,对应的触发时间应该使用System.currenTimetMillis()
25          */
26         // (int type, long triggerAtMillis, PendingIntent operation)
27         int type = AlarmManager.ELAPSED_REALTIME_WAKEUP; // 从系统开机时累积的总时间
28         long triggerAtMillis = SystemClock.elapsedRealtime() + 10 * 1000;
29         intent = new Intent(this, ServiceReceiver.class);
30         PendingIntent operation = PendingIntent.getBroadcast(this, 0, intent, 0);
31 
32         // 通过set定时执行可能会延迟,4.4之后,因为手机会有省电设计,如果要准确无误,用setExact()
33         alarmManager.set(type, triggerAtMillis, operation);
34 
35         return super.onStartCommand(intent, flags, startId);
36     }
37 
38     @Override
39     public void onDestroy() {
40         super.onDestroy();
41     }
42 
43 }
LongRunningService
 1 public class ServiceReceiver extends BroadcastReceiver {
 2 
 3     @Override
 4     public void onReceive(Context context, Intent intent) {
 5         // 被激活则直接开启服务
 6         Intent service = new Intent(context, LongRunningService.class);
 7         context.startService(service);
 8     }
 9 
10 }
ServiceReceiver

七、知晓当前是在哪一个活动

思路是:写一个BaseActivity类继承Activity,新增活动创建时打印日志功能,让所有需要关注的活动继承BaseActivity即可,当不在需要知晓当前活动时,去掉log即可获继承回Activity

 1 public class BaseActivity extends Activity {
 2 
 3     @Override
 4     protected void onCreate(Bundle savedInstanceState) {
 5         super.onCreate(savedInstanceState);
 6         LogUtil.d("BaseActivity", getClass().getSimpleName());
 7         ActivityCollector.addActivity(this);
 8     }
 9     
10     @Override
11     protected void onDestroy() {
12         super.onDestroy();
13         ActivityCollector.removeActivity(this);
14     }
15     
16 }
 1 public class ActivityCollector {
 2 
 3     public static List<Activity> activities = new ArrayList<Activity>();
 4 
 5     public static void addActivity(Activity activity) {
 6         activities.add(activity);
 7     }
 8 
 9     public static void removeActivity(Activity activity) {
10         activities.remove(activity);
11     }
12 
13     public static void finishAll() {
14         for (Activity activity : activities) {
15             if (!activity.isFinishing()) {
16                 activity.finish();
17             }
18         }
19     }
20 
21 }
ActivityCollector.java

ActivityCollector用来管理活动——随时退出程序