拍照类应用
拍照的重要性不亚于后期的编辑,完美的取景、曝光和对焦都能够有助于拍出一张效果上佳的原始照片,大大减轻后期的编辑美化工作,所以选择一款功能强大且符合自己实际需求的拍照应用是至关重要的。
(1 )ProCapture (售价3.99 美元)
ProCapture的最大特点就是使用方便,许多设置需要点击一次即可完成。例如在风景模式下,屏幕左侧会有6个小图标,用户只需用大拇指就能对其进行控制,点击并按住即可看到所有的可用选项,而手指抬起就完成了对应的选择。
ProCapture并没有因为拥有多项功能而显得混乱,相反这款应用使用起来反应非常灵敏,许多功能都被设计在相当合理的地方,定时拍照、连拍模式、白平衡调整等功能一个不落,用户甚至还能获得类似单反相机上的照片直方图,了解照片的详细信息。
( 2 ) Camera ZOOM FX (售价 2.99 美元)
Camera ZOOM FX的拍照功能也比较强大,只不过在设置上稍显麻烦,不如ProCapture直观,用户需要翻翻菜单才能找到白平衡调整和拍摄场景设置等选项,不过该应用却加入了一些比较适合初学者的拍摄模式,比如延时拍照和照片拼贴等。同时,Camera ZOOM FX拥有许多与单反相机类似的功能,用户可以在该应用上找到许多能够自定义设置的按钮和选项,这对于那些有经验的摄影师来说是非常实用的。
(3 )Pro HDR Camera (售价1.99 美元)
高动态范围图像(High-Dynamic Range,简称HDR)在摄影领域一直是一项存在争议的技术,虽然它能够提供更多的动态范围和图像细节,但有不少人摄影爱好者认为利用该技术拍摄出来的照片的色彩过于明亮,看起来有些失真。
不过,Pro HDR Camera对于Android用户来说还是比较实用的,毕竟智能手机的镜头都比较小,进光量不足是普遍的现象,所以Pro HDR Camera就有了用武之地,用该应用拍出来的照片在物体的阴影效果和亮度控制上都非常赞,它还能将一组照片合成为一张漂亮的照片。
Pro HDR Camera使用起来比较方便,用户在拍摄完成之后,应用就会自动出现设置亮度、对比度和饱和度等设置选项,即便是那些在夜间拍摄的照片,通过调整之后也能获得不错的效果。
编辑类应用
当摄影师们用相机拍完一张照片之后,为了得到更棒的效果,他们大多会在后期利用滤镜对照片进行风格处理,有的还会对照片的对比度、亮度和色温等进行调整tulaoshi,以前这些功能只有通过PS等桌面应用才能实现,如今只需一台智能手机就可以了。
在谷歌的Play Store应用商店中,充斥着大量的照片后期编辑应用,以下是功能全面且强大的三款。
(1 )Snapseed (免费)
如果你打算在自己的手机只安装一款照片编辑应用,我推荐Snapseed。这款谷歌旗下的免费照片编辑应用设计精美,同时提供了大量的滤镜效果和编辑照片的工具。
Snapseed融合了大量的手势操作,例如点击并垂直滑动手指可以切换不同的编辑工具,而左右滑动则能够调节各项参数的增减。
(2 )Adobe Photoshop Touch (售价9.99 美元)
许多专业摄影师都使用Adobe公司的Photoshop软件对照片进行后期处理,该软件支持原生的RAW文件,并且内置了大量功能强大的照片编辑工具。如今Adobe针对Android平台推出了Photoshop Touch应用,虽然只有一部分桌面版的功能,但是对于手机应用来已经足够强大了。
Photoshop Touch的菜单和文件系统看起来与桌面版的比较接近,但其中内置的一些功能几乎是市面上所有照片编辑应用所难以望其项背的,降噪、色彩平衡和照片的选择、剪切和粘贴等功能一应俱全。虽然Photoshop Touch使用起来有些繁琐,但绝对是功能最全面的照片编辑应用。
(3 )Photo Editor by Aviary (免费)
Aviary公司的Photo Editor内置了许多效果上佳的滤镜和漂亮的照片贴纸,能够满足用户的多种要求。虽然Photo Editor是一款免费照片编辑应用,但在功能上面丝毫不输那些付费应用,亮度、对比度、饱和度和清晰度调节样样不少,再加上比较专业的外观设计,Photo Editor完全值得摄影爱好者为之一试。
分享类应用
很显然,拥有拍照功能的智能手机如今已经改变了人们的生活,它们让照片的拍摄和分享变得简单而方便。所以如果你用手机拍了一张不错的照片,选择一个合适的分享平台也是非常重要的。除了Twitter和Facebook等社交网络与Snapchat、WhatsApp和谷歌Hangouts等移动消息应用能够分享照片之外,我们还选出了一下三款以照片为中心的分享应用。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/android/)( 1 )Google+ (免费)
谷歌推出Google+的主要目的是把旗下的众多产品联结在一起,但这个社交平台却有着相当出众的照片分享功能,Google+用户在经过简单设置之后,就能够实现照片的自动存储,同时其Web端和移动端应用都内置了一些照片编辑工具。
同时,用户能够将原始分辨率的照片上传到Google+上,该功能是许多照片分享平台所不具备的,这也使得Google+成为许多专业摄影师们分享照片的主要工具。
(2 )Instagram (免费)
坦言之,不管是在照片的拍摄、编辑还是分享,Instagram的功能都可谓非常强大,当然,其分享功能是最为优秀的。
Instagram除了界面简洁功能全面之外,最大的特点就是拥有巨大的用户群,同时这些用户的活跃度也非常高,每天都会数次登陆,这也就意味着当用户在分享自己的照片时,会得到更多其他用户的关注和评论,所以Instagram在照片分享方面拥有比较明显的先天优势。
(3 )Flickr (免费)
Flickr曾经是一个风光无限的照片分享平台,但被雅虎收购之后开始走下坡路。不过在梅丽莎梅耶尔(Marissa Mayer)出任雅虎CEO之后,通过一系列的措施来盘活Flickr。如今Android版本的Flickr应用也加入了定制化滤镜功能和一些比较专业的照片编辑工具。
当然,Flickr最强大的功能仍然是照片分享,在Android版Flickr中,用户能够通过侧边栏的快捷键访问个人材料、通知和联系人等信息,同时其搜索工具也能够按照人名、群组或上传组进行分类优化。在专业摄影师眼中,Flickr和Instagram一样拥有比较重要的地位,不过前者还需要在吸引新用户方面多下功夫。
除了上述9款应用之外,大家还可以考虑以下的这些图片类应用:
(1) Stock Android:一款功能稳定的拍照应用,为数不多的支持360度全景拍摄的应用;
(2) 500px:知名照片分享网站的官方移动应用;
(3) QuickPic:广受欢迎的第三方图库应用;
(4) AfterFocus Pro:一款能对照片背景进行虚化处理的强大工具;
(5) Focal:一款全功能拍照应用,内置在CyanogenMod出品的Android版本中;
(6) Camera360 旗舰版:集照片拍摄和处理于一身的全功能照片应用。(Henrish)
注 :更多精彩教程请关注图老师手机教程栏目,图老师手机数码群: 296605639欢迎你的加入
MainActivity如下:
package cc.testusespermission;
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.os.Bundle;
/**
* Demo描述:
* 查看Android应用所需权限(uses-permission)
*
* 参考资料:
* 1 http://blog.csdn.net/bage1988320/article/details/6740292
* 2 http://blog.csdn.net/hjd_love_zzt/article/details/12238301
* Thank you very much
*
* 备注说明:
* 在Demo中的代码注释是以android.permission.INTERNET权限
* 为例子对各属性进行了详细说明
*
*/
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
getUsesPermission("cc.testusespermission");
}
private void getUsesPermission(String packageName){
try {
PackageManager packageManager=this.getPackageManager();
PackageInfo packageInfo=packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
String [] usesPermissionsArray=packageInfo.requestedPermissions;
for (int i = 0; i usesPermissionsArray.length; i++) {
//得到每个权限的名字,如:android.permission.INTERNET
String usesPermissionName=usesPermissionsArray[i];
System.out.println("usesPermissionName="+usesPermissionName);
//通过usesPermissionName获取该权限的详细信息
PermissionInfo permissionInfo=packageManager.getPermissionInfo(usesPermissionName, 0);
//获得该权限属于哪个权限组,如:网络通信
PermissionGroupInfo permissionGroupInfo = packageManager.getPermissionGroupInfo(permissionInfo.group, 0);
System.out.println("permissionGroup=" + permissionGroupInfo.loadLabel(packageManager).toString());
//获取该权限的标签信息,比如:完全的网络访问权限
String permissionLabel=permissionInfo.loadLabel(packageManager).toString();
System.out.println("permissionLabel="+permissionLabel);
//获取该权限的详细描述信息,比如:允许该应用创建网络套接字和使用自定义网络协议
//浏览器和其他某些应用提供了向互联网发送数据的途径,因此应用无需该权限即可向互联网发送数据.
String permissionDescription=permissionInfo.loadDescription(packageManager).toString();
System.out.println("permissionDescription="+permissionDescription);
System.out.println("===========================================");
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
main.xml如下:
RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent" TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="查看Android应用所需权限(uses-permission)" android:layout_centerInParent="true"//RelativeLayout
有的时候需要改变分区的权限
如某些非ROOT用户需要读取分区的某部分内容
修改 systemcorerootdirueventd.rc 如下:
/dev/null 0666 root root
/dev/zero 0666 root root
/dev/full 0666 root root
/dev/ptmx 0666 root root
/dev/tty 0666 root root
/dev/random 0666 root root
/dev/urandom 0666 root root
/dev/ashmem 0666 root root
/dev/binder 0666www.Tulaoshi.com root root
# 在这里修改你的设置
#change partition permission
/dev/preloader 0640 root system
进程的优先级
12.1.1.概述
Android规定:进程的优先级分为以下五个级别,如图-1所示:
图-1
1、 前台进程 -Activte tulaoshi.comprocess
Active (前台) process是包含(与用户交互的)控件的那种应用程序。这些是Android通过回收资源来极力保护的进程。Active process包括:
(1)处于active状态的Activity,它们运行在前台来响应用户的事件。
(2)Activity Service或者正在执行onReceive事件处理函数的Broadcast Receiver。
(3)正在执行onStart,onCreate,OnDestroy事件处理函数的Service。
2、 可见进程-Visible Process
可见但不活动的进程是那些拥有可见Activity的进程。可见Activity是那些在屏幕上可见,但不是在前台或不响应用户事件的Activity。这种情况发生在当一个Activity被部分遮盖的时候(被一个非全屏或者透明的Activity)。可见进程只在极端的情况下,才会被杀死来保证Active Process的运行。包括以下情况:
(1)可见的Activity,但处于暂停(onPause()) 状态;
(2)被可见Activity绑定的Service
3、 服务进程 Service process
进程中包含已经启动的Service。Service以动态的方式持续运行但没有可见的界面。因为Service不直接和用户交互,它们拥有比visible Process较低的优先级。它们还是可以被认为是前台进程,不会被杀死,直到资源被active/visible Process需求。
4、 背景进程 Background process
进程中的Activity不可见和进程中没有任何启动的Service,这些进程都可以看作是后台进程。在系统中,拥有大量的后台进程,并且Android按照后看见先杀掉的原则来杀掉后台进程以获取资源给前台进程。
5、 空进程-Empty process
为了改善整个系统的性能,Android经常在内存中保留那些已经走完生命周期的应用程序。Android维护这些缓存来改善应用程序重新启动的时间。这些进程在资源需要的时候常常被杀掉。
当一个进程被杀掉,进程保留,变成空进程。
12.1.2.设置/取消Service为前台进程的方法
由上所述,Service排在进程的第三优先级,通常耗时的操作是放在线程中,那么将这样的线程放在Service中将会有较高的优先级,降低被Android系统杀掉的几率。
若是将线程放在Activity中,当Activity被完全遮盖,处于onStop状态时,其进程的优先级别降为第四级。明显不如放在处于第三级别的Service中更保险。
应用场景,如音乐播放器,通过在前台做其它操作时,音乐播放器在后台播放音乐,这种情况将播放音乐的线程放在Service中是适宜的。
Service类中有两个方法,分别用来设置Service为前台进程和取消前台进程。被设置为前台进程的Service拥有最高的优先级别,被Android系统杀掉的几率降至最低。
1、startForeground(int id,Notification noti);
作用:设置Service对象为前台进程。
说明:
第一个参数是通知的id值。
第二个参数是通知对象。
startForegroud方法的参数与通知管理器相同,使用上也类似,都是发送一个通知,并指定该通知对象的id值。
2、stopForeGround(int id);
作用:取消(指定id值所通知的Service对象)前台进程。
12.1.3.设置Service为前台进程的步骤
步骤1、在Service类的onStartCommand方法中(通常在该方法中)创建Intent对象,并指定与其绑定的Activity,示例代码如下:
Intent foreIntent=new Intent(this, MainActivity.class);
步骤2、创建PendingIntetn对象
PendingIntent pintent=PendingIntent.getActivity(
this, 0, foreIntent, PendingIntent.FLAG_UPDATE_CURRENT);
说明:第四个参数指明在通知栏随时刷新通知。
步骤3、创建通知对象,示例代码如下:
Notification noti=new Notification(
R.drawable.icon,"notification",System.currentTimeMillis());
说明:
第一个参数是通知栏中显示的本通知的图标。
第二个参数是通知栏中显示的本通知的标题。
第三个参数是本通知发出的时间。
步骤4、将此通知放到通知栏的(Ongoing)正在运行组中,示例代码如下:
noti.flags=Notification.FLAG_ONGOING_EVENT;
步骤5、设置通知的点击事件,示例代码如下:
noti.setLatestEventInfo(this, "title","content", pintent);
步骤6、向指定的Activity发送通知,并设置当前的Service对象为前台进程,示例代码如下:
startForeground(97789, noti);
12.1.4.示例
运行图-1所示的窗口:
图-2
1、单击图-1中的start foreground按钮,将启动一个Service对象,并设置改Service为前台进程,在该在日志窗口中出现图-2中红框内的第一行信息。
2、单击图-2中的stop foreground按钮,将取消Service的当前进程,并在日志窗口中显示图-2中红框内的第二行信息。
以下列出关键代码:
步骤1、创建项目exer12_01,包名为com.tarena.exer12_01,项目入口:MainActivity类,该中关键代码如下所示:
@Override
public void onClick(View v) {
//创建Intent对象,并设置目标组件为MyService
Intent intent=new Intent();
intent.setClass(this, MyService.class);
switch(v.getId()){
case R.id.btnStartFore:
//设置intent.action的值为Constant.ACTION_FORE
intent.setAction(Constant.ACTION_FORE);
startService(intent);//启动服务
break;
case R.id.btnStopFore:
//设置intent.action的值为Constant.ACTION_STOP_FORE
intent.setAction(Constant.ACTION_STOP_FORE);
startService(intent);
break;
case R.id.btnStopService:
stopService(intent);//停止服务
break;
}
}
步骤2、在src/com.tarena.exer12_01包下创建MyService.java该类继承自Service类。关键代码如下所示:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action=intent.getAction();
if(Constant.ACTION_FORE.equals(action)){
Log.i(tag,"startForeground");
Intent foreIntent=new Intent();
foreIntent.setClass(this, MainActivity.class);
PendingIntent pintent=PendingIntent.getActivity(
this, 0, foreIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification noti=new Notification(
R.drawable.icon,"notification",System.currentTimeMillis());
//将此通知放到通知栏的"Ongoing"即"正在运行"组中
noti.flags=Notification.FLAG_ONGOING_EVENT;
noti.setLatestEventInfo(
this, "改变Service优先级", "设置service为foreground级别", pintent);
startForeground(97789, noti);
}else if(Constant.ACTION_STOP_FORE.equals(action)){
Log.i(tag,"stopForeground");
stopForeground(true);//取消当前服务为前台服务
}
return super.onStartCommand(intent, flags, startId);
}
步骤3、打开项目清单文件,注册该服务,如下代码中红框中代码所示:
application android:icon="@drawable/icon"
android:label="@string/app_name"
service android:name="MyService"/service
/application
12.2.UI与线程
12.2.1.概述
UI是英文User Interface单词的简称。
当应用程序启动时,系统会为应用程序创建一个主线程(main)或者叫UI线程,它负责分发事件到不同的控件(例如绘画事件)以完成应用程序与Android UI孔庙件的交互。
例如,当触摸屏幕上的一个按钮时,UI线程会把触摸事件分发到控件上,更改状态并加入事件队列,UI线程会分发请求和通知到各个控件,完成相应的动作。
单线程模型的性能是非常差的,除非应用程序相当简单,特别是当所有的操作都在主线程中执行,比如访问网络或数据库之类的耗时操作将会导致用户界面锁定,所有的事件将不能分发,应用程序就像死了一样,更严重的是当超过5秒时,系统就会弹出应用程序无响应的对话框。
12.2.2.main线程
主线程也叫UI线程,主线程负责UI的创建,UI的刷新以及处理用户的输入事件。
提示:Android规定,Activity中的控件的刷新由主线程负责,其它线程不能直接刷新。
12.2.3.ANR术语
ANR的全称:Activity or Application is not responding,当用户操作超过系统规定的响应时间时,会弹出ANR对话框,如图-3所示:
图-3
若选择Force close按钮将强制关闭当前的Activity;
若选择Wait按钮将保留当前的Activity继续等待。
出现ANR的条件:
1. 在main线程(或称主线程)中有一个耗时操作正在执行,此时用户输入事件并且这个事件在5秒内没有得到响应,就会弹出ANR。
2. 广播接收者的onReceive()方法在10秒内没有执行完成,也会弹出ANR。
提示:在广播接收者的onReceive方法中要避免执行耗时的操作。
12.2.4.示例-测试ANR发生的两种情况
创建项目exer12_02,在该类中创建MyReceiver类,该类是BroadcastRecevier的子类。
图-4
1、单击download按钮,在主线程中执行一个循环模拟从网络下载数据的操作,该循环耗时10秒,在该循环执行过程中,在图-4的标注所指的编辑框中连续试图输入字符串,5秒后,将弹出图-3所示的ANR对话框。
2、单击send broadcast按钮,发送广播,然后在图-4的标注所指的编辑框内连续试图输入字符串,10秒后将弹出ANR对话框。
以下是关键代码:
步骤1、创建工具类-CommonUtils.java,该类中定义了一个模拟耗时操作的循环,代码如下所示:
public final class CommonUtils {
public static final String ACTION_RECEIVER="com.tarena.ACTION_RECEIVER";
public static void timeConsuming(int n){
for(int i=0;in;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
步骤2、创建MyReceiver.java类,该类继承自BroadcastReceiver类,代码如下所示:
public class MyReceiver extends BroadcastReceiver {
private static final String tag="MyReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.i(tag,"onReceiver");
CommonUtils.timeConsuming(15);
}
}
步骤3、以下是MainActivity.java类的代码,该代码负责发送广播,模拟下载的耗时操作,代码如下所示:
public class MainActivity extends Activity implements OnClickListener{
TextView mTextView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mTextView=(TextView)findViewById(R.id.tv);
//实例化按钮对象
Button btnDownload=(Button)findViewById(R.id.btnDownload);
Button btnSendBraodcast=(Button)findViewById(R.id.btnSendBroadcast);
//注册按钮对象的单击事件
btnDownload.setOnClickListener(this);
btnSendBraodcast.setOnClickListener(this);
}
//实现按钮的单击事件
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.btnDownload://下载按钮
CommonUtils.timeConsuming(10);//模拟(耗时10秒的)下载
mTextView.setText("finished");
break;
case R.id.btnSendBroadcast://发送广播按钮
//发送广播
Intent intent=new Intent(CommonUtils.ACTION_RECEIVER);
sendBroadcast(intent);
mTextView.setText("finished");
break;
}
}
}
12.3.Message对象
12.3.1.概述
Message类用于存放消息,该类通常与Handler类配合使用。
12.3.2.常用属性
1、int arg1:存放一个int类型的数据。
2、int arg2:存放一个int类型的数据。
3、int what:存放一个int类型的数据。
4、Object obj:存放任意类型的对象。
12.3.3.示例代码
Message msg=new Message();
msg.what=1;
msg.arg1=100;
msg.obj=hello;
msg.obj=new Runnable();
12.4.用Handler更新UI
12.4.1.概述
由以上所述,在主线程中不宜执行耗时操作,因此,通常的耗时操作都放在其它线程中,Androidghi称这样的线程为work thread(工作线程)。但Android还规定:只有主线程才能修改Activity中的控件,其它线程不能修改。
解决以上问题有多种方法,本节介绍如何通过Handler类提供的方法解决工作线程不能直接修改UI的问题。
Handler修改主线程UI的思路:Handler对象通过在工作线程中发送消息,该消息发送至消息对列中,等待处理。
在主线程中从消息对列中接收消息,根据消息中的信息决定如何更新主线程中UI.
12.4.2.常用方法
1、sendEmptyMessage(int what);
作用:从work thead(工作线程)向主线程发送一个空消息。
说明:若多个线程向主线程发送消息,则参数what用于区别不同的线程。
2、sendEmptyMessageAtTime(int what,long uptime);
作用:从work thead(工作线程)按指定时间发送空消息。
说明:第二个参数uptime:指定的时间。
3、sendEmptyMessageDelayed(int what,long delay);
作用:从work thead(工作线程)延迟发送空消息。
说明:第二个参数用于指定延迟的时间,单位:毫秒。
5、sendMessage(Message msg);
作用:从work thead(工作线程)向主线程发送消息;
说明:msg是存放消息数据的对象。
6、sendMessageAtTime(Message msg,long uptime);
作用:从work thead(工作线程)按指定时间向主线程发送消息
7、sendMessageDelayed(Message msg,long delay);
作用:从work thead(工作线程)延迟指定时间向主线程发送消息。
8、handleMessage(Message msg);
作用:接收并处理从work thread发送的消息。
说明:参数-msg:send***Message发送过来的消息对象。
图-5是Android处理消息机制的示意图:
图-5
图-5显示了Android系统提供了一个称为Looper(循环对列)用来管理消息对列,各线程通过Handler类的Send***Message命令将消息发送至消息对列,Looper再将消息对列中的消息依次交给主线程处理。
12.4.3.示例
从两个工作线程向主线程发送不同的消息,主线程接收消息并显示不同的处理信息。
步骤1、以下是Activity类中的onClick()方法中的代码,该方法用于处理按钮单击事件。
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.btnDownload://若单击了标题为下载的按钮
//创建一个work thread(工作线程)线程对象
new Thread(){
public void run() {
//以下一行代码模拟下载进度,执行时间约为10秒
CommonUtils.timeConsuming(10);
//创建消息对象
Message msg=new Message();
//CommonUtils.FLAG_DOWNLOAD值代表下载操作
msg.what=CommonUtils.FLAG_DOWNLOAD;
msg.obj="download finished";
mHandler.sendMessage(msg);//发送消息
};
}.start();//启动线程
break;
case R.id.btnUpdate://若单击了标题为更新的按钮
//创建一个work thread(工作线程对象)
new Thread(){
public void run() {
//以下一行代码模拟更新进度,执行时间约为10秒
CommonUtils.timeConsuming(8);
//创建消息对象
Message msg=new Message();
//CommonUtils.FLAG_UPDATE代表更新操作
msg.what=CommonUtils.FLAG_UPDATE;
msg.obj="update finished";
mHandler.sendMessage(msg);//发送消息
};
}.start();//启动线程
break;
}
}
步骤2、以下是在Activity.onCreate()方法中(主线程)中创建的Handler对象-mHandler:
Button btnDownload.setOnClickListener(this);
Button btnUpdate.setOnClickListener(this);
mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case CommonUtils.FLAG_DOWNLOAD:
mTextView.setText(下载结束);
break;
case CommonUtils.FLAG_UPDATE:
mTextView.setText(更新结束);
break;
}
}
说明:
1、CommonUtils是一个自定义的工具类,该类中包括以下两个int类型的常量:
public static final int FLAG_DOWNLOAD=1;
public static final int FLAG_UPDATE=2;
2、msg是work thread线程发送过来的消息对象,该线程代码在步骤1中列出。
3、若msg.what的值是CommonUtils.FLAG_DOWNLOAD,则在标签中显示:下载结束。
若msg.what的值是CommonUtils.FLAG_UPDATE ,则在标签中显示:更新结束。
12.4. 发送Runnalbe对象-更新UI
12.4.1.概述
1、Runnable接口
该接口的源代码如下所示:
public interface Runnable {
public void run();
}
Java规定:一个线程要实现Runnable中的run方法。Android的线程也同样如此。在new 一个Thread时,查看Android源代码,将发现内部代码也是要创建一个Runnable的对象,并实现run方法。
2、Handler.post()方法
Handler类中有一个方法:post(Runnable r);
作用:将Runnable对象作为参数发送至消息对列中,以下是post方法的源代码:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
由上代码可知,Runnable对象r被当作消息的第一个参数发送至消息队列,然后由Looper交给主线程处理,如图-5所示。
根据以上原理,Runnable.run中的代码可以在主线程中运行,那么在Runnable.run方法中可以编写更新主线程UI的代码。
12.4.2.示例
在按钮单击事件方法中创建一个工作线程,在该线程中先模拟下载操作,操作结束后,向主线程发送了一个实现Runnable的内部匿名类,代码如下所示:
public void onClick(View v) {
switch(v.getId()){
case R.id.btnDownload:
//创建Handler对象
final Handler handler=new Handler();
//new一个工作线程模拟下载操作
new Thread(){
public void run() {
CommonUtils.timeConsuming(5);//模拟下载操作
/*下载结束后,
*将Runable的内部匿名类的代码
*当作消息中的第一个参数发送至消息对列,
* 再由Looper交给主线程执行*/
handler.post(new Runnable() {
@Override
public void run() {
mTextView.setText("download finished");
}
});
};
}.start();//启动工作线程
break;
case R.id.btnUpdate:
break;
}
}
12.5.runOnUiThread()发送Runnable对象
12.5.1.概述
Activity类中提供了一个runOnUiThread方法,该方法封装了Handler.post方法,因此作用与Handler.post相同。
12.5.2.示例
用runOnUiThread方法发送Runnable对象,让主线程执行该对象中的run方法。示例代码如下:
//new一个工作线程
new Thread(){
public void run() {
CommonUtils.timeConsuming(8); //模拟更新操作
//创建一个Runnable接口的实例
Runnable action=new Runnable() {
//实现run方法,在该方法中可修改UI
@Override
public void run() {
mTextView.setText("update finished");
}
};
//将action对象发送至消息对列,交由主线程执行run方法中的代码
runOnUiThread(action);
};
}.start();//启动线程
12.6. View.post()发送Runnable对象-更新UI
12.6.1.概述
View类中也提供了发送Runnable对象至消息对列,然后由Looper交给主线程的方法:
1、Post(Runnable r);
2、postDelayed(Runnable r,long delayMillis);
作用:延迟指定时间,再将r对象发送至主线程执行。
12.6.2.示例
@Override
public void onClick(final View v) {
switch (v.getId()) {
case R.id.download:
new Thread() {
public void run() {
CommonUtils.timeConsuming(1);
Runnable action = new Runnable() {
@Override
public void run() {
mTextView.setText("下载完成");
}
};
//v-当前的按钮对象,延迟1秒发送action对象由主线程执行
v.postDelayed(action, 1000);
}
}.start();
break;
case R.id.update:
new Thread() {
public void run() {
CommonUtils.timeConsuming(8);
Runnable action = new Runnable() {
@Override
public void run() {
mTextView.setText("更新新闻完成");
}
};
/v-当前的按钮对象,延迟3秒发送action对象由主线程执行
v.postDelayed(action, 3000);
}
}.start();
break;
12.7.post总结
12.7.1.概述
Android提供了post方法,将Runnable对象(当作消息中的第一参数)发送至消息对列,然后由Looper将消息对列中的消息交给主线程执行。如此,就可以在Runnable.run中编写修改UI的代码。但要注意:不要在run方法中编写耗时操作的代码。耗时的代码要放在工作线程的run方法中运行。以下列出已介绍过的post的方法
1、runOnUiThread(Runnable):该方法对handler.post进行了封装。
2、View.post(Runnable):运行被选择的控件对象发送Runnable对象。
3、Handler.post(Runnable)
4、View.postDelayed(Runnable, long):延迟发送
5、Handler.postDelayed(Runnable, long):延迟发送
12.7.2.Handler.post与runOnUiThread的比较
1. Handler可以实现线程间消息通讯。
2. 在代码的可读性和性能(创建对象的个数)方面,Handler都更有优势。
12.8. AsyncTask更新UI
12.8.1.概述
前面我们采用的都是new一个线程对象,采用这种匿名线程的方式存在以下缺陷:
第一,线程的开销较大,如果每个任务都要创建一个线程,那么应用程序的效率要低很多;
第二,线程无法管理,匿名线程创建并启动后就不受程序的控制了,如果有很多个请求发送,那么就会启动非常多的线程,系统将不堪重负。 另外,在工作线程中更新UI还必须要引入handler,这让代码看上去非常臃肿。
为了解决这一问题,引入了AsyncTask。AsyncTask的特点是任务在主线程之外运行,而回调方法是在主线程中执行, 这就有效地避免了使用Handler带来的麻烦。阅读AsyncTask的源码可知,AsyncTask是使用java.util.concurrent 框架来管理线程以及任务的执行的,concurrent框架是一个非常成熟,高效的框架,经过了严格的测试。这说明AsyncTask的设计很好的解决了匿名线程存在的问题。
AsyncTask是抽象类,子类必须实现抽象方法doInBackground(Params... p) ,在此方法中实现任务的执行工作,比如连接网络获取数据等。通常还应该实现onPostExecute(Result r)方法,因为应用程序关心的结果在此方法中返回。
提示:AsyncTask一定要在主线程中创建实例。
AsyncTask定义了三种泛型类型 Params,Progress和Result。
(1)Params 启动任务执行的输入参数,比如HTTP请求的URL。
(2)Progress 后台任务执行的百分比。
(3)Result 后台执行任务最终返回的结果,比如String。
12.8.2.常用方法
AsyncTask 的执行分为四个步骤,每一步都对应一个回调方法,需要注意的是这些方法不应该由应用程序调用,开发者需要做的就是实现这些方法。在任务的执行过程中,这些方法被自动调用。
1、 onPreExecute();
作用:当任务执行之前开始调用此方法,可以在这里显示进度对话框。
2、doInBackground(Params...);
作用:在后台线程执行,执行耗时操作。在执行过程中可以调用publicProgress(Progress...)来更新任务的进度。
提示:publicProgress()相当于Handler.sendmessage()方法
3、onProgressUpdate(Progress...);
作用:此方法在主线程执行,用于显示任务执行的进度。
4、onPostExecute(Result);
作用:此方法在主线程执行,任务执行的结果作为此方法的参数返回。
12.8.3.示例
用AsyncTask实现图-6效果,当单击dwonload按钮后,在进度条中显示模拟的下载进度,并在标签中显示进度的百分比。下载结束后显示download finised。
图-6a 图-6b
public class MainActivity extends Activity implements OnClickListener{
Handler mHandler;
ProgressBar mProgressBar;//声明进度条
TextView mTextView;//标签:显示进度的百分比和操作的结果
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//实例化各控件
mProgressBar=(ProgressBar)findViewById(R.id.progressBar);
mTextView=(TextView)findViewById(R.id.tvMessage);
Button btnDownload=(Button)findViewById(R.id.btnDownload);
Button btnUpdate=(Button)findViewById(R.id.btnUpdate);
//注册按钮的单击事件
btnDownload.setOnClickListener(this);
btnUpdate.setOnClickListener(this);
}
//实现按钮的单击时间事件
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnDownload:
//创建对象
MyAsyncTask myAsyncTask=new MyAsyncTask();
myAsyncTask.execute(null);//执行任务
break;
case R.id.btnUpdate:
break;
}
}
private class MyAsyncTask extends AsyncTaskURL, Integer, String{
//在UI中执行,更新UI
@Override
protected void onProgressUpdate(Integer... values) {
mProgressBar.setProgress(values[0]);
if(values[0]100){
mTextView.setText("progress="+values[0]+"%");
}
}
//现在work thread中执行耗时操作
@Override
protected String doInBackground(URL... params) {
for (int i = 0; i 100; i++) {
publishProgress(i+1);//向onProgressUpdate发送消息
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "download finished";
}
//doInBackground结束后,执行本方法,result是doInBackground方法返回的数据
@Override
protected void onPostExecute(String result) {
mTextView.setText(result);
}
}
}
12.9. 软件开发术语
12.9.1性能
临时对象越多,垃圾回收(GC)的频率越高
GC占用CPU,CPU被占用时,无法响应用户的操作
用户感觉到卡,影响用户体验。
12.9.2资源池
存放一定数量的同样类型的对象,当程序需要使用时,可以从资源池中获取,使用完成,收回资源池。
等待下一次被使用。
示例:从资源池中获取Message对象。
Message msg=Message.obtainMessage();
提示:若之前没有创建过Message,则创建给对象。Android系统会在适当时候回收该对象,方便下次取用。
提示:解决性能问题的前提是不能影响功能。
8.1.1.概述
除了在Java代码中定义数组,Android还提供了在资源中定义数组,然后在Java代码中解析资源,从而获取数组的方法。
实际开发中,推荐将数据存放在资源文件中,以实现程序的逻辑代码与数据分离,便于项目的管理,尽量减少对Java代码的修改。
8.1.2.在资源中定义数组
步骤1、在res/values文件夹下创建arrays.xml文件;
步骤2、在arrays.xml文件中创建一个数组,如下代码所示:
?xml version="1.0" encoding="UTF-8"?
resources
string-array name="citys"
item北京/item
item天津/item
item上海/item
item重庆/item
/string-array
/resources
说明:
Android规定存放数组的文件必须在res/values文件夹下创建,推荐存放资源数组的文件名为arrays.xml。
以上定义了一个含有四个直辖市名称的字符串数组,数组名是citys,数组元素在item标签中存放。
8.1.3.Resource类
8.1.3.1.概述
Android提供了Resource类,通过该类提供的方法可以很方便地获取资源中的数据,如资源中定义的数组。
8.1.3.2.创建Resources对象
getResource();
作用:该方法是ContextWrapper类的静态方法,用于创建Resources对象。
示例:以下代码创建一个Resources对象:
Resources res=Resources.getResource();
说明:该方法必须在Context类及其子类中才能使用。
8.1.3.4.常用方法
getStringArray(int resId);
作用:获取资源索引值为resId的字符串类型的数组。
示例:以下代码将8.1.2.中创建的citys数组获取并存放在数组citys中:
Resources res=getResources();
String[] citys=res.getStringArray(R.array.citys);
提示:Resources还提供了获取int、boolean等类型的数组的方法,本章仅介绍本章代码中用到的获取字符串数组的方法。
8.2.ListView控件
8.2.1.概述
ListView是android应用程序中使用频率最高的控件。该控件使用垂直列表的方式显示多个选项,特别适合于手机这种屏幕相对较小的设备。
ListView控件的特点:每个列表项独占一行,每行的布局都相同,数据和布局分离,
数据来自适配器,ListView只负责显示,图-1是ListView的一个应用:
图-1
8.2.2.常用属性
XML属性
说明
choiceMode
设置ListView的选择方式,有三个值:
(1)none:默认方式
(1) SingleChoice:单选
(2) multipleChoice:复选
divider
设置列表项的分隔条,分隔条可以是颜色值也可以是图片
entries
指定一个字符串数组资源,用于显示在ListView中
8.2.3.常用方法
1、Object getItemAtPosition(int position)
作用:获得当前列表项
参数-position:当前列表项的索引值。
示例:
//获取索引值是position的列表项,转换为字符串
String text=(String)listView.getItemAtPosition(position);
2、void setChoiceMode(int choiceMode)
作用:设置列表的选择方式
参数-choiceMode有以下三个可选值:
进程的优先级
12.1.1.概述
Android规定:进程的优先级分为以下五个级别,如图-1所示:
图-1
1、 前台进程 -Activte process
Active (前台) process是包含(与用户交互的)控件的那种应用程序。这些是Android通过回收资源来极力保护的进程。Active process包括:
(1)处于active状态的Activity,它们运行在前台来响应用户的事件。
(2)Activity Service或者正在执行onReceive事件处理函数的Broadcast Receiver。
(3)正在执行onStart,onCreate,OnDestroy事件处理函数的Service。
2、 可见进程-Visible Process
可见但不活动的进程是那些拥有可见Activity的进程。可见Activity是那些在屏幕上可见,但不是在前台或不响应用户事件的Activity。这种情况发生在当一个Activity被部分遮盖的时候(被一个非全屏或者透明的Activity)。可见进程只在极端的情况下,才会被杀死来保证Active Process的运行。包括以下情况:
(1)可见的Activity,但处于暂停(onPause()) 状态;
(2)被可见Activity绑定的Service
3、 服务进程 Service process
进程中包含已经启动的Service。Service以动态的方式持续运行但没有可见的界面。因为Service不直接和用户交互,它们拥有比visible Process较低的优先级。它们还是可以被认为是前台进程,不会被杀死,直到资源被active/visible Process需求。
4、 背景进程 Background process
进程中的Activity不可见和进程中没有任何启动的Service,这些进程都可以看作是后台进程。在系统中,拥有大量的后台进程,并且Android按照后看见先杀掉的原则来杀掉后台进程以获取资源给前台进程。
5、 空进程-Empty process
为了改善整个系统的性能,Android经常在内存中保留那些已经走完生命周期的应用程序。Android维护这些缓存来改善应用程序重新启动的时间。这些进程在资源需要的时候常常被杀掉。
当一个进程被杀掉,进程保留,变成空进程。
12.1.2.设置/取消Service为前台进程的方法
由上所述,Service排在进程的第三优先级,通常耗时的操作是放在线程中,那么将这样的线程放在Service中将会有较高的优先级,降低被Android系统杀掉的几率。
若是将线程放在Activity中,当Activity被完全遮盖,处于onStop状态时,其进程的优先级别降为第四级。明显不如放在处于第三级别的Service中更保险。
应用场景,如音乐播放器,通过在前台做其它操作时,音乐播放器在后台播放音乐,这种情况将播放音乐的线程放在Service中是适宜的。
Service类中有两个方法,分别用来设置Service为前台进程和取消前台进程。被设置为前台进程的Service拥有最高的优先级别,被Android系统杀掉的几率降至最低。
1、startForeground(int id,Notification noti);
作用:设置Service对象为前台进程。
说明:
第一个参数是通知的id值。
第二个参数是通知对象。
startForegroud方法的参数与通知管理器相同,使用上也类似,都是发送一个通知,并指定该通知对象的id值。
2、stopForeGround(int id);
作用:取消(指定id值所通知的Service对象)前台进程。
12.1.3.设置Service为前台进程的步骤
步骤1、在Service类的onStartCommand方法中(通常在该方法中)创建Intent对象,并指定与其绑定的Activity,示例代码如下:
Intent foreIntent=new Intent(this, MainActivity.class);
步骤2、创建PendingIntetn对象
PendingIntent pintent=PendingIntent.getActivity(
this, 0, foreIntent, PendingIntent.FLAG_UPDATE_CURRENT);
说明:第四个参数指明在通知栏随时刷新通知。
步骤3、创建通知对象,示例代码如下:
Notification noti=new Notification(
R.drawable.icon,"notification",System.currentTimeMillis());
说明:
第一个参数是通知栏中显示的本通知的图标。
第二个参数是通知栏中显示的本通知的标题。
第三个参数是本通知发出的时间。
步骤4、将此通知放到通知栏的(Ongoing)正在运行组中,示例代码如下:
noti.flags=Notification.FLAG_ONGOING_EVENT;
步骤5、设置通知的点击事件,示例代码如下:
noti.setLatestEventInfo(this, "title","content", pintent);
步骤6、向指定的Activity发送通知,并设置当前的Service对象为前台进程,示例代码如下:
startForeground(97789, noti);
12.1.4.示例
运行图-1所示的窗口:
图-2
1、单击图-1中的start foreground按钮,将启动一个Service对象,并设置改Service为前台进程,在该在日志窗口中出现图-2中红框内的第一行信息。
2、单击图-2中的stop foreground按钮,将取消Service的当前进程,并在日志窗口中显示图-2中红框内的第二行信息。
以下列出关键代码:
步骤1、创建项目exer12_01,包名为com.tarena.exer12_01,项目入口:MainActivity类,该中关键代码如下所示:
@Override
public void onClick(View v) {
//创建Intent对象,并设置目标组件为MyService
Intent intent=new Intent();
intent.setClass(this, MyService.class);
switch(v.getId()){
case R.id.btnStartFore:
//设置intent.action的值为Constant.ACTION_FORE
intent.setAction(Constant.ACTION_FORE);
startService(intent);//启动服务
break;
case R.id.btnStopFore:
//设置intent.action的值为Constant.ACTION_STOP_FORE
intent.setAction(Constant.ACTION_STOP_FORE);
startService(intent);
break;
case R.id.btnStopService:
stopService(intent);//停止服务
break;
}
}
步骤2、在src/com.tarena.exer12_01包下创建MyService.java该类继承自Service类。关键代码如下所示:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action=intent.getAction();
if(Constant.ACTION_FORE.equals(action)){
Log.i(tag,"startForeground");
Intent foreIntent=new Intent();
foreIntent.setClass(this, MainActivity.class);
PendingIntent pintent=PendingIntent.getActivity(
this, 0, foreIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification noti=new Notification(
R.drawable.icon,"notification",System.currentTimeMillis());
//将此通知放到通知栏的"Ongoing"即"正在运行"组中
noti.flags=Notification.FLAG_ONGOING_EVENT;
noti.setLatestEventInfo(
this, "改变Service优先级", "设置service为foreground级别", pintent);
startForeground(97789, noti);
}else if(Constant.ACTION_STOP_FORE.equals(action)){
Log.i(tag,"stopForeground");
stopForeground(true);//取消当前服务为前台服务
}
return super.onStartCommand(intent, flags, startId);
}
步骤3、打开项目清单文件,注册该服务,如下代码中红框中代码所示:
application android:icon="@drawable/icon"
android:label="@string/app_name"
service android:name="MyService"/service
/application
12.2.UI与线程
12.2.1.概述
UI是英文User Interface单词的简称。
当应用程序启动时,系统会为应用程序创建一个主线程(main)或者叫UI线程,它负责分发事件到不同的控件(例如绘画事件)以完成应用程序与Android UI孔庙件的交互。
例如,当触摸屏幕上的一个按钮时,UI线程会把触摸事件分发到控件上,更改状态并加入事件队列,UI线程会分发请求和通知到各个控件,完成相应的动作。
单线程模型的性能是非常差的,除非应用程序相当简单,特别是当所有的操作都在主线程中执行,比如访问网络或数据库之类的耗时操作将会导致用户界面锁定,所有的事件将不能分发,应用程序就像死了一样,更严重的是当超过5秒时,系统就会弹出应用程序无响应的对话框。
12.2.2.main线程
主线程也叫UI线程,主线程负责UI的创建,UI的刷新以及处理用户的输入事件。
提示:Android规定,Activity中的控件的刷新由主线程负责,其它线程不能直接刷新。
12.2.3.ANR术语
ANR的全称:Activity or Application is not responding,当用户操作超过系统规定的响应时间时,会弹出ANR对话框,如图-3所示:
图-3
若选择Force close按钮将强制关闭当前的Activity;
若选择Wait按钮将保留当前的Activity继续等待。
出现ANR的条件:
1. 在main线程(或称主线程)中有一个耗时操作正在执行,此时用户输入事件并且这个事件在5秒内没有得到响应,就会弹出ANR。
2. 广播接收者的onReceive()方法在10秒内没有执行完成,也会弹出ANR。
提示:在广播接收者的onReceive方法中要避免执行耗时的操作。
12.2.4.示例-测试ANR发生的两种情况
创建项目exer12_02,在该类中创建MyReceiver类,该类是BroadcastRecevier的子类。
图-4
1、单击download按钮,在主线程中执行一个循环模拟从网络下载数据的操作,该循环耗时10秒,在该循环执行过程中,在图-4的标注所指的编辑框中连续试图输入字符串,5秒后,将弹出图-3所示的ANR对话框。
2、单击send broadcast按钮,发送广播,然后在图-4的标注所指的编辑框内连续试图输入字符串,10秒后将弹出ANR对话框。
以下是关键代码:
步骤1、创建工具类-CommonUtils.java,该类中定义了一个模拟耗时操作的循环,代码如下所示:
public final class CommonUtils {
public static final String ACTION_RECEIVER="com.tarena.ACTION_RECEIVER";
public static void timeConsuming(int n){
for(int i=0;in;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
步骤2、创建MyReceiver.java类,该类继承自BroadcastReceiver类,代码如下所示:
public class MyReceiver extends BroadcastReceiver {
private static final String tag="MyReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.i(tag,"onReceiver");
CommonUtils.timeConsuming(15);
}
}
步骤3、以下是MainActivity.java类的代码,该代码负责发送广播,模拟下载的耗时操作,代码如下所示:
public class MainActivity extends Activity implements OnClickListener{
TextView mTextView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mTextView=(TextView)findViewById(R.id.tv);
//实例化按钮对象
Button btnDownload=(Button)findViewById(R.id.btnDownload);
Button btnSendBraodcast=(Button)findViewById(R.id.btnSendBroadcast);
//注册按钮对象的单击事件
btnDownload.setOnClickListener(this);
btnSendBraodcast.setOnClickListener(this);
}
//实现按钮的单击事件
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.btnDownload://下载按钮
CommonUtils.timeConsuming(10);//模拟(耗时10秒的)下载
mTextView.setText("finished");
break;
case R.id.btnSendBroadcast://发送广播按钮
//发送广播
Intent intent=new Intent(CommonUtils.ACTION_RECEIVER);
sendBroadcast(intent);
mTextView.setText("finished");
break;
}
}
}
12.3.Message对象
12.3.1.概述
Message类用于存放消息,该类通常与Handler类配合使用。
12.3.2.常用属性
1、int arg1:存放一个int类型的数据。
2、int arg2:存放一个int类型的数据。
3、int what:存放一个int类型的数据。
4、Object obj:存放任意类型的对象。
12.3.3.示例代码
Message msg=new Message();
msg.what=1;
msg.arg1=100;
msg.obj=hello;
msg.obj=new Runnable();
12.4.用Handler更新UI
12.4.1.概述
由以上所述,在主线程中不宜执行耗时操作,因此,通常的耗时操作都放在其它线程中,Androidghi称这样的线程为work thread(工作线程)。但Android还规定:只有主线程才能修改Activity中的控件,其它线程不能修改。
解决以上问题有多种方法,本节介绍如何通过Handler类提供的方法解决工作线程不能直接修改UI的问题。
Handler修改主线程UI的思路:Handler对象通过在工作线程中发送消息,该消息发送至消息对列中,等待处理。
在主线程中从消息对列中接收消息,根据消息中的信息决定如何更新主线程中UI.
12.4.2.常用方法
1、sendEmptyMessage(int what);
作用:从work thead(工作线程)向主线程发送一个空消息。
说明:若多个线程向主线程发送消息,则参数what用于区别不同的线程。
2、sendEmptyMessageAtTime(int what,long uptime);
作用:从work thead(工作线程)按指定时间发送空消息。
说明:第二个参数uptime:指定的时间。
3、sendEmptyMessageDelayed(int what,long delay);
作用:从work thead(工作线程)延迟发送空消息。
说明:第二个参数用于指定延迟的时间,单位:毫秒。
5、sendMessage(Message msg);
作用:从work thead(工作线程)向主线程发送消息;
说明:msg是存放消息数据的对象。
6、sendMessageAtTime(Message msg,long uptime);
作用:从work thead(工作线程)按指定时间向主线程发送消息
7、sendMessageDelayed(Message msg,long delay);
作用:从work thead(工作线程)延迟指定时间向主线程发送消息。
8、handleMessage(Message msg);
作用:接收并处理从work thread发送的消息。
说明:参数-msg:send***Message发送过来的消息对象。
图-5是Android处理消息机制的示意图:
图-5
图-5显示了Android系统提供了一个称为Looper(循环对列)用来管理消息对列,各线程通过Handler类的Send***Message命令将消息发送至消息对列,Looper再将消息对列中的消息依次交给主线程处理。
12.4.3.示例
从两个工作线程向主线程发送不同的消息,主线程接收消息并显示不同的处理信息。
步骤1、以下是Activity类中的onClick()方法中的代码,该方法用于处理按钮单击事件。
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.btnDownload://若单击了标题为下载的按钮
//创建一个work thread(工作线程)线程对象
new Thread(){
public void run() {
//以下一行代码模拟下载进度,执行时间约为10秒
CommonUtils.timeConsuming(10);
//创建消息对象
Message msg=new Message();
//CommonUtils.FLAG_DOWNLOAD值代表下载操作
msg.what=CommonUtils.FLAG_DOWNLOAD;
msg.obj="download finished";
mHandler.sendMessage(msg);//发送消息
};
}.start();//启动线程
break;
case R.id.btnUpdate://若单击了标题为更新的按钮
//创建一个work thread(工作线程对象)
new Thread(){
public void run() {
//以下一行代码模拟更新进度,执行时间约为10秒
CommonUtils.timeConsuming(8);
//创建消息对象
Message msg=new Message();
//CommonUtils.FLAG_UPDATE代表更新操作
msg.what=CommonUtils.FLAG_UPDATE;
msg.obj="update finished";
mHandler.sendMessage(msg);//发送消息
};
}.start();//启动线程
break;
}
}
步骤2、以下是在Activity.onCreate()方法中(主线程)中创建的Handler对象-mHandler:
Button btnDownload.setOnClickListener(this);
Button btnUpdate.setOnClickListener(this);
mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case CommonUtils.FLAG_DOWNLOAD:
mTextView.setText(下载结束);
break;
case CommonUtils.FLAG_UPDATE:
mTextView.setText(更新结束);
break;
}
}
说明:
1、CommonUtils是一个自定义的工具类,该类中包括以下两个int类型的常量:
public static final int FLAG_DOWNLOAD=1;
public static final int FLAG_UPDATE=2;
2、msg是work thread线程发送过来的消息对象,该线程代码在步骤1中列出。
3、若msg.what的值是CommonUtils.FLAG_DOWNLOAD,则在标签中显示:下载结束。
若msg.what的值是CommonUtils.FLAG_UPDATE ,则在标签中显示:更新结束。
12.4. 发送Runnalbe对象-更新UI
12.4.1.概述
1、Runnable接口
该接口的源代码如下所示:
public interface Runnable {
public void run();
}
Java规定:一个线程要实现Runnable中的run方法。Android的线程也同样如此。在new 一个Thread时,查看Android源代码,将发现内部代码也是要创建一个Runnable的对象,并实现run方法。
2、Handler.post()方法
Handler类中有一个方法:post(Runnable r);
作用:将Runnable对象作为参数发送至消息对列中,以下是post方法的源代码:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
由上代码可知,Runnable对象r被当作消息的第一个参数发送至消息队列,然后由Looper交给主线程处理,如图-5所示。
根据以上原理,Runnable.run中的代码可以在主线程中运行,那么在Runnable.run方法中可以编写更新主线程UI的代码。
12.4.2.示例
在按钮单击事件方法中创建一个工作线程,在该线程中先模拟下载操作,操作结束后,向主线程发送了一个实现Runnable的内部匿名类,代码如下所示:
public void onClick(View v) {
switch(v.getId()){
case R.id.btnDownload:
//创建Handler对象
final Handler handler=new Handler();
//new一个工作线程模拟下载操作
new Thread(){
public void run() {
CommonUtils.timeConsuming(5);// 猜你喜欢