IT降级论

几年前还在大学的时候看到了一片文章,说的是IT行业降级,当时年少的我没觉得这篇文章怎么样。但是当我工作了几年后,今天突然又想起了这篇文章,又细读一遍,现在觉得写的还是有几分道理的,现在贴出来,大家读后看看写地怎么样。

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

jiangjilun

几乎一年没有写博客了,说没时间那是借口,唯一的原因是,年纪越大越发觉自己肤浅。有些想法还没提笔,就发现很幼稚,就不敢发出来贻笑大方了。这次先给大家说个小故事:

从前有三个屌丝,聚在一起做网络,提供免费的网络服务,砸锅卖铁,通宵达旦,除了卖肾啥都做了。3年后终于做到了五百万用户,对于年轻人来说,能把五百万人玩弄于鼓掌之间,已经是很牛逼轰轰的事了,不过用户越多,成本越高,每年服务器、带宽租金、房租水电、广告运营等成本,已经达到了十七八万,屌丝们不得不面对一个终极问题:如何盈利?

屌丝们定了三盘沙县水饺,围着一箱子的冰啤酒开始计算:按照最近一月的登陆情况来看,四百万个账号已经不活跃了,真正有商业价值的只有一百万人,如 果开通xx功能,收点高级会员费,让其中1%的人升级为高级会员,每年付30块钱年费,那么每年收入就是100x1%x30=30万元!不错嘛, 扣除十七八万的运营成本,还剩毛利润12万,每个屌丝年底能分到4万大洋,如果按照打工者的算法,这三个人每人月薪3333元,木有奖金,木有津贴、木有任何福利,上班还得带自家的电脑。

尽管如此,屌丝们还是激动得咬了一口水饺:感谢苍天!我们终于要盈利啦!!!那一夜,人们看到三个发疯的屌丝在屋顶翩翩起舞。

韩寒说,中国人民是最有忍耐力的族群,一点好处就感激涕零。他一定不知道,IT创业界里的屌丝,才是这群傻逼中的战斗机。他们可以平静地忍受每年都持续亏钱,而且还能信心十足的对所有人说公司的状态非常好,如果有一天居然收支平衡了,他们会激动的趁夜难眠,比北朝鲜倒掉还开心。

本文开头的三个屌丝,其实是非常幸运的,至少能做到月薪3333元。大部分的屌丝在第一年做到几万用户的时候就会挂掉,原因众多,最主要要的是意志太弱,受不了最初的寂寞;意志稍微坚强点的会在第二年第三年慢慢挂掉,原因主要是资金断裂、团队分裂;能成功熬到第四年还没饿死、还没被口水淹死、还没被肠胃病颈椎病腰肌劳损折磨死的,甚至员工不减反增的,基本上属于神仙级别了。

我为什么要说三个屌丝的故事呢。首先是因为这是身边每天都在发生的故事,其次是因为感到可惜,IT界在我眼里一直是一个无比高级的职业,聚集着全球最聪明、最富有的人类精英。以IT创业界的青年们的智商,他们可以做成任何一件事情,包括改造银行制造汽车发射航天飞机 。结果这帮人却整天在蓬头垢面得为3k的月薪而挣扎,太悲催了。

为什么用悲催这个词? 如果一个人生下来就在山沟沟里,一辈子都没机会去见什么好东西,这不叫悲催,这只叫苦难;而如果一个人生出来有一个奇怪的特异功能:皮肤出来的汗水会凝结成昂贵的水晶,本来只靠出汗就能赚钱,结果这傻逼居然觉得出汗这个行为太低级,做手术把自己的汗腺全给切了,而且丝毫没有意识到他做了什么傻事,这才叫真的悲催。

