px 与 dp, sp换算公式?

PPI = Pixels per inch,每英寸上的像素数,即 “像素密度”

  • xhdpi: 2.0
  • hdpi: 1.5
  • mdpi: 1.0 (baseline)
  • ldpi: 0.75

drawable-ldpi、drawable-mdpi、drawable-hdpi 精度分别为低、中(android默认)、高。
对应的图片大小为:36×36、48×48、72×72。

xxhdpi: 144*144
xhdpi:96*96
hdpi:72*72
mdpi:48*48
ldpi:36*36

 

dp是虚拟像素,在不同的像素密度的设备上会自动适配,比如:
在320×480分辨率,像素密度为160,1dp=1px
在480×800分辨率,像素密度为240,1dp=1.5px
计算公式:1dp*像素密度/160 = 实际像素数

drawable- hdpi、drawable- mdpi、drawable-ldpi的区别:
(1)drawable-hdpi里面存放高分辨率的图片,如WVGA (480×800),FWVGA (480×854)
(2)drawable-mdpi里面存放中等分辨率的图片,如HVGA (320×480)
(3)drawable-ldpi里面存放低分辨率的图片,如QVGA (240×320)
系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片。


ppi的运算方式是:

PPI = √(长度像素数² + 宽度像素数²) / 屏幕对角线英寸数

dp:Density-independent pixels,以160PPI屏幕为标准,则1dp=1px,

dp和px的换算公式 :
dp*ppi/160 = px。比如1dp x 320ppi/160 = 2px。

 

sp:Scale-independent pixels,它是安卓的字体单位,以160PPI屏幕为标准,当字体大小为 100%时, 1sp=1px。

sp 与 px 的换算公式:sp*ppi/160 = px

 

总结得出:

px = dp*ppi/160
dp = px / (ppi / 160)

px = sp*ppi/160
sp = px / (ppi / 160)

dp = sp?

 

参考:

[无线手册-4] dp、sp、px傻傻分不清楚[完整]
http://zhuanlan.zhihu.com/zhezhexiong/19565895

Supporting Different Screens | Android Developers
http://developer.android.com/training/basics/supporting-devices/screens.html#create-bitmaps

Iconography | Android Developers
http://developer.android.com/design/style/iconography.html

Devices and Displays | Android Developers
http://developer.android.com/design/style/devices-displays.html

 

看过几篇关于android中像素点的帖子,觉得这篇文章写得还不错,

原文地址:http://www.cnblogs.com/bluestorm/p/3640786.html

耍流氓之Service关闭又自动启(AlarmManager、PendingIntent、BroadcastReceiver、Service)

原文地址:http://blog.csdn.net/arui319/article/details/7040980#

==============================================================================

首先要说的是,用户可能把这种做法视为流氓软件。大部分时候,程序员也不想把软件做成流氓软件,没办法,领导说了算。

我们在使用某些Android应用的时候,可能会发现安装了某应用以后,会有一些服务也会随之运行。而且,这些服务每次都会随着手机开机而启动。有的服务做的更绝,当用户停止该服务器以后,过了一段时间,服务又自动运行了。虽然,从用户的角度来说,这种方式比较流氓。但是,从程序员的角度来说,这是如何做到的呢?经过研究,我发现有一种方式是可以实现的。下面就和大家分享。

先简单介绍,一会儿会贴上全部代码。

如何做到开机启动?

这个比较简单,网上的资料够多,只要实现一个BroadcastReceiver,监听手机启动完成的事件ACTION_BOOT_COMPLETED即可。需要注意的是,好像不能用模拟器,要用手机测试。

那如何做到启动一个Service,并且在用户关闭后能自动又启动了呢?

一般的,都会在上面说到的BroadcastReceiver的实现里面,监听手机启动完成后,启动一个Service,这是一般的做法。问题是,用户可以关闭掉该Service。那么怎样才能使它被关闭掉以后,再次启动呢?聪明的你一定立即就想到了,如果不直接启动Service,而是启动一个timmer,或者alarmManager,然后每隔一段时间去启动Service,就可以了。

还是看下面的全部代码吧,不过多解释了。这些代码中还是有不少概念的,不熟悉AlarmManager、PendingIntent、BroadcastReceiver、Service等等这些概念的同学可以百度一下。

package com.arui.framework.android.daemonservice;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;

public class BootBroadcast extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent mintent) {

		if (Intent.ACTION_BOOT_COMPLETED.equals(mintent.getAction())) {
			// 启动完成
			Intent intent = new Intent(context, Alarmreceiver.class);
			intent.setAction("arui.alarm.action");
			PendingIntent sender = PendingIntent.getBroadcast(context, 0,
					intent, 0);
			long firstime = SystemClock.elapsedRealtime();
			AlarmManager am = (AlarmManager) context
					.getSystemService(Context.ALARM_SERVICE);

			// 10秒一个周期,不停的发送广播
			am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstime,
					10 * 1000, sender);
		}

	}
}

 

package com.arui.framework.android.daemonservice;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class Alarmreceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent) {

		if (intent.getAction().equals("arui.alarm.action")) {
			Intent i = new Intent();
			i.setClass(context, DaemonService.class);
			// 启动service 
			// 多次调用startService并不会启动多个service 而是会多次调用onStart
			context.startService(i);
		}
	}
}

 

package com.arui.framework.android.daemonservice;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class DaemonService extends Service {

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

    @Override
    public void onCreate() {
    	super.onCreate();
    	Log.v("=========", "***** DaemonService *****: onCreate");
    }

    @Override
    public void onStart(Intent intent, int startId) {
    	Log.v("=========", "***** DaemonService *****: onStart");
    	// 这里可以做Service该做的事
    }
}

 

下面是manifest文件的代码。

 

		<receiver 
			android:name="com.arui.framework.android.daemonservice.BootBroadcast"
			android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
			<intent-filter>
				<action android:name="android.intent.action.BOOT_COMPLETED" />
			</intent-filter>
		</receiver>
		<receiver 
			android:name="com.arui.framework.android.daemonservice.Alarmreceiver" >
			<intent-filter>
				<action android:name="arui.alarm.action" />
			</intent-filter>
		</receiver>
        <service
            android:name="com.arui.framework.android.daemonservice.DaemonService" >
        </service>

 

继续讨论这个问题。

如果用户停止整个应用(在管理应用程序中停止应用,或者第三方软件停止整个应用),此时整个进程被杀死,所有的服务自然也被杀死了,timmer,或者alarmManager也就停止了。此时就不会再定期启动服务了。

那么,怎么才能做到,用户或者第三方软件无法停止整个应用呢。我们可以再注册一个系统级别的监听(BroadcastReceiver),来监听系统级别的消息,再次启动timmer,或者alarmManager。这样,即使应用被杀死了,隔一段时间,应用还会自动启动。具体的,就不在这里展开了。

全局定时器AlarmManager

自从知道了android中Timer在手机锁屏的时候不会正常倒计时后,就寻找到了另外一种全局的计时器AlarmManager,它是android中提供的一个系统服务,即使手机在锁屏的情况下也能倒计时。短时间的计时是没问题的,长时间的倒计时正在进行测试。

下面先了解下AlarmManager的一些方法和属性:

AlarmManager 包含的主要方法:

// 取消已经注册的与参数匹配的定时器
void cancel(PendingIntent operation)
//注册一个新的延迟定时器
void set(int type, long triggerAtTime, PendingIntent operation)
//注册一个重复类型的定时器
void setRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)
//注册一个非精密的重复类型定时器
void setInexactRepeating (int type, long triggerAtTime, long interval, PendingIntent operation)
//设置时区
void setTimeZone(String timeZone)

定时器主要类型:

