MobileSafe Day05

Day05##

5.1复习#
5.2.根据号码查询号码归属地数据库操作# **

前边将手机防盗模块功能全部实现了,开始做“查询号码归属地”模块



根据号码查询号码归属地一般用在拨打电话时,腾讯管家,360 金山卫士都会弹出对话框提醒

	例:133 3333 3333   河北秦皇岛

		第一位是:1

		第二位是:34578

		第三位是:0-9

	中国:前三位可确定号码运营商:131联通,133电信,134移动

		 第三位开始往后四位,可确定号码归属地:陕西

		 号码前7位就可确定号码归属地:陕西移动

		 这个由好心网友做的,不是移动联通电信

	原理:将号码和归属地存到数据库,根据号码查归属地,淘宝10元可买数据库,做手机安全软件需要



	android中一般能不用数据库就不用数据库,因为android都是即点即先操作,点了按钮要立刻显示,数据库是耗时操作,做再好总有速度上的缺陷,不同机型上显示快慢也不一样

	

	这个数据库准备好了,上课资料小米数据库文件夹,有个address.db,telocation.db

	用数据库帮助软件SQLiteExport打开telocation.db,里边有个mob_location看下



	这个数据库不好:

		在location这,上海电信有很多相同数据,没必要显示多条,产生冗余数据,占用了数据库空间

	工作中对数据库优化:

		根据号码查询归属地,data1和data2通过外键关联,先查询出外键

				select outkey from data1 where id=1300001

		运行这个sql语句,查询出外键是496,data2中的id和data1中的outkey关联,所以id来个496

				select location from data2 where id=496

		运行一下,出来个location为江苏常州联通,这样就可以查询出号码归属地

				查询时用了两次数据库,浪费资源,写一个关联查询:

				select location from data2 where id=(select outkey from data1 where id=1300001)



		运行打印location是:江苏常州联通,只需要查询一次,比较节省资源



		这个操作在android中算最复杂了,android中数据库主要功能是存储,最简单的增删改查

		都是andorid系统设定好的,比如requery,把表名写一下,查询字段以参数形式传一下就好



		像上边这种sql语句写法很少见,开发时也就总共遇到过3次

		把上边三条sql语句写到笔记中:

			select outkey from data1 where id=1300001

			select location from data2 where id=496

			select location from data2 where id=(select outkey from data1 where id=1300001)

5.3拷贝数据库# ***

1.将数据库address.db导入项目assets目录

  assets目录下的资源在程序编译时不会去编译,这个目录中的文件在R.java中不会生成标识,程序运行时直接生成到内存中去使用,数据库万年不动,数据库不需要编译,编译也没有用



  assets目录下的数据库不能直接打开,必须将数据库拷贝到手机目录中,通过打开手机目录中的数据库来实现查询数据库操作,但是可以打开assets目录下存放的html文件

	DDMS中的data/databases中的某个数据库删掉,重新打开金山卫士直接又生成一个,这是splash界面作用讲的拷贝数据库功能

  拷贝数据库是将assets目录下的数据库拷贝到工程的DDMS中的data/database或files目录下,一般是在files目录下,files目录下的路径比较好拿,再去打开数据库时,不是打开assets目录下的数据库,而是打开存放在DDMS下的data/files目录下的数据库

2.拷贝assets目录下的数据库到手机目录中

	市面软件拷贝数据库都是在刚打开应用的欢迎界面做的

 	SplashActivity的onCreate中写一个方法copyDb();,在copyDb()去拷贝数据库

 	数据库存放在assets目录下,先通过getAssets()获取assets管理者AssetManager

3.通过assets管理者打开数据库,返回读出流流信息InputSteam是一个字节流,异常捕获下:

4.new一个写入流FileOutputSteam

	读出流流信息InputStream有了,再来个写入流FileOutputSteam(file)

	参数file:new File(dir,name)

	参数dir:需要一个文件dir存放目录,getCacheDir()意思是获取缓存路径

	如把数据库保存到缓存路径,清理缓存时会清掉

	还有一个方法getFileDir() 获取文件路径

	参数name:文件名称,写成“address.db”,后缀.db一定要加:

5.进行读写操作并关流

  	因为是字节流,所以创建字节数组,来个1024,作为缓冲区,再来个int len = -1;

  	接下来可以while循环进行读写操作

  	如果不等于-1,可以进行写入操作

		out.write(buffer,byteOffset,byteCount),将缓存区写入,从0开始,到len

  	最后关闭输入输出流,在finally关流



	关流也有异常,如不想捕获异常可用XUtils的IOUtils,用IOUtils关流需要把out,in设置成一个值:

		IOUtils源码:

			开源框架中找到xutils,library/src/com/lidroid/xutils/util/IOUtils.java

			打开发现它里边也是close操作,我们不用捕获异常是因为它已经捕获了

			它进行了一下封装,才起名叫closeable,closeable把java中的流操作进行了封装,不管是字节流,字符流,全都可以采用这种方式去关闭

6.添加是否拷贝数据库判断

	第一次打开欢迎页时进行拷贝数据库操作,当再次打开时手机目录中已有这个数据库了,就没有必要再执行拷贝数据库操作

	判断file是否存在,不存在就去拷贝数据库



	拷贝数据库完整流程如下:(我们是写在SplashActivity中的)



	private void copyDb() {



		File file = new File(getFilesDir(), "address.db");

		//5.判断file是否存在,存在不去拷贝

		if (!file.exists()) {

			//1.获取assets管理者

			AssetManager assets = getAssets();

			InputStream in = null;

			FileOutputStream out = null;

			try {

				//2.通过assets管理者打开数据库

				in = assets.open("address.db");

				//getCacheDir() : 获取缓存路径,getFilesDir():获取文件的路径

				out = new FileOutputStream(file);

				//3.读写操作

				//缓冲区

				byte[] b = new byte[1024];

				int len = -1;

				while((len = in.read(b)) != -1){

					out.write(b, 0, len);

				}

			} catch (IOException e) {

				e.printStackTrace();

			}finally{//有没有异常都会执行

				//out.close();

				//in.close();

				//4.关流

				IOUtils.closeQuietly(out);

				IOUtils.closeQuietly(in);

			}

		}

	}



运行程序,打开DDMS中data/data/cn.itcast.mobilesafexian02,里边有个files目录,它里边有个address.db

说明拷贝数据库操作已完成

5.4打开数据库,查询号码归属地# ***

接下来就可以打开拷贝到手机目录file中的数据库address.db,并查询数据了



基础班时一般新建XXXOpenHelper继承自SQLiteOpenHelper,实现构造方法,onCreate中创建命名结构:

		public class XXXOpenHelper extends SQLiteHelper{

			public XXXOpener(){

				super(context,"info.db",null,1);

			}

			@Override

			public void onCreate(SQLiteDatabase db){

			}

	    }

	这个XXXOpenHelper是创建数据库时使用,数据库address.db已经有了,不用创建数据库,所以不用写它