我们IT界中的很多人,生下来就是有这个出汗成水晶的特异功能的,正是因为这种与众不同,这群人能混入牛逼的大学,整天打网游还能写出像样的毕业论文, 拿到学位,进外企,考CPA,做咨询、做证券分析,研究高分子材料,做电子商务,做云计算。。。一级一级的上升,直到有一天,发现身边的人里,已经没有一个不是CPA,不是咨询师,不是高级研究员了,身边的人全是业界精英,个个都超级强悍。在这个所谓的高级圈子里,自己并没有任何过人之处,只不过是just another analyst而已。在高级圈子里拼的头破血流,最后也只能混到给台湾人整理数据而已。莫然回首,发现当年的血气方刚、年少时的无限梦想,进化成了一身肥胖的赘肉。这个时候,有个旁观者说:升级到头了,该降级了

当一个社会疯狂鼓吹快节奏的时候,一定需要有人来宣扬慢生活;当全社会跟打了鸡血似的吹捧升级的时候,一定需要有人来说说降级论。

IT青年们喜欢打游戏,喜欢升级。他们的人生也和游戏一样,沉醉于不停的升级中,不仅喜欢升级自己手上的技术,把MySql改成MongoDB,把Apache升级为Nginx,在Mac上装UbuntuUbuntu里再装个虚拟机去跑Mac OS。。。IT青年们也喜欢升级自己的人生,从程序员升级到项目经理,再升级到技术总监或产品总监,再升级到合伙人。。。

在不断追求升级的过程中,所面临的一个很大事实是:当一个人从A刚升级到A+级的时候,其实这个人的能力层级依然只是A的层级,还未胜任A+的层级,他必须要到A+的后期,才可以胜任A+。就好像一个高中生,高考完之后,虽然理论上已经属于大学生了,但是他的实际能力依然只是高三毕业的水平,除非他全部pass了大一的期末考试。同样的道理,这个世界上有很多人的身份和称谓,都是在描述未来的自己,而不是现在的自己。当你从销售员升级为销售经理的时候,你自我感觉很好:我现在是销售经理了,但是这个时候 ,你并未通过公司对你作为销售经理这一年的工作成果的考核,你只是一个未来可能是合格的销售经理的前身。如果年终考核你失败了,那么这一年最准确的描述是:一个销售员,占了整整一年销售经理的位子,最后失败了。而且这一年一定会过的很累,因为通过考核的其他销售经理,才是真正胜任这个层级的人,跟一帮真正属于这个圈子的人厮杀,就好像拳击馆里当陪练的小角色,去和泰森比了一年的武,怎么可能不累呢?

当我07年进入互联网行业的时候,就是那个拳击馆里陪练的小角色,我被迫去跟全国各地的泰森比拼,结果累的半死。后来我开始反思最初的目标,为什么要在自己身上挂一个拳击高手的招牌,被那么多泰森追着打? 我把这块招牌卸了,找个完全没练武的人去比拼,不是更容易赢么?于是果断照做,去找了一个没人懂拳击的小乡村,做了纯英文的Tucia.com(需翻墙),只做国外的业务。在那个地方,作为一个知名武馆的拳击小陪练,我成了村子里拳击技术最高超的人,受人仰慕,还开武馆教人拳击,活的非常滋润,而且在教人拳击的过程中,自己的拳术也比以前提高了很多,发展出一套属于自己的拳法,我虽然进不了泰森们的大圈子,但他们也进不了我的小圈子。

关于圈子,有一个很赤裸裸的现实:不会是你进入圈子,只能是圈子进入你。很多人会四处找关系,帮我介绍给xxx吧,我想进入你们的圈子,这样的人是永远进不去这个圈子的,因为圈子的天性是,永远追求更高一个层级的人。而我们的大部分人,其实都在以低一级的属性,占着更高一级的位子,徘徊在更高一级的圈子边缘,与更高一级的人竞争,幻想着自己可以升级到那个圈子里去。也许永远进不去,悲催的努力一辈子;也许运气好,某一天真的进入这个圈子了,但那个时候又会有下一个目标,希望进入更高级的圈子,这是一场没有终点的战斗。永远的追求升级,永远的累。

有没有想过降级呢?