public static final int ELAPSED_REALTIME
// 当系统进入睡眠状态时,这种类型的闹铃不会唤醒系统。直到系统下次被唤醒才传递它,该闹铃所用的时间是相对时间,是从系统启动后开始计时的,包括睡眠时 间,可以通过调用SystemClock.elapsedRealtime()获得。系统值是3 (0x00000003)。

public static final int ELAPSED_REALTIME_WAKEUP
//能唤醒系统,用法同ELAPSED_REALTIME,系统值是2 (0x00000002) 。

public static final int RTC
//当系统进入睡眠状态时,这种类型的闹铃不会唤醒系统。直到系统下次被唤醒才传递它,该闹铃所用的时间是绝对时间,所用时间是UTC时间,可以通过调用 System.currentTimeMillis()获得。系统值是1 (0x00000001) 。

public static final int RTC_WAKEUP
//能唤醒系统,用法同RTC类型,系统值为 0 (0x00000000) 。

Public static final int POWER_OFF_WAKEUP
//能唤醒系统,它是一种关机闹铃,就是说设备在关机状态下也可以唤醒系统,所以我们把它称之为关机闹铃。使用方法同RTC类型,系统值为4(0x00000004)。

当你的应用不在运行,而此时你仍然需要你的应用去执行一些操作(比如,短信拦截),只有这种时候才使用AlarmManager, 其他正常情况下的,推荐使用Handler。

AlarmManager 生命周期:

repeating AlarmManager一旦启动就会一直在后台运行(除非执行cancel方法),可以在“应用管理”中看到这个应用状态是正在运行。 “强行停止”可以让Alarmmanager停掉。

尝试了几种任务管理器, 都只能重置计数器(确实释放内存了),但都无法关闭定时器,只有系统自带的“强行停止”奏效。

如果某个AlarmManager已经启动, 程序又再次去启动它,只要PendingIntent是一样,那么之前那个AlarmManager会被release掉。

如何使用AlarmManager?

使用AlarmManager共有三种方式, 都是通过PendingIntent。

getActivity(Context, int, Intent, int)

getBroadcast(Context, int, Intent, int)

getService(Context, int, Intent, int)

==========================代码示例===========================================

package com.aibb.alarmmanagertest;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;

public class AlarmManagerService extends Service{

private class AlarmOnBroadcastReciver extends BroadcastReceiver{

@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(“com.aibb.alarmamanger.start”)){
logTimeMsg(“alarm manager end:”);
}
}

}

@Override
public void onCreate() {
super.onCreate();
//一定要注册reveicer
AlarmOnBroadcastReciver reve = new AlarmOnBroadcastReciver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(“com.aibb.alarmamanger.start”);
this.registerReceiver(reve, intentFilter);

startAlarmManager();
}

@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}

private void startAlarmManager() {
Intent intent =new Intent(“com.aibb.alarmamanger.start”);
PendingIntent sender=PendingIntent
.getBroadcast(this, 0, intent, 0);
//开始时间
long firstime=SystemClock.elapsedRealtime();//System.currentTimeMillis();//SystemClock.elapsedRealtime();
long currenttime = System.currentTimeMillis();
firstime+=1000;
AlarmManager am=(AlarmManager)getSystemService(ALARM_SERVICE);

//如果是周期性的执行,就需要使用SystemClock.elapsedRealtime() time since boot
/*am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP
, firstime, 5*1000, sender);*/
// am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstime+180*1000, sender);
am.set(AlarmManager.RTC_WAKEUP, currenttime+1800*1000, sender);
logTimeMsg(“alarm manager start:”);
}

private static final String TAG = AlarmManagerActivity.class.getSimpleName();

private Writer logWriter;

private File logFile;

/**
* 记录Timer的执行时间
* @param location
*/
public void logTimeMsg(String prefixStr) {
if (null == logFile) {
logFile = new File(Environment.getExternalStorageDirectory(),”AlarmManagerTime.txt”);
}
if (null == logWriter) {
try {
logWriter = new FileWriter(logFile, true);
} catch (Exception e) {
Log.e(“EnvironmentChangeReceiver”, e.getMessage(), e);
}
}
try {
String jsonString = prefixStr;
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
String time = sdf.format(date);;
logWriter.write(jsonString+”:” +time+ “\r\n”);
logWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}

}

android Timer运行时的问题

这几天有个APP需要在刷了NFC后一段时间内保持一个状态不改变,比如10min内一直保持一个状态,然后再到了10min时做一个判断,更新其最新状态。当时一想到这个场景,立马就想到了使用android中定时器Timer来实现这个功能,起初也没多想,直到测试的时候才发现这个Timer在运行时有很多问题。比如,Timer在手机锁屏的情况下就会停止运行,当开屏的时候才会继续倒计时,还有就是Timer在锁屏的情况下可能会被回收。

在网上查阅帖子后,找到了一篇测试Timer的文章,写的很好。

原文地址是:http://blog.csdn.net/lzqjfly/article/details/8021551

============================================================================

目标: 希望采用Timer来计时,要求在服务中运行,每10分钟记录一次数据。但是采用timer来做了以后,发现统计的次数没有达到预期的目标。甚至没有运行,以下是在测试情况

1.为了能够看到测试效果,将循环时间设置为2秒

本打算用服务做测试,但为了方便就用activity做测试

package com.test.timertest;
  1. /**
  2.  * 对计时器的测试
  3.  */
  4. import java.util.Timer;
  5. import java.util.TimerTask;
  6. import android.app.Activity;
  7. import android.os.Bundle;
  8. import android.os.Handler;
  9. import android.os.Message;
  10. import android.widget.TextView;
  11. public class TimerActivity extends Activity {
  12.     private TextView txtCount;
  13.     private int count;
  14.     //处理界面
  15.     private  Handler handler = new Handler(){
  16.         public void handleMessage(android.os.Message msg) {
  17.             if(msg.arg1 == 1){
  18.                 txtCount.setText(String.valueOf(count));
  19.             }
  20.         };
  21.     };
  22.     @Override
  23.     protected void onCreate(Bundle savedInstanceState) {
  24.         // TODO Auto-generated method stub
  25.         super.onCreate(savedInstanceState);
  26.         setContentView(R.layout.activity_main);
  27.         this.txtCount = (TextView)findViewById(R.id.count);
  28.         new Timer().schedule(countTask, 102000);  //延迟10毫秒,每2秒钟执行一次     
  29.     }
  30.     //任务
  31.     TimerTask countTask = new TimerTask() {
  32.         @Override
  33.         public void run() {
  34.             // TODO Auto-generated method stub
  35.             count ++;
  36.             Message msg = new Message();
  37.             msg.arg1 = 1;
  38.             handler.sendMessage(msg);
  39.         }
  40.     };
  41. }

结果:

1.将手机与电脑连接测试,改程序正常,能够一直运行。并且按下电源键后仍然能够正常运行,统计的次数也正常

2.手机与电脑断开连接后,然后重新运行改程序,该程序能正常运行。然后按下电源键,手机处于待机状态,过一段时间后在看屏幕上的次数,发现次数没有动,不知道为啥???

3.手机与电脑断开连接后,运行程序,然后按home键,在手机没有处于待机的状态下,统计的次数发生变化,能够正常运行。但是如果手机处于待机状态后,程序不在运行。

问题: 手机待机后会让大部分程序不在运行(除电话,短信等)。难道这是系统的保护机制???

