Android——实用小技巧
一、获取全局Context——解决Toast找不到可用Contex的尴尬
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()+"\n");
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()+"\n");
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.设置断点——在需要调试的开始行双击——取消也是双击
2.将程序跑起来,到需要调试的位置为止
3.进入DDMS
4.选中测试机器的包名进程——最下面一行,点击上方绿色小蜘蛛,成功后包名前面会多出一个小蜘蛛
5.继续执行程序,就会出现Debug了
6.F6逐行执行
7.查看变量值
8.停止调试——点击红框按钮即可
这样做的好处在于,可以随时进入程序调试。所以并没用Debug as 来执行程序,而是直接run as
五、编写测试用例
必要性在于:当开发的项目规模大的时候,测试用例格外重要,每当修改增加了某功能后,都应该把测试用例跑一遍!!!
测试用例也不过是一段代码而已,一般一个用例测试一个小单元的功能。
1.创建测试工程
File->New->Other->Android Test Project
工程名直接为要测试的工程加个Test后缀即可,路径也是该工程目录下新建tests文件即可
选择已存在的该工程,finish即可
创建成功之后,就会多出一个工程,这就是测试工程了!!
在这个工程里面编写测试用例即可!
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 , 发现能够跑通,说明满足测试用例(绿色)
然后,对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一下,发现不能跑通,
提示期望是1,但是结果是2
发现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 }
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 }
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 }
七、知晓当前是在哪一个活动
思路是:写一个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用来管理活动——随时退出程序