如果一个来自微软的高级工程师,辞职去一个养猪场做开放平台经理,那么他的到来不仅会让养猪圈感到无比荣幸,更是意味着,利用他在IT界训练出来的高效工作方式和逻辑思维能力,他可以掀起一场养猪行业的革命,使得20年后才会出现的人性、高效、开放、协作、健康的养殖方式提前到达。在这场革命中,他会活的非常有价值。这种价值,在原先的圈子里,是完全体验不到的,因为他此前的所有工作,只是在满身疮痍的windows系统上不停的打补丁,无论打多少都逃不开产品衰落、被人鄙视的命运。

很多人的命运,都像是上面那个微软工程师。只需要降级,就能创造更大的价值,也能获得更大的满足。那为什么不呢?为什么要死死抱着那个所谓的高级职业不放呢?

去年我曾犯贱去趟了移动互联网的浑水,做了个手机app,刚开始的时候感觉很高级,但很快,铺天盖地的竞争对手就出现了,我又发现自己陷入了07年一样的场景:作为一个小小陪练,我他妈的又被一帮泰森们给围住了。当泰森中的战斗机微信,变得无比牛逼之后,我就知道,战胜这群泰森是绝对不可能的事情了。于是我再次投靠了降级论,把自己从牛逼哄哄的移动互联网行业,降级到了一个被人不齿的低级项目:Tucia Baby

这个项目虽然是传统行业,但是我们基本上是按照互联网产品的思路去做的,除了拍摄需要来店里以外,其他一切,包括营销、预约、客服、后期、选片、取片、客户关系等,所有环节都放在网络上,尤其是微博(@tuciababy官网)。当然,最重要的是,作为一个脑残的果粉,我按照iPhone的做工去要求每一张作品,必须达到我们能力可以做到的最好水准,不计成本的最好水准,才允许送给客户。正式接客不到两个月时间,虽然还远未达到成功,但目前已做到每天都有客户订单,财务上已实现盈利,未来相信一定会比大部分app开发者更光明。(ps:我们没有请公务员吃饭喝酒泡桑拿,也没有塞钱给任何政府机关。当你的产品真的用心做到很好的时候,其实你不需要讨好任何人的。)

这个项目让我沉思了很久:07年我曾把一个纯纯的web2.0网站做到了alexa中国区前1000名(如有质疑,请查询2010年附近的tucia.com排名),结果一路亏损,到最后只剩下一个员工;11年我把那个纯纯的app做到苹果官方推荐免费榜第一位(如有质疑,请看点此看截图),那段时间每天四五千iPhone安装量,结果一路烧钱,到最后濒临关闭;而如今,我只需把自己从纯纯的互联网降级下来,做一些看起来有些低级的项目,居然就能立即实现收支平衡。

除此以外,我还发现一个现象,中国消费者在与奸商们的长期斗争中,已经培养出了一种非常苦B的品质:只要不被坑,他就谢天谢地。如果商家严格做到了承诺的每一件事情,客户就会感动的泪如泉涌。如果商家不仅做到了所有承诺的事情,还很贴心的提供了一些额外的服务(比如我们给每位客户赠送非常好吃的樱桃和进口巧克力作为甜点),那么客户就会激动的哭天喊地、奔走相告,推荐给他认识的每一个人。

其实这片肮脏的国土,就是上天赐予IT青年们的最好机会。

在一个不会练武的村子里,只要你会打两拳,你就是拳术最厉害的人;在一个没有服务意识、忽视产品质量的土地上,只要你用心做服务,用最高的标准去要求自己,你就会成为这块土地上最出色的商家;在一个没有现代管理意识,不懂网络、不懂微博、不懂用户体验、不懂口碑传播的粗犷社会里,你只需要把之前花在IT产品上的心思的10%拿过来用,就可以秒杀一切天朝对手。

所以,