2.采用线程的Sleep处理;

  1. package com.test.timertest;
  2. /**
  3.  * 对计时器的测试
  4.  */
  5. import java.util.Timer;
  6. import java.util.TimerTask;
  7. import android.app.Activity;
  8. import android.os.Bundle;
  9. import android.os.Handler;
  10. import android.os.Message;
  11. import android.widget.TextView;
  12. public class TimerActivity extends Activity {
  13.     private TextView txtCount;
  14.     private int count;
  15.     //处理界面
  16.     private  Handler handler = new Handler(){
  17.         public void handleMessage(android.os.Message msg) {
  18.             if(msg.arg1 == 1){
  19.                 txtCount.setText(String.valueOf(count));
  20.             }
  21.         };
  22.     };
  23.     @Override
  24.     protected void onCreate(Bundle savedInstanceState) {
  25.         // TODO Auto-generated method stub
  26.         super.onCreate(savedInstanceState);
  27.         setContentView(R.layout.activity_main);
  28.         this.txtCount = (TextView)findViewById(R.id.count);
  29. //      new Timer().schedule(countTask, 10, 2000);  //延迟10毫秒,每2秒钟执行一次     
  30.         new CountThread().start();
  31.     }
  32.     class CountThread extends Thread{
  33.         @Override
  34.         public void run() {
  35.             while(true){
  36.                 count ++;
  37.                 Message msg = new Message();
  38.                 msg.arg1 = 1;
  39.                 handler.sendMessage(msg);
  40.                 try {
  41.                     Thread.sleep(2000);
  42.                 } catch (InterruptedException e) {
  43.                     // TODO Auto-generated catch block
  44.                     e.printStackTrace();
  45.                 }
  46.             }
  47.         }
  48.     }
  49. //  //任务
  50. //  TimerTask countTask = new TimerTask() {
  51. //      
  52. //      @Override
  53. //      public void run() {
  54. //          // TODO Auto-generated method stub
  55. //          count ++;
  56. //          Message msg = new Message();
  57. //          msg.arg1 = 1;
  58. //          handler.sendMessage(msg);
  59. //      }
  60. //  };
  61. }

采用Sleep的处理结果和上面1中的一样,怀疑是不是activity和thread有区别,于是采用线程来处理,并将结果保存到xml中

服务如下:

  1. package com.test.timertest;
  2. import java.util.Timer;
  3. import java.util.TimerTask;
  4. import android.app.Service;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.content.SharedPreferences;
  8. import android.content.SharedPreferences.Editor;
  9. import android.os.IBinder;
  10. public class CountService extends Service {
  11.     @Override
  12.     public IBinder onBind(Intent intent) {
  13.         // TODO Auto-generated method stub
  14.         return null;
  15.     }
  16.     @Override
  17.     public void onCreate() {
  18.         // TODO Auto-generated method stub
  19.         super.onCreate();
  20.         new Timer().schedule(countTask, 102000);   //2秒钟
  21.     }
  22.     // 任务
  23.     TimerTask countTask = new TimerTask() {
  24.         @Override
  25.         public void run() {
  26.             saveAppCount();
  27.         }
  28.     };
  29.     // 保存数据
  30.     private void saveAppCount() {
  31.         int count = getAppCount() + 1;
  32.         SharedPreferences sf = getSharedPreferences(“appcount”,
  33.                 Context.MODE_PRIVATE);
  34.         Editor editor = sf.edit();
  35.         editor.putInt(“count”, count);
  36.         editor.commit();
  37.     }
  38.     // 获取数据
  39.     public int getAppCount() {
  40.         SharedPreferences spf = getSharedPreferences(“appcount”,
  41.                 Context.MODE_PRIVATE);
  42.         return spf.getInt(“count”0);
  43.     }
  44. }

显示数据的activity

  1. package com.test.timertest;
  2. /**
  3.  * 对计时器的测试
  4.  */
  5. import android.app.Activity;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.content.SharedPreferences;
  9. import android.os.Bundle;
  10. import android.widget.TextView;
  11. public class TimerActivity extends Activity {
  12.     private TextView txtCount;
  13.     @Override
  14.     protected void onCreate(Bundle savedInstanceState) {
  15.         // TODO Auto-generated method stub
  16.         super.onCreate(savedInstanceState);
  17.         setContentView(R.layout.activity_main);
  18.         this.txtCount = (TextView)findViewById(R.id.count);
  19.         SharedPreferences spf = getSharedPreferences(“appcount”,
  20.                 Context.MODE_PRIVATE);
  21.         int count =  spf.getInt(“count”0);
  22.         txtCount.setText(String.valueOf(count));
  23.         Intent intent = new Intent(this,CountService.class);
  24.         startService(intent);
  25.     }
  26. }

测试结果::

1.手机和电脑连接,手机处于调试模式,不管是按下电源键让手机处于待机状态还是按下home键,服务都能够正常的统计数据

2.手机与电脑断开连接,不管是手机自动处于待机状态还是主动按下电源键让手机处于待机状态,服务里面的线程都没有正常的记录数据。 求解 ???

最终结合网上资料采用AlarmManager 控制计时操作,能够保证系统在sleep的时候发出广播,达到统计的目的

  1. package com.test.timertest;
  2. /**
  3.  * 对计时器的测试
  4.  */
  5. import java.util.Timer;
  6. import android.app.Activity;
  7. import android.app.AlarmManager;
  8. import android.app.PendingIntent;
  9. import android.content.Context;
  10. import android.content.Intent;
  11. import android.content.SharedPreferences;
  12. import android.os.Bundle;
  13. import android.os.SystemClock;
  14. import android.widget.TextView;
  15. public class TimerActivity extends Activity {
  16.     private TextView txtCount;
  17.     public final String ACTION = “com.test.timertest.alarmreciver”;
  18.     @Override
  19.     protected void onCreate(Bundle savedInstanceState) {
  20.         // TODO Auto-generated method stub
  21.         super.onCreate(savedInstanceState);
  22.         setContentView(R.layout.activity_main);
  23.         this.txtCount = (TextView)findViewById(R.id.count);
  24.         SharedPreferences spf = getSharedPreferences(“appcount”,
  25.                 Context.MODE_PRIVATE);
  26.         int count =  spf.getInt(“count”0);
  27.         txtCount.setText(String.valueOf(count));
  28. //      Intent intent = new Intent(this,CountService.class);
  29. //      startService(intent);
  30.         //闹钟全局变量
  31.         AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
  32.         Intent intent = new Intent(ACTION);
  33.         PendingIntent sender = PendingIntent.getBroadcast(this0, intent, 0);
  34.         long firsttime = SystemClock.elapsedRealtime();
  35.         firsttime  += 2*1000;
  36.         am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,firsttime, 2*1000,sender);  //AlarmManager.ELAPSED_REALTIME_WAKEUP 这里要用这个类型的tiype才能保证系统在sleep的时候也能发广播,不懂的可以去看文档的介绍
  37.     }
  38. }

接受广播的类

  1. package com.test.timertest;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.content.SharedPreferences;
  6. import android.content.SharedPreferences.Editor;
  7. public class AlarmReciver extends BroadcastReceiver {
  8.     @Override
  9.     public void onReceive(Context context, Intent intent) {
  10.         // TODO Auto-generated method stub
  11.         saveAppCount(context);
  12.     }
  13.     // 保存数据
  14.     private void saveAppCount(Context context) {
  15.         int count = getAppCount(context) + 1;
  16.         SharedPreferences sf = context.getSharedPreferences(“appcount”,
  17.                 Context.MODE_PRIVATE);
  18.         Editor editor = sf.edit();
  19.         editor.putInt(“count”, count);
  20.         editor.commit();
  21.     }
  22.     // 获取数据
  23.     public int getAppCount(Context context) {
  24.         SharedPreferences spf = context.getSharedPreferences(“appcount”,
  25.                 Context.MODE_PRIVATE);
  26.         return spf.getInt(“count”0);
  27.     }
  28. }

 

Intent和PendingIntent的区别

从网络上总结Intent和PendingInent的区别:

intent英文意思是意图pending表示即将发生或来临的事情
PendingIntent这个类用于处理即将发生的事情。比如在通知Notification中用于跳转页面,但不是马上跳转。