1.创建AddressDao

	数据库address.db我们有,那创建一个数据库操作的AddressDao.java,放在.db或者.db.dao包中,我们放在.db包

	AddressDao中创建方法queryAddress,用来查询号码归属地

	查询号码归属地是根据号码来查询,并返回一个号码归属地:

2.在queryAddress中打开数据库

	用SQLiteDatabase调用openDatabase(path,factory,flags),打开数据库

	参数path:数据库路径,参数factory:游标工厂参数,flags:权限标签

	数据库路径写什么

		在SplashActivity中的copyDb()中拷贝数据库时,有个file文件,拿过来之后用file去获取绝对路径

	游标factory不用,写成null

	第三个参数指定权限标签,有读写权限,只读权限,只是查询只读OPEN_READONLY就可以 

3.在queryAddress中查询数据库

	可以用query(),rawquery()两种方式查询数据库

		1)query()方式(没办法实现sql语句关联查询)

		database.query(table,columns,selection,selectionArgs,groupBy,having,orderBy);

		参数table:表名,columns:字段,selection查询的条件,selectionArgs条件参数,groupBy是否分组,orderBy:排序

		2)rawQuery方式:(可以用sql语句关联查询)

		database.rawQuery(sql,selectionArgs);

		参数sql:sql语句 

		参数selectionArgs:查询条件的参数

		

		演示查询时用的sql语句,query()方式没办法实现sql语句关联查询,这里选用第二种rawQuery查询



		参数sql:把上边实现关联查询的sql语句拿过来

			select location from data2 where id=(select outkey from data1 where id=1300001

			我们是根据这个id去查询的,所以应该写成id=?,即:

			select location from data2 where id=(select outkey from data1 where id=?

		参数selectionArgs:new一个String[],放个num,在查询时id是一个7位的号码对,输入时一般输入的是11位,需要截取一下,subString截取字符串包含头不包含尾,实际上它是0-6,刚好七位



		根据号码对去查询归属地,一个号码对应一个归属地,用while浪费,来个if判断,当cursor.moveToNext()时,用cursor.getString(0)去获取数据,返回一个String类型数据,把它声明出来并初始化为null,起名为location并返回

	

	打开数据库,根据号码查询号码归属地完整代码如下:



			public class AddressDao{

		

			public String queryAddress(String num,Context context){

				String location = "";

				File file = new File(context.getFilesDir(), "address.db");

				//1.打开数据库

				//path : 数据库的路径

				//factory : 游标工厂

				//flags : 权限标签

				//file.getAbsolutePath() : 获取绝对路径

				SQLiteDatabase database = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);

				//2.查询号码归属地,substring:包含头不包含尾  0-6

				Cursor cursor = database.rawQuery("select location from data2 where id=(select outkey from data1 where id=?)", new String[]{num.substring(0, 7)});

				//3.解析cursor

				if (cursor.moveToNext()) {

					location = cursor.getString(0);

				}

				return location;

				}

			}



	凡是和数据库,业务相关操作,都需要单元测试

		在TestMobilesafexian02项目的cn.itcast.mobilesafexian02.test包下,重新创建单元测试类TestAddress.java,继承自AndroidTestCase,创建一个testAddress()

			方法中new一个AddressDao,调用queryAddress(num,context)方法

			参数num:来个13121646499

			参数context:来个getContext,返回一个String类型归属地数据queryAddress,进行判空并输出

			也可以做一个判空处理:

		public class TestAddress extends AndroidTestCase{

			public void testAddress(){

				AdressDao addressDao = new AddressDao();

				String queryAddress = addressDao.queryAddress("13121646499",getContext());

				if(!TextUtils.isEmpty(queryAddress)){

					System.out.println(queryAddress);

				}

			}

		}

	

	选中testAddress方法,右键Run As,LogCat中输出:北京联通	

5.5查询号码归属地界面# **

查询号码归属地操作已实现,只进行了下单元测试,接下来给这个操作来个界面

在高级工具来个按钮,点击跳转查询号码界面可输入号码,再来个按钮点击查询可查询出归属地



	HomeActivity给高级工具条目增加点击事件,通过intent跳转到AToolsActivity

		case 7://高级工具

				Intent intent7 = new Intent(HomeActivity.this,AToolsActivity.class);

				startActivity(intent7);

				break;



	将ToolsActivity创建出来,清单文件注册,回到AToolsActivity重写onCreate

	onCreate中通过setContentView加载布局activity_atool.xml

	运行程序,点击高级工具条目就进入到高级工具界面了

	在高级工具界面activity_atool.xml添加按钮跳转到查询号码归属地界面

	这个查询号码归属地button可添加状态选择器,模仿选择联系人按钮,在drawable文件夹下创建状态选择器



		selector_contact_button.xml

		

		<?xml version="1.0" encoding="utf-8"?>

		<selector xmlns:android="http://schemas.android.com/apk/res/android">

		    <item android:state_pressed="true"

		          android:drawable="@drawable/btn_green_pressed" /> <!-- pressed 按下-->

		    <item android:drawable="@drawable/btn_green_normal" /> <!-- default 默认图片-->

		</selector>

	回到AToolActivity中实现点击事件address,跳转到查询号码归属地界面AddressActivity:

		public void address(View v){

			//跳转到查询号码归属地界面

			Intent intent = new Intent(this,AddressActivity.class);

			startActivity(intent);

		}



	创建AddressActivity

		在布局文件activity_address.xml中来个“请输入查询的号码”的输入框,接着来个“查询”button,再来个textView显示号码归属地,到AddressActivity中加载布局

			运行点击“查询号码归属地”按钮,直接跳转到“查询号码归属地”页面了

			要的效果是在EditText输入电话号码,点击“查询”,将号码归属地显示到textView上

			给"查询"按钮增加点击事件queryaddress,查询之前下边的textView不显示,把textView默认文字删除掉就看不出来了



	接下来在AddressActivity中实现点击事件queryaddress()

	在onCreate中把EidtText,textView初始化出来

	点击事件queryaddress()中getText()获取输入的内容,判断输入的号码是否为空	

	为空,提醒用户“请输入要查询的号码”,不为空,根据输入的号码查询归属地

	查询号码归属地要拿到addressDao,用queryAddress(num,context)来查询号码归属地,返回一个String类型的号码归属地



	对返回的号码归属地queryAddress判空,不为空显示到TextView上



			public class AddressActivity extends Activity {

		

				private EditText et_address_queryphone;

			    private TextView tv_address_queryaddress;

		

				@Override

				protected void onCreate(Bundle savedInstanceState) {

				super.onCreate(savedInstanceState);

					setContentView(R.layout.activity_address);

				et_address_queryphone = (EditText) findViewById(R.id.et_address_queryphone);

				tv_address_queryaddress = (TextView) findViewById(R.id.tv_address_queryaddress);

				}

		

				public void queryaddress(View v){

				//1.获取输入的内容

				String phone = et_address_queryphone.getText().toString().trim();

				//2.判断号码是否为空

				if (!TextUtils.isEmpty(phone)) {

					//3.查询号码归属地

					String queryAddress = addressDao.queryAddress(phone, getApplicationContext());

					//4.判断查询的归属地是否为空

					if (!TextUtils.isEmpty(queryAddress)) {

						tv_address_queryaddress.setText(queryAddress);

					}else{

					Toast.makeText(getApplicationContext(), "请输入要查询的号码", 0).show();

					return;

					}

				}

			}



	运行程序,输入号码为13121646499,点击查询,显示:北京联通

5.6电话号码查询逻辑处理# ***

输入123456,点击查询,直接奔溃了,原因:

	Cause by:java.lang.StringIndexOutOfBoundsException:length:6,regionStart=7;

	角标越界问题,它说长度是6,截取时截取的是7,在AddressDao中的截取字符串处,截取的是6位,肯定没法截取第7位,所以报错



1.用正则表达式匹配身份证

	身份证前17位都可以是0-9

		写成[0-9]或者\d都可以,即: ^[0-9]$

		它要来个17次,即: ^[0-9]{17}$

		那后边还要来个0-9或者x:^[0-9]{17}[0-9x]$,这个只是简单的匹配了下18位数字和字母

2.用正则表达式匹配电话号码

		打开资料中的正则表达式语法.html

			看到是以^开始,以$符号结尾

			用[xyz]表示:字符集合,匹配所包含的任意一个字符

			[a-z],表示字符范围

			\d,表示匹配一个数字字符,等价于[0-9]

			{n},n表示是一个非负整数,匹配确定的n次

	

			第一位肯定是1

			第二位有可能是34578之一,34578之一用[34578]表达

			第三位0-9都可以,用[0123456789]表达

	

			这样写可以,但是有点low

			正则表达式语法文档中有个[a-z],表示字符范围

			所以可以将[0123456789]写成[0-9]

			\d,表示匹配一个数字字符,等价于[0-9],所以还可以写成\d

			后边还有9位也可以0-9,那后边还要写9个\d,这样写又太low

			文档中上边还有一个{n},n表示是一个非负整数,匹配确定的n次,既然加上第三位后边总共有9位都是可以0-9,那我们就写成:/d{9} 



 	用正则表达式匹配出来的电话号码:   ^1[34578]\d{9}$

	

	在AddressDao的queryAddress中的第2步查询号码归属地上边判断

	num.matches(regularExpression),参数regularExpression:将电话号码的正则表达式拷贝过来:

				if (num.matches("^1[34578]\d{9}$")) {

				}



	这样写会报错,因为\是转义字符,应该再加个\表示是转义字符:

				if (num.matches("^1[34578]\d{9}$")) {

				}

	

	如果匹配这个格式才去查询号码归属地,最后关闭database

	匹配号码是否11位,110不是11位但也是电话,所以当不是11位时else对num长度判断:



		if (num.matches("^1[34578]\d{9}$")) {

			//2.查询号码归属地,substring:包含头不包含尾  0-6

			Cursor cursor = database.rawQuery("select location from data2 where id=(select outkey from data1 where id=?)", new String[]{num.substring(0, 7)});

			//3.解析cursor

			if (cursor.moveToNext()) {

				location = cursor.getString(0);

			}

			cursor.close();

		}else{

			switch (num.length()) {

			case 3://110 120  119

				location = "特殊电话";

				break;

			case 4://5556   5554

				location = "虚拟电话";

			   	break;

			case 5://10086 10010  10000   95588

				location = "客服电话";

				break;

			case 7://本地电话

			case 8://本地电话

				location = "本地电话";

				break;

			default://长途电话  010 1234567 10位  010 12345678  11位  0372 12345678  12位

				//startsWith : 是否以哪个字符开头

				if (num.length() >= 10 && num.startsWith("0")) {

					//长途电话

					//根据区号查询相应归属地,3位和4位

					//3位

					//截取区号

					String result = num.substring(1, 3);//010 -> 10

					//根据区号查看归属地

					Cursor cursor = database.rawQuery("select location from data2 where area=?", new String[]{result});

					//解析cursor

					if (cursor.moveToNext()) {

						location = cursor.getString(0);

						location = location.substring(0, location.length()-2);

						cursor.close();

					}else{

						//当查询三位查询不出来,直接查询四位

						//复用前面三位区号使用的变量,可以减少内存的使用

						result = num.substring(1, 4);

						cursor = database.rawQuery("select location from data2 where area=?", new String[]{result});

						if (cursor.moveToNext()) {

							location = cursor.getString(0);

							location = location.substring(0, location.length()-2);

							cursor.close();

							}

						}

					}

					break;

				}

			}



		在default中如果num.length()大于等于10并且num以0开头,判定为长途电话

		因为要有3位或4位,先来判断3位,num截取区号,数据库中第一位0都省略没写,那区号第一位都是0

		从1开始,到3结束,比如010,截取后就变成了10这种情况

				它会返回一个result: String result = num.substring(1,3);

		区号有了,根据区号查询归属地:

				database.rawQuery(sql,selectionArgs);

		这里需要获取区号的sql,用区号查询号码归属地的sql语句也写下:

				select location from data2 where area=10

		运行这段语句,查询出来了,但有3个,先把它查询出来,再解决出现3个的问题,把sql语句拷贝过来:

				String result = num.substring(1,3);

				Cursor cursor = database.rawQuery("select location from data2 where area=?",new String[]{result});

		接下来解析cursor,如果cursor.moveToNext(),等于下一个就直接取出来放到location里边:

				if (cursor.moveToNext()) {

					location = cursor.getString(0);

					cursor.close();

				}



	这是3位情况,4位情况是当3位情况查询不出时,就去查询4位,这个操作和三位操作相似,但是注意将3改成4,这样写会将原来的三位和原来三位的cursor都覆盖掉:

				else{

					//当查询三位查询不出来,直接查询四位

					//复用前面三位区号使用的变量,可以减少内存使用

					result = num.substring(1, 4);

					cursor = database.rawQuery("select location from data2 where area=?", new String[]{result});

					if (cursor.moveToNext()) {

						location = cursor.getString(0);

						location = location.substring(0, location.length()-2);

						cursor.close();

					}

				}

	前边3位时都定义了一个String类型result,和Cursor类型cursor

	查询3位时如果没查询出来,那String result,Cursor cursor都没用了

	查询4位时,既然和3位是一样操作,就直接拿查询3位时定义的String result,Cursor cursor类型用

	这样可减少占用空间和资源,查询4位时的result和cursor是把查询3位时的变量复用了一下,可减少内存使用,因为使用时使用了同一个内存地址



	运行看下,输入556,点击查询不会再奔溃了,出来归属地是虚拟电话,不会再出现奔溃



	本地电话里边有一些规则,匹配起来就需要一些算法,这个先不写了

	查询1234567是本地电话,输入长途电话0101234567,点击查询出来北京电信

	sql语句查询时有3个结果,分别是:北京电信,北京移动,北京联通

	根据区号查询出来的号码,比如长途电话,有可能是北京电信,也有可能是北京移动,北京联通

	都是北京,直接截取字符串,显示北京,截取时不是从0-2了,不要想着只是两个字,看到数据库中还有四个字的“云南临沧”,倒着来,让location长度减去2,截取的都是最后两个字

	把这两个字截取掉就相当于从0开始截取,截取长度是总长度减去后边两个字长度:

		location = location.substring(0,location.length()-2);



	运行程序,输入01012345678,点击查询只显示北京,这样就没有问题了

5.7动态显示号码归属地的操作# ***

上边已实现输入电话号码,点击查询按钮可查询出号码归属地,用户体验不好,现在是用户输入完号码后不点查询直接显示

输入完号码后直接显示归属地,监听输入框输入状态变化的操作 addTextChangedListener(watcher):

	onTextChanged(CharSequence s, int start, int before, int count)

	参数CharSequence:ctrl+t查看继承树,String类型直接实现了CharSequence,它是一个接口,String类型实现了CharSequence接口,就是一个String类型

	参数int start,表示新文本从哪个位置开始输入

	参数int before,表示旧文本长度

	参数int count,表示新文本替换旧文本的字符个数



	参数连起来:新文本从哪个位置开始替换旧文本中哪个个数,旧文本指的是旧文本长度,从新文本哪个位置开始去替换旧文本长度里的字符个数

	后边三个参数一般用不到,要用的是第一个参数,toString()是获取输入内容,指新内容

	用addressDao查询号码归属地queryAddress(num,context),参数num是输入的号码,得到查询出来的结果queryAddress,判断结果不为空直接给textView赋值即可



		//监听输入框输入状态变化的操作

		et_address_queryphone.addTextChangedListener(new TextWatcher() {

		//当输入内容改变完成时调用

		@Override

		public void onTextChanged(CharSequence s, int start, int before, int count) {

			//获取输入新内容

			String phone = s.toString();

			//查询号码归属地

			String queryAddress = addressDao.queryAddress(phone, getApplicationContext());

			if (!TextUtils.isEmpty(queryAddress)) {

				tv_address_queryaddress.setText(queryAddress);

			}

		}

		//输入之前调用

		@Override

		public void beforeTextChanged(CharSequence s, int start, int count,

				int after) {

			// TODO Auto-generated method stub

		}

		//输入之后调用

		@Override

		public void afterTextChanged(Editable s) {

			// TODO Auto-generated method stub

		}

	});



运行程序,输入110查询出来归属地为特殊电话了



onTextChanged意思是只要输入一个,就表示输入完成了,就会调用onTextChanged



onTextChanged哪里最常用

	商城项目或小商品的搜索,点击搜索,下边会弹出搜索列表,就是通过这种形式实现的,在这里边判断一下输入的内容,获取一下查询一下数据库或网络再显示出来,都是在onTextChanged中做的

5.7抖动的效果# **

APIDemos是android各种控件效果,打开Views/Animation/shake,只要往EditText输入内容就会有抖动效果



EditText抖动效果:

1.拷贝抖动代码到AddressActivity点击事件方法queryaddress的号码为空,提示“请输入要查询的号码”下方:



		public void queryaddress(View v){

			//1.获取输入的内容

			String phone = et_address_queryphone.getText().toString().trim();

			//2.判断号码是否为空

			if (!TextUtils.isEmpty(phone)) {

				//3.查询号码归属地

				String queryAddress = addressDao.queryAddress(phone, getApplicationContext());

				//4.判断查询的归属地是否为空

				if (!TextUtils.isEmpty(queryAddress)) {

					tv_address_queryaddress.setText(queryAddress);

				}

			}else{

				Toast.makeText(getApplicationContext(), "请输入要查询的号码", 0).show();



				//抖动

				Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);

	//			//代码实现动画插入器

	//			shake.setInterpolator(new Interpolator() {

	//				

	//				@Override

	//				public float getInterpolation(float x) {

	//					return 0;//根据x的值去获取y的值  y=x^2  y=x-k

	//				}

	//			});

				et_address_queryphone.startAnimation(shake);

				//振动

				Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);

				vibrator.vibrate(100);//振动100毫秒

				return;

			}

		}



		它还需要一个shake.xml文件,也拷贝到anim文件夹下:

2.拷贝shake.xml

		<?xml version="1.0" encoding="utf-8"?>

		<!-- interpolator : 动画插入器 -->

		<translate xmlns:android="http://schemas.android.com/apk/res/android"

	    android:duration="1000"

	    android:fromXDelta="0"

	    android:interpolator="@anim/cycle_7"

	    android:toXDelta="10" />

	发现shake.xml报错,它需要一个cycle_7.xml

	interpolator是动画插入器,在模拟器APIDemos找到interpators效果,就是一些特殊动画效果,有由慢到快,由快到慢,想使用这些特殊效果,可以查下文本,到源码中直接查找相应代码,既然少了一个cycle_7.xml,也把它拷贝到anim下



3.拷贝cycle_7.xml

		<?xml version="1.0" encoding="utf-8"?>

		<!-- cycles : 执行频率 -->

		<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"

	    android:cycles="7" />



	这个是动画插入器,cycles是执行频率,来回的形式

	运行程序,查看效果,实现了EditText抖动效果



	再来看一下拷贝到AdressActivity中抖动代码,Animation是用AnimationUtils调用loadAnimation获取动画,用动画插入器设置了动画插入效果,返回shake后startAnimation运行动画



	动画中还有一个操作:

		shake.setInterpolator(new Interpolator() {

			@Override

			public float getInterpolation(float x) {

				return 0;//根据x的值去获取y的值  y=x^2  y=x-k

			}

		});



	动画插入器不只可用cycle_7.xml布局实现,也可用代码实现,用代码实现动画插入器操作一般不用,因为要涉及数学上的语言,比如正弦余弦等算法,一般搞开发的,如果不是数学学得特别好都不用这玩意,用也是用布局文件形式



	它里边有getInterpolation方法,参数float input,input相当于x值,最后return返回的就是根据x值获取y值,比如y=x^2  y=x-k这些情况,这个其实和正弦余弦有关系,比如画一个坐标系,里边画一条y=x线,y=x-k线,y=x^2,这个就是动画插入器效果