IT青年们,当你在为网站的转化率苦苦思索的时候,当你在为app的活跃度辗转反侧的时候,当你在为融资计划苦苦哀求各界大佬引荐的时候,也许犯了一个错误,也许你们的脑子最值得闪光的地方,不是去悲催的IT界当炮灰,而应该是去按摩界、餐饮界、烧烤界、早餐界、理发界、送花界、纺织界、成人用品界、现代化养殖界、有机蔬果界、个人护理界、汽车修理界。。。。与IT界相比,这些行业的确无比低级,他们的老板连qq都会发音成抠抠,他们的员工一辈子都没用过Email;跟他们解释什么是SEO,什么是用户体验,什么是数据挖掘,他们会在听你说完之前就开枪自杀掉。正是因为如此,这些行业才是如此的不堪一击。正是因为如此,当智商高达147IT青年还在为3k薪水拼命、而智商不到50的烧烤店老板正坐在porsche里玩着前面那位青年开发的app的时候,我就忍不住仰望星空。

这些原始而纯粹的行业,正在等待IT精英们的降级,如同蒲公英一般的伞兵,在黑夜里从天而降,长驱直入,用最智慧的产品、最优质的服务拯救这些早就该死的行业,屌丝的生命将会绽放出银色的羽翼,无比丰满,无比性感。

最后注意,请珍惜生命,远离我的微博:@meditic

耍流氓之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;
}

 

研发人员为什么不愿意做那些“能实现的东西”(转载)

原文地址:http://www.xuding.info/index.php/logs/551

作为一线研发人员,总是听到经理提出这样的问题:这个东西能不能做?从技术角度出发,我们往往会回答:可以。然后经理又会紧接着问:给你XX的时间,能不能做不出来?这时候研发人员就会变得吞吞吐吐。这个时候经理总会诧异,为什么呢?你自己也承认可以做的,为什么真真正正做起来总是这么费劲?你说一天,我给你一天;你不够,我给你两天,为什么还是做不出来?

于是多次经历过这些事情的“老程序员”们,总是很谨慎很小心的回答经理提出的“能不能做”的问题。为什么“老程序员”会变得如此小心呢?因为坑啊,因为怕掉进“坑”里呀!一个经验再丰富的程序员也无法保证他们可以对未来要做功能毫无风险,因为我们无法预测未来,我们只能出于经验,按以往的经验或者别人的经验来对未来的事物进行评估。单纯从技术上来说,哪有什么东西是绝对无法实现的呢?因为理论上的可行,一线研发人员就需要为自己的承诺负责,无时无刻不顶着承担未知风险的压力,凭什么?王侯将相宁有种乎?

经理时常会说:XX功能很重要,客户上次表达了对这个功能的期望,我们必须要去将它实现。要知道,很少有哪个程序员会毫不留情的回答:做不到!他们更多时候宁可吞吞吐吐,也不会残忍的拒绝别人,这已经是程序员们最大的让步了,因为程序员有自己的底线,有自己的良知,程序员多么希望经理可以正视其中的风险,让他自己去做出判断。当程序员遮遮掩掩、避而不答的时候,其实他就已经处在万丈深渊的悬涯峭壁了,他们已经退无可退了,这个时候请不要逼迫程序员了,程序员可是会掉头发的。

当程序员敢于说”做不到“的时候,其实他们已经违背了自己的良知,不再是一个单纯的程序员了——而是一个勇敢的程序员。当程序员说做不到的时候,程序员就已经超越了自己的固有职责,不再是一个纯粹的程序员了,而是一个具有反抗精神的程序员。一个纯粹的程序员,应当是一个”无底线“的程序员,他们可以担然接受所有要求,毫不犹豫的去实现它;而经理应当固守自己的底线,什么该做,什么可以做,这才是经理的良知所在。然而大多数时候事实相反的,经理变得毫无底线,客户要做什么就做什么,领导要求什么就做什么;程序员反而在固守”底线“,这也做不了,那也做不了。