Intent 是及时启动,intent 随所在的activity 消失而消失。
PendingIntent 可以看作是对intent的包装,通常通过getActivity,getBroadcast ,getService来得到pendingintent的实例,当前activity并不能马上启动它所包含的intent,而是在外部执行 pendingintent时,调用intent的。正由于pendingintent中 保存有当前App的Context,使它赋予外部App一种能力,使得外部App可以如同当前App一样的执行pendingintent里的 Intent, 就算在执行时当前App已经不存在了,也能通过存在pendingintent里的Context照样执行Intent。另外还可以处理intent执行后的操作。常和alermanger 和notificationmanager一起使用。
Intent一般是用作Activity、Sercvice、BroadcastReceiver之间传递数据,而Pendingintent,一般用在 Notification上,可以理解为延迟执行的intent,PendingIntent是对Intent一个包装。

Android中的消息通知Notification和NotificationManager

Notification,即通知,一般用在电话,短信,邮件,闹钟铃声,在手机的状态栏上就会出现一个小图标,提示用户处理这个通知,这时手从上方滑动状态栏就可以展开并处理这个快讯。

已添加的Notification.Builder,使其更容易构建通知。notification是一种让你的应用程序在没有开启情况下或在后台运行警示用户。它是看不见的程序组件(Broadcast Receiver,Service和不活跃的Activity)警示用户有需要注意的事件发生的最好途径。

先来区分以下状态栏和状态条的区别:

1、状态条就是手机屏幕最上方的一个条形状的区域;

在状态条有好多信息量:比如usb连接图标,手机信号图标,电池电量图标,时间图标等等;

2、状态栏就是手从状态条滑下来的可以伸缩的view;

在状态栏中一般有两类(使用FLAG_标记):

(1)正在进行的程序;

(2)是通知事件;

通常情况下发布一条Notification分4个步骤:

1、通过getSystemService()方法得到NotificationManager对象;

nManager = (NotificationManager) this.getSystemService(service);

2、对Notification的一些属性进行设置比如:内容,图标,标题,相应notification的动作进行处理等等;

notification.icon = R.drawable.ic_launcher;// 设置通知的图标
notification.tickerText = tickerText; // 显示在状态栏中的文字
notification.when = when; // 设置来通知时的时间

3、通过NotificationManager对象的notify()方法来执行一个notification的消息;

// 发出通知
nManager.notify(ID, notification);

4、取消一个Notification

nManager.cancel(ID);

Notification的一些其他特性:

/*

* 添加声音

* notification.defaults |=Notification.DEFAULT_SOUND;

* 或者使用以下几种方式

* notification.sound = Uri.parse(“file:///sdcard/notification/ringer.mp3”);

* notification.sound = Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, “6”);

* 如果想要让声音持续重复直到用户对通知做出反应,则可以在notification的flags字段增加”FLAG_INSISTENT”

* 如果notification的defaults字段包括了”DEFAULT_SOUND”属性,则这个属性将覆盖sound字段中定义的声音

*/

/*

* 添加振动

* notification.defaults |= Notification.DEFAULT_VIBRATE;

* 或者可以定义自己的振动模式:

* long[] vibrate = {0,100,200,300}; //0毫秒后开始振动,振动100毫秒后停止,再过200毫秒后再次振动300毫秒

* notification.vibrate = vibrate;

* long数组可以定义成想要的任何长度

* 如果notification的defaults字段包括了”DEFAULT_VIBRATE”,则这个属性将覆盖vibrate字段中定义的振动

*/

/*

* 添加LED灯提醒

* notification.defaults |= Notification.DEFAULT_LIGHTS;

* 或者可以自己的LED提醒模式:

* notification.ledARGB = 0xff00ff00;

* notification.ledOnMS = 300; //亮的时间

* notification.ledOffMS = 1000; //灭的时间

* notification.flags |= Notification.FLAG_SHOW_LIGHTS;

*/

/*

* 更多的特征属性

* notification.flags |= FLAG_AUTO_CANCEL; //在通知栏上点击此通知后自动清除此通知

* notification.flags |= FLAG_INSISTENT; //重复发出声音,直到用户响应此通知

* notification.flags |= FLAG_ONGOING_EVENT; //将此通知放到通知栏的”Ongoing”即”正在运行”组中

* notification.flags |= FLAG_NO_CLEAR; //表明在点击了通知栏中的”清除通知”后,此通知不清除,

* //经常与FLAG_ONGOING_EVENT一起使用

* notification.number = 1; //number字段表示此通知代表的当前事件数量,它将覆盖在状态栏图标的顶部

* //如果要使用此字段,必须从1开始

* notification.iconLevel = ; //

*/

 

示例代码如下:

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends ActionBarActivity {

private Button sendBtn, cancelBtn;
private Notification notification;
private NotificationManager nManager;
private Intent intent;
private PendingIntent pIntent;
// Notification的标示ID
private static final int ID = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//测试NotificationManager
sendBtn = (Button) this.findViewById(R.id.send);
cancelBtn = (Button) this.findViewById(R.id.cancel);

String service = NOTIFICATION_SERVICE;
nManager = (NotificationManager) this.getSystemService(service);

notification = new Notification();
String tickerText = “aibb测试Notifaction”; // 通知提示
// 显示时间
long when = System.currentTimeMillis();

notification.icon = R.drawable.ic_launcher;// 设置通知的图标
notification.tickerText = tickerText; // 显示在状态栏中的文字
notification.when = when; // 设置来通知时的时间
notification.sound = Uri.parse(“android.resource://com.sun.alex/raw/startsuccess”); // 自定义声音
notification.flags = Notification.FLAG_NO_CLEAR; // 点击清除按钮时就会清除消息通知,但是点击通知栏的通知时不会消失
notification.flags = Notification.FLAG_ONGOING_EVENT; // 点击清除按钮不会清除消息通知,可以用来表示在正在运行
notification.flags |= Notification.FLAG_AUTO_CANCEL; // 点击清除按钮或点击通知后会自动消失
// notification.flags |= Notification.FLAG_INSISTENT; // 一直进行,比如音乐一直播放,知道用户响应
notification.defaults = Notification.DEFAULT_SOUND; // 调用系统自带声音
notification.defaults = Notification.DEFAULT_SOUND;// 设置默认铃声
notification.defaults = Notification.DEFAULT_VIBRATE;// 设置默认震动
notification.defaults = Notification.DEFAULT_ALL; // 设置铃声震动
notification.defaults = Notification.DEFAULT_ALL; // 把所有的属性设置成默认
notification.ledOnMS = 300; //LED亮的时间

sendBtn.setOnClickListener(sendClickListener);
cancelBtn.setOnClickListener(cancelClickListener);
}

private OnClickListener sendClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
// 单击通知后会跳转到NotificationResult类
intent = new Intent(MainActivity.this,
NotificationResultActivity.class);
// 获取PendingIntent,点击时发送该Intent
pIntent = PendingIntent.getActivity(MainActivity.this, 0,
intent, 0);
// 设置通知的标题和内容
notification.setLatestEventInfo(MainActivity.this, “标题-测试”,
“内容-测试”, pIntent);
// 发出通知
nManager.notify(ID, notification);
}
};

private OnClickListener cancelClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
// 取消通知
nManager.cancel(ID);
}
};
}

 

android音频的播放

Android提供了常见的音频、视频的编码、解码机制。

Android支持MP3,WAV和3PG等格式的音频,通常使用MediaPlayer或者SoundPool来播放音频,

他们2者之间的区别是:

MediaPlayer资源占用较高,延迟时间较长,不支持多个音频文件同时播放,适合播放重复性

不高,时间长的音频;而SoundPool主要用于播放一些较短的声音片段,SoundPool还支持自行