5.8让手机振动的效果# **

例子工程“振动”,包名为com.example.vibrate

MainActivity获取振动管理者Vibrator,都是通过getSystemService获取



	Vibrator中vibrate(milliseconds),milliseconds是振动持续时间,设置成Long.MAX_VALUE,手机会振动到废掉:

	有些手机这样设置完后,还是只振动一次,比如小米

	vibrator还有一个方法vibrate(pattern,repeat),参数pattern:振动频率,repeat:是否重复 -1不重复,非-1重复



	pattern是long类型数组,new一个long类型数组,来个50l,再来个20l,再来个50l,再来个20l,第二个参数来个-1,这时振动会先按照50频率振动一下再按照20频率振动一下,再按50频率振动一下,再按20频率震动一下:



	public class MainActivity extends Activity{



		@Override

		portected void onCreate(Bundle savedInstanceState){

			super.onCreate(savedInstanceState);

			setContentView(R.layout.activity_main);



			//获取振动的管理者

			Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);

			//振动

			vibrator.vibrate(Long.MAX_VALUE);

			vibrator.vibrate(new long[]{50l,20l,50l,20l}, -1);

			}

		}



		市面很多软件会用到这个操作,振动需要添加权限,打开清单文件选择Permissions,添加android.permissions.VIBRATE

		在浏览器中输入www.javaapk.com,应用汇搜索按摩器,效果图上有个一档二档档位就是按上边代码设置

	

		添加振动频率时它是long类型数组,所以在添加不同频率时,给它后边加个l,表示是long类型



		运行振动项目,模拟器没有振动效果,用真机



	

		将振动效果移植到手机卫士



		AddressActivity输入内容为空时,不止可抖动还可振动:



				else{

					Toast.makeText(getApplicationContext(), "请输入要查询的号码", 0).show();

					//抖动

					Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);

		//			//代码去实现动画插入器

		//			shake.setInterpolator(new Interpolator() {

		//				

		//				@Override

		//				public float getInterpolation(float x) {

		//					return 0;//根据x的值去获取y的值  y=x^2  y=x-k

		//				}

		//			});

					et_address_queryphone.startAnimation(shake);

	

					//振动

					Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);

					vibrator.vibrate(100);//振动100毫秒

					return;

				}

	

	添加振动权限:

		<uses-permission android:name="android.permission.VIBRATE"/>

 

	运行程序,进入高级工具,点击查询按钮,EditText来回抖动了,提醒“请输入要查询的号码”,振动功能也实现了,只是因为模拟器没法查看振动效果