行业软件的确有行业软件的艰辛,我们知道,但我们是从什么时候开始在客户面前变得如此的毫无尊严、奴颜婢膝的呢?要知道当年苏联解体的时候,苏联的第一代核动力航母“乌里扬诺夫斯克”号完工了30%,一贫如洗的乌克兰病急乱投医,禁不住美国和荷兰两个大客户的高价”忽悠“,二话不说把“乌里扬诺夫斯克”号给拆掉了,美国轻轻松松解决掉了一个大威胁,这下世界上除了美国再也没有核力航母了;而乌克兰呢,到现在还是纷争不断,天天挨打。公司再大又怎样,高管再怎么英明神武,要是经理们追求业绩都这么不择手段,毫无底线,一切为了客户,信客户如神,后果真是不堪设想。当年里根提出了个”星球大战“计划,结果苏联”激动不已“,投入了大把资源,辛辛苦苦十年过去了,竹篮打水一场空啊!什么可以做,什么不可以做,善良的程序员真的不喜欢拒绝别人。程序员当然希望去相信自己的经理,没有人天生就喜欢背叛,但如果一个程序员辛辛苦苦奋斗了十年,结果发现自己做的东西是毫无意义的,谁会去为程序员逝去的青春负责任呢?

XX功能几天能做完?有时候听到经理提出工期要求时,程序员会怒不可遏:我就这点儿工资,我干我该干的活,别指望太多!这个时候领导就会摆出一大堆大道理来,要求程序员正视自己,好好工作。说实话,程序员再怎么加班加点做出来的成果,都是献给了公司,因为他是拿着工资在做。但如果他涉及的技术足够高级,那他完全可以把利用这个技术去接私活,这个技术单位时间内可以产更大的价值,因为他知道把这个技术应用在一些变态的需求中无法发挥其中的价值。让一个优秀的技术在一个糟糕的业务需求中腐烂变质,这绝不是一个尊重技术的程序员该干的事!为什么行业软件研发人员会嫌弃自己工资低?归根到底就是因为行业软件产出设入比太低,为产出不高?因为客户要求的功能本身就是一踏糊涂,我们把大量的精力花在了繁重的流程和变更上,在本应实现自动化的流程中硬生生地加入了某些人为因素,来实现所谓的”定制功能“;在本应由人为控制的业务上,非要引入自动化控制,实现所谓的亮点。

该做什么,不该做什么,程序员无法决定什么。程序员为什么要跳槽?因为程序员发现干别的可以产生更大的价值,这个价值最大的反应就是工资,这是很现实的问题。对于公司来说,如果不懂得拒绝客户,那么永远也无法摆拖”奴颜婢膝“的命运,永远也谈不上发展,只能看看户脸色行事,永远只能为了微薄的利润,“低调做人”。要摆脱“低调做人”的命运,必须要有所割舍,发展新业务,干该干的事,这也是很现实的问题。

Apache Shiro—–Java权限安全框架

先记录,后学习!

学习链接:http://www.9iu.org/2013/12/10/springrain1-shiro.html

http://www.ibm.com/developerworks/cn/java/j-lo-shiro/

一、什么是Shiro
Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能:

  • 认证 – 用户身份识别,常被称为用户“登录”;
  • 授权 – 访问控制;
  • 密码加密 – 保护或隐藏数据防止被偷窥;
  • 会话管理 – 每用户相关的时间敏感的状态。

对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro要简单的多。

二、Shiro的架构介绍
首先,来了解一下Shiro的三个核心组件:Subject, SecurityManager 和 Realms. 如下图:

Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

Shiro完整架构图:


除前文所讲Subject、SecurityManager 、Realm三个核心组件外,Shiro主要组件还包括:
Authenticator :认证就是核实用户身份的过程。这个过程的常见例子是大家都熟悉的“用户/密码”组合。多数用户在登录软件系统时,通常提供自己的用户名(当事人)和支持他们的密码(证书)。如果存储在系统中的密码(或密码表示)与用户提供的匹配,他们就被认为通过认证。
Authorizer :授权实质上就是访问控制 – 控制用户能够访问应用中的哪些内容,比如资源、Web页面等等。
SessionManager :在安全框架领域,Apache Shiro提供了一些独特的东西:可在任何应用或架构层一致地使用Session API。即,Shiro为任何应用提供了一个会话编程范式 – 从小型后台独立应用到大型集群Web应用。这意味着,那些希望使用会话的应用开发者,不必被迫使用Servlet或EJB容器了。或者,如果正在使用这些容器,开发者现在也可以选择使用在任何层统一一致的会话API,取代Servlet或EJB机制。
CacheManager :对Shiro的其他组件提供缓存支持。