设置声音的品质、音量、播放比率等参数。

1.播放应用的资源文件

一般应用的音频资源文件都放在工程的res/raw目录下,播放应用的资源文件只需要2步:

MediaPlayer mp = MediaPlayer.create(this, R.raw.startsucess);
// mp.prepare();  //在create 里已经调用过prepare方法了,重复调用会报IllegalStateException异常
mp.start();

2.播放应用的原始资源文件

原始资源文件一般放在assert文件夹下

AssetManager am = getAssets();
//打开指定的资源文件
AssetFileDescriptor afd = am.openFd(“music.wav”);
MediaPlayer mPlayer = new MediaPlayer();
mPlayer.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength());
//准备声音
mPlayer.prepare();
//播放声音
mPlayer.start();

3.播放外部存储器上的音频文件

MediaPlayer mPlayer = new MediaPlayer();

mPlayer.setDataSource(“/mnt/sdcard/music.wav”);

//准备声音
mPlayer.prepare();
//播放声音
mPlayer.start();

4.播放来自网络的音频文件

Uri uri = Uri.parse(“http://www.betterbing.com/music.wav”);

MediaPlayer mPlayer = new MediaPlayer();

mPlayer.setDataSource(this,uri);

//准备声音
mPlayer.prepare();
//播放声音
mPlayer.start();

5.使用SoundPool播放音频

private SoundPool soundpool;

//存放音频池中的key-value(音频ID)
HashMap<Integer,Integer> audioMap = new HashMap<Integer,Integer> ();

//初始化音频池

soundpool = new SoundPool(2,AudioManager.STREAM_SYSTEM,5);

audioMap.put(1, soundpool.load(this, R.raw.startsucess, 5));
audioMap.put(2,soundpool.load(this, R.raw.stopsuccess, 5));

//播放

soundpool.play(audioMap.get(1), 1, 1, 0, 0, 1);

 

NFC技术介绍及代码示例

最近项目中需要使用NFC(近场通信),在网上找了一些资料,摘抄汇总如下。

近距离无线通信(NFC)是一种简单的,非触控式的互联技术,可以让消费者简单直观的交换信息,访问内容和服务,在电子消费领域有着广泛的应用。NFC整合非接触式读卡器、非接触式智能卡和点对点(Peer-to-Peer)通信功能,为消费者开创全新便捷生活方式。

NFC的技术优势?

与蓝牙相比:NFC操作简单,配对迅速

与RFID相比:NFC适用范围广泛、可读可写,能直接集成在手机中

与红外线相比:数据传输较快、安全性高、能耗低

与二维码相比:识别迅速、信息类型多样

将来与移动支付相结合,势必简化支付的购买流程,重塑消费者的购物模式。

    1.NFC技术

NFC终端有三种工作模式:
1)主动模式,NFC终端作为一个读卡器,主动发出自己的射频场去识别和读/写别的NFC设备;
2)被动模式,NFC终端可以模拟成一个智能卡被读写,它只能在其它设备发出的射频场中被动响应;
3)双向模式,双方都主动发出射频场来建立点对点的通信。

NFC 在android架构中采用Service和Manager基本结构模型,通过Binder和Service通信,如图一所示android基于Binder的IPC的基本模型是基于会话的客户/服务器(C/S)架构的。Android使用内核模块Binder来中转各个进程之间的会话数据,它是一个字符驱动程序,主要通过IOCTL与用户空间的进程交换数据。一次会话是在一个代理Binder对象和服务Binder对象之间进行,可以在同一进程也可以在不同进程。会话是一个同步操作,由代理Binder对象发起请求,一直要等到服务Binder对象将回复传递给代理Binder对象才算完成。

NFC架构

 

2.Android所支持的NFC标签技术

在使用NFC标签和Android设备来进行工作的时候,使用的读写NFC标签上数据的主要格式是NDEF。当设备扫描到带有NDEF的数据时,Android会提供对消息解析的支持,并在可能的时候,会以NdefMessage对象的形式来发送它。但是,有些情况下,设备扫描到的NFC标签没有包含NDEF数据,或者该NDEF数据没有被映射到MIME类型或URI。在这些情况下,你需要打开跟NFC标签的通信,并用自己的协议(原始的字节形式)来读写它。Android用android.nfc.tech包提供了对这些情况的一般性支持,这个包在下表1中介绍。你能够使用getTechList()方法来判断NFC标签所支持的的技术,并且用android.nfc.tech提供的一个类来创建对应的TagTechnology对象。

 

表1.NFC标签所支持的技术

介绍

TagTechnology 所有的NFC标签技术类必须实现的接口。
NfcA 提供对NFC-A(ISO 14443-3A)属性和I/O操作的访问。
NfcB 提供对NFC-B(ISO 14443-3B)属性和I/O操作的访问。
NfcF 提供对NFC-F(ISO 6319-4)属性和I/O操作的访问。
NfcV 提供对NFC-V(ISO 15693)属性和I/O操作的访问。
IsoDep 提供对NFC-A(ISO 14443-4)属性和I/O操作的访问。
Ndef 提供对NDEF格式的NFC标签上的NDEF数据和操作的访问。
NdefFormatable 提供了对可以被NDEF格式化的NFC标签的格式化操作。

表2.可选的NFC标签所支持的技术

介绍

MifareClassic 如果Android设备支持MIFARE,那么它提供了对经典的MIFARE类型标签属性和I/O操作的访问。
MifareUltralight 如果Android设备支持MIFARE,那么它提供了对超薄的MIFARE类型标签属性和I/O操作的访问。

 

 

3.标签识别过程

在标签识别开始前,确认NFC设备使用正常,可获取NDEF设备。NFC HAL探测到有效距离范围内有标签存在,则读取数据,向NFC Service发送标签识别事件,NFC Service 广播NfcAdapter.ACTION_TAG_DISCOVERED Intent消息,应用程序通过接受该消息即可获取标签数据。 readTag

4.Tag发布系统

当android设备扫描到一个NFC tag,通用的行为是自动找最合适的Activity会处理这个tag Intent而不需要用户来选择哪个Activity来处理。因为设备扫描NFC tags是在很短的范围和时间,如果让用户选择的话,那就有可能需要移动设备,这样将会打断这个扫描过程。你应该开发你只处理需要处理的tags的Activity,以防止让用户选择使用哪个Activity来处理的情况。Android提供两个系统来帮助你正确的识别一个NFC tag是否是你的Activity想要处理的:Intent发布系统和前台Activity发布系统。

Intent发布系统检查所有Activities的intent filters,找出那些定义了可以处理此tag的Activity,如果有多个Activity都配置了处理同一个tag Intent,那么将使用Activity选择器来让用户选择使用哪个Activity。用户选择之后,将使用选择的Activity来处理此Intent.

前台发布系统允许一个Activity覆盖掉Intent发布系统而首先处理此tag Intent,这要求你将要处理Tag Intent的Activity运行在前台,这样当一个NFC tag被扫描到,系统先检测前台的Activity是否支持处理此Intent,如果支持,即将此Intent传给此Activity,如果不支持,则转到Intent发布系统。

使用Intent发布系统

Intent发布系统指定了3个intent有不同的优先级。通常当一个tag被检测到之后,Intent就被启动(start)了,这个启动遵循以下行为:

  • android.nfc.action.NDEF_DISCOVERED: 这个intent是在一个包含NDEF负载的tag被检测到时启动,这是最高优先级的intent, android系统不会让你指定一个Intent能处理所有的NFC数据类型,你必须在AndroidManifest.xml中指定与NFC tag对应的<data>元素,这样当扫描到的tag传过来的数据类型与你定义的相匹配时,你的Activity就会被调用。例如想处理一个包含plain text 的 NDEF_DISCOVERED intent ,你要按照如下定义AndroidManifest.xml file:

<intent-filter>
<action android:name=”android.nfc.action.NDEF_DISCOVERED”/>
<data android:mimeType=”text/plain” />
</intent-filter>

如果NDEF_DISCOVERED intent 已经被启动,TECH_DISCOVERED 和 TAG_DISCOVERED intents 将不会被启动。假如一个未知的tag或者不包含NDEF负载的tag被检测到,此Intent就不会被启动。

  • android.nfc.action.TECH_DISCOVERED: 如果 NDEF_DISCOVERED intent没启动或者没有一个Activity的filter检测NDEF_DISCOVERED ,并且此tag是已知的,那么此TECH_DISCOVERED Intent将会启动. TECH_DISCOVERED intent要求你在一个资源文件里(xml)里指定你要支持technologies列表。更多细节请看下面的Specifying tag technologies to handle.
  • android.nfc.action.TAG_DISCOVERED: 如果没有一个activity处理_DISCOVERED and TECH_DISCOVEREDintents或者tag被检测为未知的,那么此Intent将会被启动。

Specifying tag technologies to handle指定处理的technologies

假如你的Activity在AndroidManifest.xml文件里声明了处理android.nfc.action.TECH_DISCOVERED intent ,你必须创建一个Xml格式的资源文件,并加上你的activity支持的technologies到tech-list集合里。这样你的activity将被认作能处理这些tech-list的处理者,如果tag使用的technology属于你的定义的list里,你的Activity将接收此Intent。你可以用getTechList()来获得tag支持的technologies。

例如:如果一个tag被检测到支持MifareClassic, NdefFormatable, 和 NfcA,你的tech-list集合必须指定了其中的一项或者多项来保证你的Activity能处理此Intent。

下面是一个资源文件例子,定义了所有的technologies. 你可以根据需要删掉不需要的项,将此文件以任意名字+.xml保存到<project-root>/res/xml文件夹.

<resources xmlns:xliff=”urn:oasis:names:tc:xliff:document:1.2″>
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.NfcF</tech>
<tech>android.nfc.tech.NfcV</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NdefFormatable</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>

你也可以指定多个tech-list集合,每个集合都认做独立的。如果任何单个tech-list集合是getTechList()返回的technologies集合的子集,那么你的Activity将被认为匹配了。这个还提供’与’和’或’操作。下面的例子表示支持 NfcA和NDef的卡,或者支持NfcB和NDef的卡:

<resources xmlns:xliff=”urn:oasis:names:tc:xliff:document:1.2″>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>

<resources xmlns:xliff=”urn:oasis:names:tc:xliff:document:1.2″>
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>

在 AndroidManifest.xml 文件中, 指定这个tech-list资源文件的方法是在<activity> 元素中创建<meta-data>元素,例如下面例子:

<activity>

<intent-filter>
<action android:name=”android.nfc.action.TECH_DISCOVERED”/>
</intent-filter>

<meta-data android:name=”android.nfc.action.TECH_DISCOVERED”
android:resource=”@xml/nfc_tech_filter” />

</activity>

 

使用前台发布系统Using the foreground dispatch system

前台发布系统允许一个Activity 拦截一个tag Intent 获得最高优先级的处理,这种方式很容易使用和实现:

  1. 添加下列代码到Activity的onCreate() 方法里
  2. 创建一个 PendingIntent 对象, 这样Android系统就能在一个tag被检测到时定位到这个对象

PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, new Intent(this,getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

  1. 在Intent filters里声明你想要处理的Intent,一个tag被检测到时先检查前台发布系统,如果前台Activity符合Intent filter的要求,那么前台的Activity的将处理此Intent。如果不符合,前台发布系统将Intent转到Intent发布系统。如果指定了null的Intent filters,当任意tag被检测到时,你将收到TAG_DISCOVERED intent。因此请注意你应该只处理你想要的Intent。

IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType(“*/*”);    /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
}
catch (MalformedMimeTypeException e) {
throw new RuntimeException(“fail”, e);
}
intentFiltersArray = new IntentFilter[] {
ndef,
};

  1. 设置一个你程序要处理的Tag technologies的列表,调用Object.class.getName() 方法来获得你想要支持处理的technology类。

techListsArray = new String[][] { new String[] { NfcF.class.getName() } };

  1. 覆盖下面的方法来打开或关闭前台发布系统。比如onPause()和onResume()方法。必须在主线程里调用enableForegroundDispatch(Activity, PendingIntent, IntentFilter[], String[][]) 而且Activity在前台(可以在onResume()里调用来保证这点)。你也要覆盖onNewIntent回调来处理得到的NFC tag数据。

public void onPause() {
super.onPause();
mAdapter.disableForegroundDispatch(this);
}

public void onResume() {
super.onResume();
mAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray,techListsArray);
}

public void onNewIntent(Intent intent) {
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//do something with tagFromIntent
}

See the ForegroundDispatch sample from API Demos for the complete sample.

5.读一个NFC tag

当一个NFC tag靠近一个NFC设备,一个相应的Intent将在设备上被创建。然后通知合适的程序来处理此Intent
下面的方法处理TAG_DISCOVERED intent并且使用迭代器来获得包含在NDEF tag负载的数据

NdefMessage[] getNdefMessages(Intent intent) {
// Parse the intent
NdefMessage[] msgs = null;
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
Parcelable[] rawMsgs =intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsgs != null) {
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
}
}
else {
// Unknown tag type
byte[] empty = new byte[] {};
NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty,empty);
NdefMessage msg = new NdefMessage(new NdefRecord[] {record});
msgs = new NdefMessage[] {msg};
}
}
else {
Log.e(TAG, “Unknown intent ” + intent);
finish();
}
return msgs;
}

请记住NFC设备读到的数据是byte类型,所以你可能需要将他转成其他格式来呈现给用户。NFCDemo例子展示了怎样用com.example.android.nfc.record中的类来解析NDEF消息,比如纯文本和智慧型海报。

6.NFC tag

NFC tag写东西涉及到构造一个NDEF 消息和使用与tag匹配的Tag技术。下面的代码展示怎样写一个简单的文本到NdefFormatable tag

NdefFormatable tag = NdefFormatable.get(t);
Locale locale = Locale.US;
final byte[] langBytes = locale.getLanguage().getBytes(Charsets.US_ASCII);
String text = “Tag, you’re it!”;
final byte[] textBytes = text.getBytes(Charsets.UTF_8);
final int utfBit = 0;
final char status = (char) (utfBit + langBytes.length);
final byte[] data = Bytes.concat(new byte[] {(byte) status}, langBytes, textBytes);
NdefRecord record = NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, newbyte[0], data);
try {
NdefRecord[] records = {text};
NdefMessage message = new NdefMessage(records);
tag.connect();
tag.format(message);
}
catch (Exception e){
//do error handling
}

7.点对点的数据交换

前台推送技术支持简单点对点的数据交换,你可以用enableForegroundNdefPush(Activity, NdefMessage) 方法来打开此功能. 为了用这个功能:

  • 推送数据的Activity必须是前台Activity。
  • 你必须将你要发送的数据封装到NdefMessage对象里。
  • 接收推送数据的设备必须支持com.android.npp NDEF推送协议,这个对于Android设备是可选的

假如你的Activity打开了前台推送功能并且位于前台,这时标准的Intent发布系统是禁止的。然而,如果你的Activity允许前台发布系统,那么此时检测tag的功能仍然是可用的,不过只适用于前台发布系统。

要打开前台推送:

  1. 创建一个你要推送给其他NFC设备的包含NdefRecords的NdefMessage。
  2. 在你的Activity里实现onResume()onPause() 的回调来正确处理前台推送的生命周期。你必须在你的Activity位于前台并在主线程里调用enableForegroundNdefPush(Activity, NdefMessage) (可以在onResume()里调用来保证这点).

public void onResume() {
super.onResume();
if (mAdapter != null)
mAdapter.enableForegroundNdefPush(this, myNdefMessage);
}
public void onPause() {
super.onPause();
if (mAdapter != null)
mAdapter.disableForegroundNdefPush(this);
}

当Activity位于前台,你可以靠近另外一个NFC设备来推送数据。请参考例子ForegroundNdefPush来了解点对点数据交换。

8.代码示例

首先要在AndroidManifest.xml中声明如下配置信息:

使用<uses-permission>元素允许设备访问NFC硬件:

<uses-permission android:name=“android.permission.NFC” />  

使用<uses-sdk>元素设置最小SDK版本,笔者基于android 4.0环境,因此声明如下:

<uses-sdk android:minSdkVersion=”14″ android:targetSdkVersion=”14″ />

下面这项不一定需要,如果你希望你的软件可以在android market中显示有NFC硬件,可以使用<uses-feature>元素声明:

<uses-feature android:name=”android.hardware.nfc” android:required=”true” />

让一个Activity“监听”扫描NFC标签时的事件:

下面这个是最高级别的,扫描后会立马弹出此Activity:

<intent-filter>
<action android:name=”android.nfc.action.NDEF_DISCOVERED” ></action>
<category android:name=”android.intent.category.DEFAULT” ></category>
<data android:mimeType=”text/plain” ></data>
</intent-filter>

也可以通过以下方式监听:

<intent-filter>
<action android:name=”android.nfc.action.TECH_DISCOVERED” />
<action android:name=”android.nfc.action.TAG_DISCOVERED” />
<category android:name=”android.intent.category.DEFAULT”/>
</intent-filter>
<meta-data
android:name=”android.nfc.action.TECH_DISCOVERED”
android:resource=”@xml/nfc_tech_filter” />

在activity中初始化adapter:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc);
nfcTView=(TextView)findViewById(R.id.nfc_textview);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
// nfcTView.setText(“设备不支持NFC!”);
Toast.makeText(this, “设备不支持NFC!”, Toast.LENGTH_LONG).show();
finish();
return;
}
if (nfcAdapter!=null&&!nfcAdapter.isEnabled()) {
// nfcTView.setText(“请在系统设置中先启用NFC功能!”);
Toast.makeText(this, “请在系统设置中先启用NFC功能!”,Toast.LENGTH_LONG).show();
finish();
return;
}
}

@Override
protected void onResume() {
super.onResume();
NdefMessage[] mesArr = NFCUtils.getNdefMessages(getIntent());
if (NFCUtils.isNFCEntry(mesArr)) {
NFCUtils.sendNFCBroadcast(this,NFCConsts.NFC_OPERATE_TYPE_ENTRY);
}else if(NFCUtils.isNFCExit(mesArr)) {
NFCUtils.sendNFCBroadcast(this,NFCConsts.NFC_OPERATE_TYPE_EXIT);
}
finish();
}

下面开始解析:

public static NdefMessage[] getNdefMessages(Intent intent) {
// Parse the intent
NdefMessage[] msgs = null;
String action = intent.getAction();
//目前只监听ACTION_NDEF_DISCOVERED的action,NfcAdapter.ACTION_TAG_DISCOVERED先不管
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsgs != null) {
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
}
}
else {
// Unknown tag type
byte[] empty = new byte[] {};
NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
NdefMessage msg = new NdefMessage(new NdefRecord[] {record});
msgs = new NdefMessage[] {msg};
}
}
return msgs;
}

private static String parseNdefRecord(NdefRecord r) {
byte[] payload = r.getPayload();
try {
String textEncoding = ((payload[0] & 0200) == 0) ? “UTF-8” : “UTF-16”;
int languageCodeLength = payload[0] & 0077;
// String languageCode = new String(payload, 1, languageCodeLength, “US-ASCII”);
String text =new String(payload, languageCodeLength + 1,payload.length – languageCodeLength – 1, textEncoding);
return text;
} catch (UnsupportedEncodingException e) {
// should never happen unless we get a malformed tag.
//throw new IllegalArgumentException(e);
LogUtils.e(TAG, “NFC读取不支持此编码格式”, e);
}
return StringUtils.EMPTY;
}

 

android入门之文件系统操作

(一)获取总根
File[] fileList=File.listRoots();
//返回fileList.length为1
//fileList.getAbsolutePath()为”/”
//这就是系统的总根
File[] fileList=File.listRoots(); //返回fileList.length为1 //fileList.getAbsolutePath()为”/” //这就是系统的总根
(二)打开总根目录
File file=new File(“/”);
File[] fileList=file.listFiles();

//获取的目录中除了”/sdcard”和”/system”还有”/data”、”/cache”、”/dev”等
//Android的根目录并不像Symbian系统那样分为C盘、D盘、E盘等
//Android是基于Linux的,只有目录,无所谓盘符
File file=new File(“/”); File[] fileList=file.listFiles();

//获取的目录中除了”/sdcard”和”/system”还有”/data”、”/cache”、”/dev”等 //Android的根目录并不像Symbian系统那样分为C盘、D盘、E盘等 //Android是基于Linux的,只有目录,无所谓盘符
(三)获取系统存储根目录
File file=Environment.getRootDirectory();//File file=new File(“/system”);
File[] fileList=file.listFiles();
//这里说的系统仅仅指”/system”
//不包括外部存储的手机存储的范围远远大于所谓的系统存储
File file=Environment.getRootDirectory();

//File file=new File(“/system”); File[] fileList=file.listFiles();

//这里说的系统仅仅指”/system”

//不包括外部存储的手机存储的范围远远大于所谓的系统存储
(四)获取SD卡存储根目录
File file=Environment.getExternalStorageDirectory();//File file=new File(“/sdcard”);
File[] fileList=file.listFiles();
//要获取SD卡首先要确认SD卡是否装载
boolean is=Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
//如果true,则已装载
//如果false,则未装载
File file=Environment.getExternalStorageDirectory();//File file=new File(“/sdcard”); File[] fileList=file.listFiles(); //要获取SD卡首先要确认SD卡是否装载 boolean is=Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); //如果true,则已装载 //如果false,则未装载
(五)获取data根目录
File file=Environment.getDataDirectory();//File file=new File(“/data”);
File[] fileList=file.listFiles();
//由于data文件夹是android里一个非常重要的文件夹,所以一般权限是无法获取到文件的,即fileList.length返回为0
File file=Environment.getDataDirectory();//File file=new File(“/data”); File[] fileList=file.listFiles(); //由于data文件夹是android里一个非常重要的文件夹,所以一般权限是无法获取到文件的,即fileList.length返回为0
(六)获取私有文件路径
Context context=this;//首先,在Activity里获取context
File file=context.getFilesDir();
String path=file.getAbsolutePath();
//此处返回的路劲为/data/data/包/files,其中的包就是我们建立的主Activity所在的包
//我们可以看到这个路径也是在data文件夹下
//程序本身是可以对自己的私有文件进行操作
//程序中很多私有的数据会写入到私有文件路径下,这也是android为什么对data数据做保护的原因之一
Context context=this;//首先,在Activity里获取context File file=context.getFilesDir(); String path=file.getAbsolutePath(); //此处返回的路劲为/data/data/包/files,其中的包就是我们建立的主Activity所在的包 //我们可以看到这个路径也是在data文件夹下 //程序本身是可以对自己的私有文件进行操作 //程序中很多私有的数据会写入到私有文件路径下,这也是android为什么对data数据做保护的原因之一
(七)获取文件(夹)绝对路径、相对路劲、文件(夹)名、父目录
File file=……
String relativePath=file.getPath();//相对路径
String absolutePath=file.getAbsolutePath();//绝对路径
String fileName=file.getName();//文件(夹)名
String parentPath=file.getParent();//父目录
File file=…… String relativePath=file.getPath();//相对路径 String absolutePath=file.getAbsolutePath();//绝对路径 String fileName=file.getName();//文件(夹)名 String parentPath=file.getParent();//父目录
(八)列出文件夹下的所有文件和文件夹
File file=……
File[] fileList=file.listFiles();
File file=…… File[] fileList=file.listFiles();
(九)判断是文件还是文件夹
File file=……
boolean is=file.isDirectory();//true-是,false-否
File file=…… boolean is=file.isDirectory();//true-是,false
(十)判断文件(夹)是否存在
File file=……
boolean is=file.exists();//true-是,false-否
File file=…… boolean is=file.exists();//true-是,false-否
(十一)新建文件(夹)
File file=……
oolean is=file.isDirectory();//判断是否为文件夹
/*方法1*/
if(is){
String path=file.getAbsolutePath();
String name=”ABC”;//你要新建的文件夹名或者文件名
String pathx=path+name;
File filex=new File(pathx);
boolean is=filex.exists();//判断文件(夹)是否存在
if(!is){
filex.mkdir();//创建文件夹
//filex.createNewFile();//创建文件
}
/*方法2*/
if(is){
String path=file.getAbsolutePath();
String name=”test.txt”;//你要新建的文件夹名或者文件名
File filex=new File(path,name);//方法1和方法2的区别在于此
boolean is=filex.exists();//判断文件(夹)是否存在
if(!is){
filex.mkdir();//创建文件夹
//filex.createNewFile();//创建文件
}
File file=…… oolean is=file.isDirectory();//判断是否为文件夹 /*方法1*/ if(is){ String path=file.getAbsolutePath(); String name=”ABC”;//你要新建的文件夹名或者文件名 String pathx=path+name; File filex=new File(pathx); boolean is=filex.exists();//判断文件(夹)是否存在 if(!is){ filex.mkdir();//创建文件夹 //filex.createNewFile();//创建文件 } /*方法2*/ if(is){ String path=file.getAbsolutePath(); String name=”test.txt”;//你要新建的文件夹名或者文件名 File filex=new File(path,name);//方法1和方法2的区别在于此 boolean is=filex.exists();//判断文件(夹)是否存在 if(!is){ filex.mkdir();//创建文件夹 //filex.createNewFile();//创建文件 }
(十二)重命名文件(夹)
File file=……
String parentPath=file.getParent();
String newName=”name”;//重命名后的文件或者文件夹名
File filex=new File(parentPath,newName);//File filex=new File(parentPaht+newName)
file.renameTo(filex);
File file=…… String parentPath=file.getParent(); String newName=”name”;//重命名后的文件或者文件夹名 File filex=new File(parentPath,newName);//File filex=new File(parentPaht+newName) file.renameTo(filex)
(十三)删除文件(夹)
File file=……
file.delete();//立即删除
//file.deleteOnExit();//程序退出后删除,只有正常退出才会删除

(十四)获得所有可存储的路径,可能包括手机内置的SD卡和外置的SD卡

(不同手机Environment.getExternalStorageDirectory()获得路径不一样)

String starageStr = “”;

StorageManager sm = (StorageManager)context.getSystemService(Context.STORAGE_SERVICE);
try {
String[] paths = (String[]) sm.getClass().getMethod(“getVolumePaths”, null).invoke(sm, null);
for (int i = paths.length – 1; i > -1; i–) {
String status = (String) sm.getClass()
.getMethod(“getVolumeState”, String.class).invoke(sm, paths[i]);
if (status.equals(android.os.Environment.MEDIA_MOUNTED)) {
// SDCARD_PATH = paths[i];
// break;
starageStr+=paths[i];
}
}
} catch (Exception e) {}

(十五)下载缓存目录

File downloadCacheDir = Environment.getDownloadCacheDirectory();

(十六)检查设备的外存是否是内存模拟的

boolean isEmu = Environment.isExternalStorageEmulated();

(十七)检查外存是否是可拆卸的

boolean canRemoved = Environment.isExternalStorageRemovable();

后台线程如何正确发送消息(来自内网)

作者:xuding 转过来作为记录。

由于历史原因,我们有的APP依然使用webservice进行网络请求,我们为此封装了WebserviceUtils工具类,这个类具有明显的工具类特点——不与界面业务有任何耦合,这本应是个不错的设计。
然而我们长大后确发现,正因为不与界面耦合,WebserviceUtils在请求操作出错时,无法向界面传递消息,起初我们纠结万分,痛苦之中在研究了android Looper类的源码,于是我们自以为掌握了android消息队列的原理,参考:
http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html
于是有了如下代码:

01.try {

02. // 网络请求

03.} catch (Exception e) {

04. Log.e(TAG, “请求超时”, e);

05. Activity activity = ActivityStackManager.getCurrentActivity();

06. Looper.prepare();

07. SysUtils.closeHint();

08. Toast.makeText(activity, “网络通讯异常,请检查网络…”, Toast.LENGTH_LONG).show();

09. Looper.loop();

10.}
复制代码这样书写终于可以弹出提示了,然而悲剧的是,弹出提示紧接着,界面就失去响应(ANR),曾记否,那一年的冬天,大约也是这个季节,当时客户现场不断传来APP停止响应的呼吼,出错现象如同雪花在面前纷纷扬扬,而我们居然一片都抓不住啊
说正题,此处的问题很经典,需要从后台线程向界面上展示消息,Android展示消息只能在主线程里操作。Android中每个线程都有handler对象,消息框(Toast)、对话框等都是通过主线程的handler操作消息对列来做的。
上述代码中的Looper.prepare()、Looper.loop()是参考Handler.java源码来做的,那么为什么会ANR呢?
关键就是这里的loop(),和其他系统一样,android的消息队列也是通过死循环实现的:

01.for (;;) {

02. Message msg = queue.next(); // might block

03. if (msg == null) {

04. // No message indicates that the message queue is quitting.

05. return;

06. }

07. msg.target.dispatchMessage(msg);

08. // 。。。。。

09. msg.recycle();

10.}
复制代码低版本sdk中是while(){…),都一样,在后台线程loop()之后,因为这个死循环,该线程就一直处于阻塞状态了,于是这里的消息也就成了APP临终的遗言,紧接着ANR失去响应,然后就没有然后了。。。因为是死循环导致出错,所以APP捕获不到uncaughtException,也就无法输出错误日志了,于是更加让人不明所以,加大了排查错误的难度。
那么问题来了,后台线程究竟有没有办法向前台发消息呢?当然有,其实我们有了上述的知识,离胜利已经很近了:

01.new Handler(Looper.getMainLooper()).post(new Runnable() {

02. @Override

03. public void run() {

04. SysUtils.closeHint();

05. Toast.makeText(activity, “网络通讯异常,请检查网络…”, Toast.LENGTH_LONG).show();

06. }

07.});
复制代码非常优雅的通过Looper.getMainLooper()得到主线程handler,然后post!
非常简单吧!这个故事告诉我们一个道理,凡事一要求甚解,知其然还要知其所以然。我们有时候闭门造车容易将自己蒙蔽,吾尝终日而思,不如须臾之所学也。当我们脚踏实地,蓦然回首,就会发现,原来希望一直都在我们身边,只是我们被雪花迷失了方向。。。
万幸的是,这样的错误,我们以后的APP开发中很难再出现了,我们使用的annotations框架可以很优雅的处理各种对话框,从此Looper.loop()、AnsyncTask、Handler都成为了过往的种种。。。
其实Annotations处理主线程事件,也是通过上面的new Handler(Looper.getMainLooper()).post(…)来做的,欲知Annotations如何,且听下回分解~