5.10 来电显示号码归属地#

查询号码归属地操作已做完,让用户点进这个界面,输入号码才自动显示归属地,这种功能一般用户不会去用

市面安全软件拨打电话,来电时,会在拨打电话,来电界面显示归属地,挂断消失



	监听来电显示状态,监听状态都用广播接收者,这里有些例外,可以用TelephoneManager监听电话的来电显示状态

	

	监听是时时刻刻都去监听,那就要写在服务中,开启服务,不管什么时候来电,都能显示号码归属地



	已经在service包下写过一个GPSService服务

	在service包下新建AddressService服务继承自service,紧接着到清单文件配置:



		<service android:name="cn.itcast.mobilesafexian02.service.AddressService" >

        </service>



	AddressService中重写onCreate,onCreate中就可以去获取电话管理者telephonyManager,获取管理者都是通过getSystemService(name)获取,把它声明成成员变量,方便其他地方使用:



	telephonyManager中有个listen方法,电话状态的监听器,把监听创建出来继承自PhoneStateListener

	

	listen第二个参数events是一个事件表示要监听哪个事件,要监听的是电话来电状态

	既然是电话状态,PhoneStatelistener中有个LISTEN_CALL_STATE是监听电话状态

	此外还有一个LISTEN_NONE,NONE表示不监听任何状态,要监听的是电话的状态



	开启服务时要去监听电话状态,关闭时不去监听任何状态

	onDestroy中设置下listen,第一个参数也需要MyPhoneStateListener,onCreate的listen的第一个参数不能那么去写,把它拿出来声明成成员变量,onCreate和onDestory就都可以使用myPhoneStateListener了,在onDestroy中服务关闭不监听任何状态了,将onDestory中listen第二个参数改为LISTEN_NONE



	到MyPhoneStateListener处理,选中LISTEN_CALL_STATE,标识出来你应该要实现的方法是onCallStateChanged

	在onCallStateChanged中就可去实现电话状态,参数state是电话状态,incomingNumber是来电号码

	CALL_STATE_IDLE是空闲的状态

	CALL_STATE_RINGING是响铃的状态

	CALL_STATE_OFFHOOK是通话的状态



	响铃时显示号码归属地,需要去查询号码归属地了,把AddressDao在onCreate中new出来申明成成员变量调用queryAddress(num,context),参数num是incomingNumber,返回归属地queryAddress,对返回的归属地判空,不为空就用Toast显示号码归属地:

	

	public class AddressService extends Service{

		private MyPhoneStateListener myPhoneStateListener;

			@Override

			public void onCreate() {

				super.onCreate();	

				addressDao = new AddressDao();

				//1.获取电话的管理者

				telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);

				myPhoneStateListener = new MyPhoneStateListener();

				telephonyManager.listen(myPhoneStateListener,PhoneStateListener.LISTEN_CALL_STATE)

			}

		}

			

		private class MyPhoneStateListener extends PhoneStatelistener{

			@Override

			public void onCallStateChanged(int state, String incomingNumber) {

				super.onCallStateChanged(state, incomingNumber);

				//4.判断电话的状态

				switch (state) {

				case TelephonyManager.CALL_STATE_IDLE://空闲的状态

					break;

				case TelephonyManager.CALL_STATE_RINGING://响铃的状态

					//5.查询显示号码归属地

					String queryAddress = addressDao.queryAddress(incomingNumber, getApplicationContext());

					if (!TextUtils.isEmpty(queryAddress)) {

						Toast.makeText(getApplicationContext(), queryAddress, 0).show();

					}

					break;

				case TelephonyManager.CALL_STATE_OFFHOOK://通话的状态

					break;

			}

		}		

	}

		@Override

		public void onDestroy() {

			super.onDestroy();

			//6.取消监听状态

			telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_NONE);//不去监听任何状态

		}

		

	之所以用Toast是因为Toast有跨界面显示效果

	服务已经写完了,要时时刻刻监听,到SplashActivity的onCreate中开启服务(调用):

	

		// 开启服务

		 Intent intent = new Intent(this,AddressService.class);

		 startService(intent);



	运行程序,AddressService服务有没有开启,打开settings/Apps/找到手机卫士西安2号,看到有一个服务开启,而且是AddressService,那现在打开DDMS拨打下电话,Incoming number为:18888888888,点击call,模拟器来电话18888888888了,toast提示:“北京移动”



	总结“来电显示号码归属地”步骤:

		1.创建一个addressService,清单文件配置

		2.通过TelephoneManager实现监听来电显示操作

		3.在spalshactivity中开启服务

5.11 自定义Toast# **