原文链接:http://kdboy.iteye.com/blog/1154644

Shiro与Spring集成:

Shiro的组件都是JavaBean/POJO式的组件,所以非常容易使用Spring进行组件管理,可以非常方便的从ini配置迁移到Spring进行管理,且支持JavaSE应用及Web应用的集成。

 

在示例之前,需要导入shiro-spring及spring-context依赖,具体请参考pom.xml。

spring-beans.xml配置文件提供了基础组件如DataSource、DAO、Service组件的配置。

 

JavaSE应用

 

spring-shiro.xml提供了普通JavaSE独立应用的Spring配置:

Java代码

  1. <!– 缓存管理器 使用Ehcache实现 –>
  2. <bean id=”cacheManager” class=”org.apache.shiro.cache.ehcache.EhCacheManager”>
  3.     <property name=”cacheManagerConfigFile” value=”classpath:ehcache.xml”/>
  4. </bean>
  5. <!– 凭证匹配器 –>
  6. <bean id=”credentialsMatcher” class=”
  7. com.github.zhangkaitao.shiro.chapter12.credentials.RetryLimitHashedCredentialsMatcher”>
  8.     <constructor-arg ref=”cacheManager”/>
  9.     <property name=”hashAlgorithmName” value=”md5″/>
  10.     <property name=”hashIterations” value=”2″/>
  11.     <property name=”storedCredentialsHexEncoded” value=”true”/>
  12. </bean>
  13. <!– Realm实现 –>
  14. <bean id=”userRealm” class=”com.github.zhangkaitao.shiro.chapter12.realm.UserRealm”>
  15.     <property name=”userService” ref=”userService”/>
  16.     <property name=”credentialsMatcher” ref=”credentialsMatcher”/>
  17.     <property name=”cachingEnabled” value=”true”/>
  18.     <property name=”authenticationCachingEnabled” value=”true”/>
  19.     <property name=”authenticationCacheName” value=”authenticationCache”/>
  20.     <property name=”authorizationCachingEnabled” value=”true”/>
  21.     <property name=”authorizationCacheName” value=”authorizationCache”/>
  22. </bean>
  23. <!– 会话ID生成器 –>
  24. <bean id=”sessionIdGenerator”
  25. class=”org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator”/>
  26. <!– 会话DAO –>
  27. <bean id=”sessionDAO”
  28. class=”org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO”>
  29.     <property name=”activeSessionsCacheName” value=”shiro-activeSessionCache”/>
  30.     <property name=”sessionIdGenerator” ref=”sessionIdGenerator”/>
  31. </bean>
  32. <!– 会话验证调度器 –>
  33. <bean id=”sessionValidationScheduler”
  34. class=”org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler”>
  35.     <property name=”sessionValidationInterval” value=”1800000″/>
  36.     <property name=”sessionManager” ref=”sessionManager”/>
  37. </bean>
  38. <!– 会话管理器 –>
  39. <bean id=”sessionManager” class=”org.apache.shiro.session.mgt.DefaultSessionManager”>
  40.     <property name=”globalSessionTimeout” value=”1800000″/>
  41.     <property name=”deleteInvalidSessions” value=”true”/>
  42.     <property name=”sessionValidationSchedulerEnabled” value=”true”/>
  43.    <property name=”sessionValidationScheduler” ref=”sessionValidationScheduler”/>
  44.     <property name=”sessionDAO” ref=”sessionDAO”/>
  45. </bean>
  46. <!– 安全管理器 –>
  47. <bean id=”securityManager” class=”org.apache.shiro.mgt.DefaultSecurityManager”>
  48.     <property name=”realms”>
  49.         <list><ref bean=”userRealm”/></list>
  50.     </property>
  51.     <property name=”sessionManager” ref=”sessionManager”/>
  52.     <property name=”cacheManager” ref=”cacheManager”/>
  53. </bean>
  54. <!– 相当于调用SecurityUtils.setSecurityManager(securityManager) –>
  55. <bean class=”org.springframework.beans.factory.config.MethodInvokingFactoryBean”>
  56. <property name=”staticMethod”
  57. value=”org.apache.shiro.SecurityUtils.setSecurityManager”/>
  58.     <property name=”arguments” ref=”securityManager”/>
  59. </bean>
  60. <!– Shiro生命周期处理器–>
  61. <bean id=”lifecycleBeanPostProcessor”
  62. class=”org.apache.shiro.spring.LifecycleBeanPostProcessor”/>

可以看出,只要把之前的ini配置翻译为此处的spring xml配置方式即可,无须多解释。LifecycleBeanPostProcessor用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调,在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调。如UserRealm就实现了Initializable,而DefaultSecurityManager实现了Destroyable。具体可以查看它们的继承关系。

 

测试用例请参考com.github.zhangkaitao.shiro.chapter12.ShiroTest。

 

Web应用

Web应用和普通JavaSE应用的某些配置是类似的,此处只提供一些不一样的配置,详细配置可以参考spring-shiro-web.xml。

Java代码

  1. <!– 会话Cookie模板 –>
  2. <bean id=”sessionIdCookie” class=”org.apache.shiro.web.servlet.SimpleCookie”>
  3.     <constructor-arg value=”sid”/>
  4.     <property name=”httpOnly” value=”true”/>
  5.     <property name=”maxAge” value=”180000″/>
  6. </bean>
  7. <!– 会话管理器 –>
  8. <bean id=”sessionManager”
  9. class=”org.apache.shiro.web.session.mgt.DefaultWebSessionManager”>
  10.     <property name=”globalSessionTimeout” value=”1800000″/>
  11.     <property name=”deleteInvalidSessions” value=”true”/>
  12.     <property name=”sessionValidationSchedulerEnabled” value=”true”/>
  13.     <property name=”sessionValidationScheduler” ref=”sessionValidationScheduler”/>
  14.     <property name=”sessionDAO” ref=”sessionDAO”/>
  15.     <property name=”sessionIdCookieEnabled” value=”true”/>
  16.     <property name=”sessionIdCookie” ref=”sessionIdCookie”/>
  17. </bean>
  18. <!– 安全管理器 –>
  19. <bean id=”securityManager” class=”org.apache.shiro.web.mgt.DefaultWebSecurityManager”>
  20. <property name=”realm” ref=”userRealm”/>
  21.     <property name=”sessionManager” ref=”sessionManager”/>
  22.     <property name=”cacheManager” ref=”cacheManager”/>
  23. </bean>

1、sessionIdCookie是用于生产Session ID Cookie的模板;

2、会话管理器使用用于web环境的DefaultWebSessionManager;

3、安全管理器使用用于web环境的DefaultWebSecurityManager。

 