使用Toast显示来电归属地,样式不太好,自定义一下toast样式



	toast中View.inflate和LayoutInflater.from(context)的区别:

		View.inflate(打开View.class源码)里边是一个LayoutInflater.from(context);

		LayoutInflater.from(context)相当于还是通过cotext.getSystemService 

		它两是一样的效果,一般我喜欢用的是View.inflate,因为简单

	

	到AddressService中,在响铃状态处调用showToast(),在下边创建showToast

	获取windowmanager调用addView(view,params)添加view,既然是view就来个简单的TextView:

	

	给textView设置text,这个文本显示的就是查询的号码归属地,在showToast的参数上给它传过来,在AddressService中的"5.查询显示号码归属地"处调用的showToast(),此时也需要把它改为showToast(queryAddress):



	紧接着再给它设置字体大小20,textColor为RED,把textView设置给addView()



	addView还有一个参数params,是一个LayoutParams,到源码Toast.class中的mWM.addView(mView,mParams)中找到TN(),里边就有mParams,把这段全部拷贝过来:



		public void showToast(String queryAddress){

			// 获取windowmanager

			windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);

			TextView textView = new TextView(getApplicationContext);

			textView.setText(queryAddress);

			textView.setTextSize(20);

			textView.setTextColor(Color.RED);

	

			// 设置toast的属性,LayoutParams就代表控件的属性,你的控件在那个父控件上显示,你就要获取那个父控件的LayoutParams,表示你这个控件符合父控件的属性规则

			// LayoutParams:设置属性

			params = new WindowManager.LayoutParams();

			params.height = WindowManager.LayoutParams.WRAP_CONTENT;// 高度包裹内容

			params.width = WindowManager.LayoutParams.WRAP_CONTENT;// 宽度包裹内容

			params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE// 不可获取焦点

					// | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE //不可触摸

					| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; // 保持屏幕常亮

			params.format = PixelFormat.TRANSLUCENT; // 透明

			params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;// 控件的类型,显示窗体上的类型,toast天生没有触摸事件

			// 设置控件的显示位置, | 表示前后两个属性都有效果,两个相对应的属性,默认是左边和上边

			params.gravity = Gravity.LEFT | Gravity.TOP;

			params.x = sp.getInt("x", 100);// x表示的不是坐标,而是距离边框的距离,根据Gravity来设置的,LEFT就是距离左边框的距离,ringht就是距离右边框的距离

			params.y = sp.getInt("y", 100);// y和x是相同的含义,根据top或者bottom来时设置距离顶部或者底部的距离

	

	

			windowManager.addView(textView, params);

		}



		windowManager.addView(textView, params);意思是先把属性params设置给textview,然后将textview添加到窗体上,上边params属性height为包裹内容,width为包裹内容,flags标签为不可获取焦点,不可触摸

		保持屏幕常量,透明的,toast都是透明, 看到后边是个黑色,其实是设置了背景,控件类型type尤为重要

		这里类型指的是显示到窗体上的类型,是TYPE_TOAST类型,只有设置成吐司才能像吐司那样去显示

		实际上吐司能显示也是因为它设置了TYPE_TOAST属性,这个就是设置TextView的属性

	

		这里是WindowManager.LayoutParams,你的控件在哪个父控件上显示就要获取哪个父控件的LayoutParams,表示这个控件符合父控件属性规则,因为每个控件里边设置的LayoutParams属性都不一样,这样才能显示出来



		运行程序,打开DDMS点击call拨打电话显示出北京移动了,它没有像吐司一样消失,但当挂断电话后还不消失

		再次拨打电话时,又会出现“北京移动”,而且和前边没有消失的那个重叠了,它好像镶嵌到屏幕上一样



	

		再次查看Toast.class源码,它有一个handleHide()隐藏,它做了mWM.removeView(mView);操作

		刚才addView是让显示,那removeView是让移除



		回到AddressService写个hideToast是用来隐藏Toast的,就是调用了windowManager中的一个removeView()



		还需要判断下windowManager和textView是否为空,为空就没必要remove:



		最后需要将windowManager和textView置为空, 这是为了避免用户直接在这里调用隐藏Toast的方法,再移除的话就会报空指针异常,同时也为下次显示toast做准备,隐藏Toast之后将windowManager,textView置为空,当再次去使用Toast调用showToast时,得到windowmanger,并会再次new一个TextView出来:



				public void hideToast(){

						//隐藏toast

						//判断windowManager和textview是否为空,如果为空就不去remove

						if (windowManager != null && textView != null) {

							windowManager.removeView(textView);

							//避免用户再次使用隐藏报出view不在window的异常,同时也是为下次显示toast做准备

							windowManager = null;

							textView = null;

						}

					}



		挂断电话之后就可以去隐藏toast了,挂断电话就是空闲状态,即来到AddressService.java中的CALL_STATE_IDLE中调用hideToast即可



		运行程序,拨打电话,显示归属地为“北京联通”,挂断电话后,归属地直接就消除了



	自定义Toast具体流程整理如下:



	显示toast:



		1.获取一个WindowManager

			windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);

		2.创建一个textview

			textView = new TextView(getApplicationContext());

			textView.setText(queryAddress);

			textView.setTextSize(100);

			textView.setTextColor(Color.RED);

		3.设置toast的属性

			//设置toast的属性,LayoutParams就代表控件的属性,你的控件在那个父控件上显示,你就要获取那个父控件的LayoutParams,表示你这个控件符合父控件的属性规则

			 WindowManager.LayoutParams params = new WindowManager.LayoutParams();

			 params.height = WindowManager.LayoutParams.WRAP_CONTENT;//高度包裹内容

	         params.width = WindowManager.LayoutParams.WRAP_CONTENT;//宽度包裹内容

	         params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE//不可获取焦点

	                 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE //不可触摸

	                 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; // 保持屏幕常亮

	         params.format = PixelFormat.TRANSLUCENT;  // 透明

	         params.type = WindowManager.LayoutParams.TYPE_TOAST;//控件的类型,显示窗体上的类型

		4.将textivew添加到window

			windowManager.addView(textView, params);//先把属性设置给textview,然后将textview添加到窗体上

	

			隐藏toast:

		

				/**
  • 隐藏toast的操作
     */
     public void hideToast(){
     //隐藏toast
     //判断windowManager和textview是否为空,如果为空就不去remove
     if (windowManager != null && textView != null) {
     windowManager.removeView(textView);
     //避免用户再次使用隐藏报出view不在window的异常,同时也是为下次显示toast做准备
     windowManager = null;
     textView = null;
     }
     }

      	WindowManager.LayoutParams这个上边讲的很重要,后边还会遇到LinearLayout.LayoutParams,你的控件在哪个父控件上显示,就要获取哪个父控件的LayoutParams表示你这个控件符合父控件属性规则
    
    
    
      	textView里边有这样一个操作,setLayoutParams(params)将params属性设置给textView:
    
      		textView.setLayoutParams(params);
    
      	这里不直接使用setLayoutParams,因为下边有个windowManager.addView(textView, params),通过addView将属性添加给了textView才设置给windowManager
    