Java代码

  1. <!– 基于Form表单的身份验证过滤器 –>
  2. <bean id=”formAuthenticationFilter”
  3. class=”org.apache.shiro.web.filter.authc.FormAuthenticationFilter”>
  4.     <property name=”usernameParam” value=”username”/>
  5.     <property name=”passwordParam” value=”password”/>
  6.     <property name=”loginUrl” value=”/login.jsp”/>
  7. </bean>
  8. <!– Shiro的Web过滤器 –>
  9. <bean id=”shiroFilter” class=”org.apache.shiro.spring.web.ShiroFilterFactoryBean”>
  10.     <property name=”securityManager” ref=”securityManager”/>
  11.     <property name=”loginUrl” value=”/login.jsp”/>
  12.     <property name=”unauthorizedUrl” value=”/unauthorized.jsp”/>
  13.     <property name=”filters”>
  14.         <util:map>
  15.             <entry key=”authc” value-ref=”formAuthenticationFilter”/>
  16.         </util:map>
  17.     </property>
  18.     <property name=”filterChainDefinitions”>
  19.         <value>
  20.             /index.jsp = anon
  21.             /unauthorized.jsp = anon
  22.             /login.jsp = authc
  23.             /logout = logout
  24.             /** = user
  25.         </value>
  26.     </property>
  27. </bean>

1、formAuthenticationFilter为基于Form表单的身份验证过滤器;此处可以再添加自己的Filter bean定义;

2、shiroFilter:此处使用ShiroFilterFactoryBean来创建ShiroFilter过滤器;filters属性用于定义自己的过滤器,即ini配置中的[filters]部分;filterChainDefinitions用于声明url和filter的关系,即ini配置中的[urls]部分。

 

接着需要在web.xml中进行如下配置:

Java代码

  1. <context-param>
  2.     <param-name>contextConfigLocation</param-name>
  3.     <param-value>
  4.         classpath:spring-beans.xml,
  5.         classpath:spring-shiro-web.xml
  6.     </param-value>
  7. </context-param>
  8. <listener>
  9.    <listener-class>
  10. org.springframework.web.context.ContextLoaderListener
  11. </listener-class>
  12. </listener>

通过ContextLoaderListener加载contextConfigLocation指定的Spring配置文件。

 

Java代码

  1. <filter>
  2.     <filter-name>shiroFilter</filter-name>
  3.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  4.     <init-param>
  5.         <param-name>targetFilterLifecycle</param-name>
  6.         <param-value>true</param-value>
  7.     </init-param>
  8. </filter>
  9. <filter-mapping>
  10.     <filter-name>shiroFilter</filter-name>
  11.     <url-pattern>/*</url-pattern>
  12. </filter-mapping>

DelegatingFilterProxy会自动到Spring容器中查找名字为shiroFilter的bean并把filter请求交给它处理。

 

其他配置请参考源代码。

 

Shiro权限注解

Shiro提供了相应的注解用于权限控制,如果使用这些注解就需要使用AOP的功能来进行判断,如Spring AOP;Shiro提供了Spring AOP集成用于权限注解的解析和验证。

为了测试,此处使用了Spring MVC来测试Shiro注解,当然Shiro注解不仅仅可以在web环境使用,在独立的JavaSE中也是可以用的,此处只是以web为例了。

 

在spring-mvc.xml配置文件添加Shiro Spring AOP权限注解的支持:

Java代码

  1. <aop:config proxy-target-class=”true”></aop:config>
  2. <bean class=”
  3. org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor”>
  4.     <property name=”securityManager” ref=”securityManager”/>
  5. </bean>

如上配置用于开启Shiro Spring AOP权限注解的支持;<aop:config proxy-target-class=”true”>表示代理类。

 

接着就可以在相应的控制器(AnnotationController)中使用如下方式进行注解:

Java代码

  1. @RequiresRoles(“admin”)
  2. @RequestMapping(“/hello2”)
  3. public String hello2() {
  4.     return “success”;
  5. }

访问hello2方法的前提是当前用户有admin角色。

 

当验证失败,其会抛出UnauthorizedException异常,此时可以使用Spring的ExceptionHandler(DefaultExceptionHandler)来进行拦截处理:

Java代码

  1. @ExceptionHandler({UnauthorizedException.class})
  2. @ResponseStatus(HttpStatus.UNAUTHORIZED)
  3. public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) {
  4.     ModelAndView mv = new ModelAndView();
  5.     mv.addObject(“exception”, e);
  6.     mv.setViewName(“unauthorized”);
  7.     return mv;
  8. }

如果集成Struts2,需要注意《Shiro+Struts2+Spring3 加上@RequiresPermissions 后@Autowired失效》问题:

http://jinnianshilongnian.iteye.com/blog/1850425

 

权限注解

Java代码

  1. @RequiresAuthentication

表示当前Subject已经通过login进行了身份验证;即Subject. isAuthenticated()返回true。

 

Java代码

  1. @RequiresUser

表示当前Subject已经身份验证或者通过记住我登录的。

 

Java代码

  1. @RequiresGuest

表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。

 

Java代码

  1. @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)

表示当前Subject需要角色admin和user。

 

Java代码

  1. @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)

表示当前Subject需要权限user:a或user:b。

 

 

链接:http://jinnianshilongnian.iteye.com/blog/2029717