5.12服务的开启# ***

	AddressService服务是在Splash界面打开时就去开启这个服务,用户体验不好,现在软件是在设置中心增加条目,点击显示号码归属地,再点击隐藏号码归属地,由用户控制显示不显示



		小米这些手机都内置了号码归属地显示,有些用户不想要你app中的号码归属地提醒,所以现在安全软件都会让用户选择打开还是关闭

	

		这个操作和设置中心已经做过的打开/关闭提示更新一模一样

	

		来到设置中心SettingActivity中,将打开/关闭提示更新布局复制一份出来,这个就是为什么要封装设置中心布局中的属性原因

		这样复制一份下来特别少,将复制的控件中title改为“显示号码归属地”,将des_on改为“打开显示号码归属地”,将des_off改为“关闭显示号码归属地”,并将id改为sv_setting_address:

	

		activity_settting.xml

	

			<cn.itcast.mobilesafexian02.ui.SettingView

		            android:id="@+id/sv_setting_address"

		            android:layout_width="match_parent"

		            android:layout_height="wrap_content"

		            itcast:des_off="关闭显示号码归属地"

		            itcast:des_on="打开显示号码归属地"

		            itcast:title="显示号码归属地" >

		        </cn.itcast.mobilesafexian02.ui.SettingView>

	

		到SettingActivity初始化控件添加点击事件,具体流程和更新的一模一样,将更新操作封装到一个方法中,不然在onCreate中会越写越多,选中需要抽取的代码,alt+shift+m,给Method name起名为update点击ok,onCreate中调用了update方法,在下边直接抽取出了update方法

	

		归属地也一样,onCreate中调用address()生成address方法

		在这个方法中就可以给它增加点击事件setOnClickListener()

		

		和提示更新操作一模一样,判断下checkbox值,等于true表示已开启服务,再点击表示关闭服务

	

		开启服务用startService(intent),关闭服务用stopService(intent),同时要更改checkbox值:



			private void address() {

				

				sv_setting_address.setOnClickListener(new OnClickListener() {

		

					@Override

					public void onClick(View v) {

						Intent intent = new Intent(SettingActivity.this,AddressService.class);

						if (sv_setting_address.isChecked()) {

								//关闭服务

								stopService(intent);

								sv_setting_address.setChecked(false);

							}else{

								//打开服务

								startService(intent);

								sv_setting_address.setChecked(true);

							}

					}

			}



	在SettingActivity中去开启/关闭AddressService服务了,那就将SplashActivity中开启服务的代码注释掉



	运行代码,进入设置中心点击显示号码归属地条目,显示打开显示号码归属地了,到setting/Apps/running中,点击手机卫士西安2号,看到AddressService服务已开启了

	再点击设置中心的显示号码归属地条目,显示关闭显示号码归属地了,再来到settting下看不到这个服务了

5.13 服务的开启2_动态获取服务是否开启 # ***

打开“显示号码归属地”,再次回到设置中心又变成关闭显示号码归属地,但是在settings中AddressService服务是开启的,所以要对设置中心的“显示号码归属地”条目进行回显操作



	问题:

	以前都保存在sp中,这次不能保存在sp了,因为在settings/apps/running/手机卫士西安2号处有个stop,点击stop可以把服务关闭掉



	比如点击设置中心的“显示号码归属地”开启了AddressService并保存到sp中,用户到settings/apps/running/手机卫士西安2号处,手动点击stop,这时服务关闭了,但是sp中保存的还是开启状态,再次进去设置中心还是会显示“已打开号码归属地”,这就显示不准确了



解决: 动态获取服务是否开启



	utils包下创建工具类AddressUtils,再创建方法isRunningService实现动态获取服务是否开启操作

	需要传一个全类名过来,判断服务是否开启,返回值写boolean

	ActivityManager是通过getSystemService(name)获取

	getSystemService是通过context获取, 所以方法的参数还需要一个上下文:



	ActivityManager别误解为Activity管理者,它是进程管理者或活动管理者

	看它里边操作:

	getLargeMemoryClass()  获取最大内存

	getMemoryInfo() 获取内存的一些信息

	getProcessesInErrorState()  获取进程错误的状态

	getProcessesMemoryInfo() 获取进程内存的一些信息

	getRunningServices(maxNum) 获取正在运行的服务,maxNum是返回的上限,就是最多返回多少个正在运行的服务

		这里来个1000,不是返回1000个正在运行的服务,而是返回小于等于1000个正在运行的服务,超过1000个的不返回

		手机运行内存普遍应该是3MB,不可能有1000个正在运行的服务,开100个内存都会溢出,写1000就是一个上限值



	返回一个list集合,里边存放的是RunningServiceInfo,xxxInfo都是bean类,用来存放信息

	遍历集合并用runningServiceInfo调用.service,返回组件标示ComponentName

	ComponentName在超级管理员权限处讲过,是组件标识,名称,通过它可以调用:

			getClass()获取字节码文件

			getClassName()获取全类名

			getPackageName()获取包名

			getShortClassName()获取短类名

	

		我们获取下全类名,返回一个服务的全类名,判断获取的服务的全类名是否和传递过来的全类名相同

		相同表示服务已开启,不相同表示服务没有开启

	

		public class AddressUtils {

					

			public static boolean isRunningService(String className,Context context){

				//1.进程的管理者,活动的管理者

				ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

				//2.获取正在运行的服务

				List<RunningServiceInfo> runningServices = activityManager.getRunningServices(1000);//maxNum : 返回上限,最多返回多少个服务

				//3.遍历集合

				for (RunningServiceInfo runningServiceInfo : runningServices) {

					//4.获取服务的标示

					//ComponentName : 组件的标示

					ComponentName componentName = runningServiceInfo.service;

					//5.根据标示获取服务的全类名

					String className2 = componentName.getClassName();

					//6.判断获取的服务的全类名是否和传递过来的全类名相同,相同表示服务已经开启,不相同表示服务没有开启

				if (className.equals(className2)) {

					return true;

					}

				}

				return false;

			}

		}



		给isRunningService方法添加static,方便调用

	    回到SettingActivity的address方法中,判断isRunningService(className,context)

	    参数className需要AddressService全类名

		为true表示服务已开启,else表示服务没开启



		private void address(){

			//回显

			//动态的获取服务是否开启

			if (AddressUtils.isRunningService("cn.itcast.mobilesafexian02.service.AddressService", this)) {

				sv_setting_address.setChecked(true);

			}else{

				sv_setting_address.setChecked(false);

			}

		}



		运行程序,设置中心点击开启显示号码归属地,到setttings/apps/running下找到手机卫士西安2号,看到AddressService服务已开启,再次进入设置中心还是开启显示号码归属地

		再点击关闭显示号码归属地,到settings/apps/running下找到手机卫士西安2号,发现没有AddressService服务(关闭了),再回到设置中心,还是关闭显示号码归属地



	还有一个问题:

	在settings/apps/running下找到手机卫士西安2号,手动点击stop关闭AddressService,回到设置中心发现还是打开显示号码归属地,退出设置中心再进来才显示关闭显示号码归属地



	相当于没及时更新界面,切换“设置中心”和settings时,按的是home键,界面只是不可见了,始终没有退出界面,再次回来还是“设置中心”界面,在可见和不可见时没有更新“设置中心”界面



	onStart是界面可见时调用,SettingActivity中重写onStart,把在onCreate中调用的address()移动到onStart中调用:



					@Override

					protected void onStart(){

						super.onStart();

						address();

					}



	运行程序,重复上边步骤再回到设置中心时就及时更新了条目状态



	服务开启步骤总结如下:



	alt+shift+m : 将相应代码抽取方法中

	1.在设置中心中增加显示号码归属地条目

	2.给条目增加点击事件



		/**
  • 显示号码归属地
     */
     private void address() {
     sv_setting_address.setOnClickListener(new OnClickListener() {

      			@Override
    
      			public void onClick(View v) {
    
      				Intent intent = new Intent(SettingActivity.this,AddressService.class);
    
      				if (sv_setting_address.isChecked()) {
    
      					//关闭服务
    
      					stopService(intent);
    
      					sv_setting_address.setChecked(false);
    
      				}else{
    
      					//打开服务
    
      					startService(intent);
    
      					sv_setting_address.setChecked(true);
    
      				}
    
      			}
    
      		});
    
      	}
    
      3.回显操作,动态获取服务是否开启  ***
    
      	a.创建一个工具类
    
      		public class AddressUtils {
    
      			/**
    
  • 动态获取服务是否开启

  • @param className

  • @return
     */
     public static boolean isRunningService(String className,Context context){
     //1.进程的管理者,活动的管理者
     ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
     //2.获取正在运行的服务
     List runningServices = activityManager.getRunningServices(1000);//maxNum : 返回上限,最多返回多少个服务,10M 10000M 10G
     //3.遍历集合
     for (RunningServiceInfo runningServiceInfo : runningServices) {
     //4.获取服务的标示
     //ComponentName : 组件的标示
     ComponentName componentName = runningServiceInfo.service;
     //5.根据标示获取服务的全类名
     String className2 = componentName.getClassName();
     //6.判断我们获取的服务的全类名是否和我们传递过来的全类名相同,相同就是表示服务已经开启,不相同就表示服务没有开启
     if (className.equals(className2)) {
     return true;
     }
     }
     return false;
     }
     }
     b.在activity中调用(SettingActivity.java)

      	private void address(){
    
    
    
      		/动态的获取服务是否开启
    
      		if (AddressUtils.isRunningService("cn.itcast.mobilesafexian02.service.AddressService", this)) {
    
      			sv_setting_address.setChecked(true);
    
      		}else{
    
      			sv_setting_address.setChecked(false);
    
      		}
    
      	}
    

5.12修改toast样式# **

服务的开启上边已讲完,再回过头来看下代码,在AddressService.java服务里边去显示Toast时,用的TextView去显示

在windowManager.addView(View view,LayoutParams params)时,来个View.Inflate,需要布局文件



将布局文件toast_custom.xml创建出来,先来个ImageView,并给LinearLayout设置背景图片,借鉴apk资料/金山卫士/drawable下的图片,它有5个不同颜色的图片,全部都拷贝到项目的drawble-hdpi目录,在LinearLayout中给它默认设置绿色图片:call_locate_blue,再来个TextView控件,设置默认文字为“北京电信”:



	toast_custom.xml



	<?xml version="1.0" encoding="utf-8"?>

	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

	    android:layout_width="wrap_content"

	    android:layout_height="wrap_content"

	    android:orientation="horizontal" 

	    android:gravity="center_vertical"

	    android:background="@drawable/call_locate_blue">

	    <ImageView 

	        android:layout_width="wrap_content"

	        android:layout_height="wrap_content"

	        android:src="@drawable/ic_launcher"/>

	    <TextView 

	        android:id="@+id/tv_toastcustom_address"

	        android:layout_width="wrap_content"

	        android:layout_height="wrap_content"

	        android:text="北京电信"

	        android:textSize="18sp"

	        android:textColor="#000000"/>

	</LinearLayout>



	回到AddressService中加载布局,会返回一个View对象,初始化下Textview(号码归属地),并给它setText,将号码归属地设置给布局中的TextView,将下边单独写的TextView对象注释掉,会有好几个地方因为注释掉textView而报错,将报错地方的textCView分别替换成view,到这修改Toast样式就做完了:  



	/**
  • 显示toast
     */
     public void showToast(String queryAddress) {
     int[] bgcolor = new int[] { R.drawable.call_locate_white,
     R.drawable.call_locate_orange, R.drawable.call_locate_blue,
     R.drawable.call_locate_gray, R.drawable.call_locate_green };
     // 获取windowmanager
     windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
     // 将布局文件转化成view对象之后,view对象就是控件的最外层的控件
     view = View.inflate(getApplicationContext(), R.layout.toast_custom,
     null);
     TextView tv_toastcustom_address = (TextView) view
     .findViewById(R.id.tv_toastcustom_address);
     tv_toastcustom_address.setText(queryAddress);
     // 设置view对象的背景,根据保存的选项索引从bgcolor图片数组中获取相应的图片
     view.setBackgroundResource(bgcolor[sp.getInt(“which”, 0)]);

      	// textView = new TextView(getApplicationContext());
    
      	//
    
      	// textView.setTextSize(100);
    
      	// textView.setTextColor(Color.RED);
    
    
    
      	// android.view.ViewGroup.LayoutParams layoutParams =
    
      	// textView.getLayoutParams();//获取他在父控件中的属性
    
      	// textView.setLayoutParams(params);//设置属性
    
    
    
      	// 设置toast的属性,LayoutParams就代表控件的属性,你的控件在那个父控件上显示,你就要获取那个父控件的LayoutParams,表示你这个控件符合父控件的属性规则
    
      	// LayoutParams:设置属性
    
      	params = new WindowManager.LayoutParams();
    
      	params.height = WindowManager.LayoutParams.WRAP_CONTENT;// 高度包裹内容
    
      	params.width = WindowManager.LayoutParams.WRAP_CONTENT;// 宽度包裹内容
    
      	params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE// 不可获取焦点
    
      			// | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE //不可触摸
    
      			| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; // 保持屏幕常亮
    
      	params.format = PixelFormat.TRANSLUCENT; // 透明
    
      	params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;// 控件的类型,显示窗体上的类型,toast天生没有触摸事件
    
      	// 设置控件的显示位置, | 表示前后两个属性都有效果,两个相对应的属性,默认是左边和上边
    
      	params.gravity = Gravity.LEFT | Gravity.TOP;
    
      	params.x = sp.getInt("x", 100);// x表示的不是坐标,而是距离边框的距离,根据Gravity来设置的,LEFT就是距离左边框的距离,ringht就是距离右边框的距离
    
      	params.y = sp.getInt("y", 100);// y和x是相同的含义,根据top或者bottom来时设置距离顶部或者底部的距离
    
    
    
      	setTouch();
    
    
    
      	windowManager.addView(view, params);// 先把属性设置给textview,然后将textview添加到窗体上
    
      }
    

    运行程序,打开来电归属地,拨打号码,出现了一个蓝色Tost,提示北京移动

5.12修改toast样式# **

总结略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值