MobileSafe Day04

Day04##

Day04 02.界面优化处理# ***

昨天将联系人数据从“联系人界面”获取出来展示到“安全号码界面”了,其实是有一些问题的



问题1:耗时操作放在了主线程中出现的卡顿现象的处理

	   ContactActivity中获取联系人就是查询了一下Contact2.db数据库,现在在模拟器没问题是因为只有三个联系人,真机上保存的联系人上百个,这时查询数据库是会耗费一些时间的

解决:将加载数据库的操作放到子线程中执行



		ContactsActivity的onCreate中创建子线程,将获取联系人操作放在子线程run方法执行:	

	

			new Thread(){

					public void run() {

						//获取联系人

						list = ContactsEngine.getAllContacts(getApplicationContext());

						handler.sendEmptyMessage(0);

					};

			}.start();



问题2:耗时操作放在子线程后出现的空指针异常处理

	在子线程中去获取联系人list,那下边setAdapter就不能在原来那里去写,因为在Myadapter中的getCount中用到一个return list.size();

	当把获取联系人list的操作放到子线程中去执行,在原来的地方去setAdapter时,这的list.size()会报空指针异常,提示list还没有填充完数据

		解决:

			在ContactsActivity的成员变量处重写一个Handler:

				private Handler handler = new Handler(){

					public void handleMessage(android.os.Message msg){

			

					};

				};

		在子线程中获取完联系人list后,用handler去发送一个消息,这里只有一个发送消息,所以发送一个空白的消息就可



				new Thread(){

						public void run() {

							//获取联系人

							list = ContactsEngine.getAllContacts(getApplicationContext());

							handler.sendEmptyMessage(0);

						};

						

				}.start();



		这个0是因为handlerMessage参数msg默认标识what就是0

		将setAdapter放到handler的handleMessage中

				private Handler handler = new Handler(){

					public void handleMessage(android.os.Message msg){

						//msg的默认标识what就是0

						lv_contact_contacts.setAdapter(new Myadapter);

					};

				};

	

		运行程序,点击选择联系人跳转到了选择联系人界面,内容部分一片空白,过3秒后才显示出联系人





问题3:重开子线程导致空白界面的用户体验处理

	  点击选择联系人按钮有一个空白界面,3秒之后才显示出联系人,用户体验不好,开发时会让用户误解为程序卡死了,只要涉及耗时操作都要在加载数据时提醒用户正在加载,不是已经卡死,一般加一个进度条来表示



	  使用进度条显示正在加载,掩盖耗时操作导致的用户不良体验



	到activity_contact.xml中listview正上方加进度条,这时要用到FrameLayout,也可用RelativeLayout



			<!-- FrameLayout : 帧布局,会用在视频播放器 

	    		布局文件最下面的控件,显示时是在最上边

			    -->

			    <FrameLayout 

			        android:layout_width="match_parent"

			        android:layout_height="match_parent"

			    <ListView 

			        android:id="@+id/lv_contact_contacts"

			        android:layout_width="match_parent"

			        android:layout_height="match_parent"

		如果使用FrameLayout,想让进度条显示在正中间,就需要在ProgressBar中使用如下属性:

			android:layout_gravity="center"

		如果使用RelativeLayout,想让进度条显示在正中间,那就需要在ProgressBar中使用如下属性:

			android:layout_centerInParent="true"



	代码中使用布局文件中的进度条:



	  a.注解初始化,声明

		前边使用XUtils框架实现过下载功能,现在实现加载操作



		ContactActivity的成员变量处:

				//通过注解的方式初始化控件,spring框架通过注解获取bean对象

				@ViewInject(R.id.loading)

				private ProgressBar loading;

		在oncreate方法中:

				//注解初始化控件相当于反射实现findviewbyid

				ViewUtils.inject(this);



		一些机型对于这种注解形式不兼容,初始化不出来id,这是在工作中遇到的,这种机型很少,但一般我都是用findViewById



		不建议频繁使用注解,android现有几千种机型,肯定有用注解没法兼容的,但findViewById不存在这种问题



		进度条在上边通过注解方式初始化后,在子线程运行之前,让进度条显示出来,子线程中获取完联系人后隐藏进度条



	  b.在子线程之前显示,显示数据之后隐藏



				private Handler handler = new Handler(){

					public handleMessage(android.os.Message msg){

		

					lv_contact_contacts.setAdapter(new Myadapter());

					loading.setVisibility(View.INVISIBLE); //显示数据之后隐藏进度条

				    };

				};

		

				@Override

				protected void onCreate(Bundle savedInstanceState) {

					super.onCreate(savedInstanceState);

					setContentView(R.layout.activity_contact);

					//注解初始化控件,其实就相当于通过反射实现findviewbyid的形式

					ViewUtils.inject(this);

					

					loading.setVisibility(View.VISIBLE);//显示进度条

				}

Day04 03.异步加载框架# **

审视上边对界面优化处理的逻辑:

		loading.setVisibility(View.VISIBLE)是在子线程之前执行的

	获取联系人操作是在子线程之中执行的,即:

		list = ContactsEngine.getAllContacts(getApplicationContext());

	执行完后发送一个消息给Handler,Handler中显示数据后隐藏进度条,handler这个操作是在子线程之后执行的



	子线程之前,之中,之后的操作,完全可以封装到工具类中执行



	模板设计模式:

	父类中创建一些方法,但不知道方法具体执行代码,让子类根据自己的特性去执行相应代码

		在SetUpBaseActivity中创建的两个方法:

			//父类不知道下一步和上一步具体代码,写一个抽象类让子类实现,根据自己的特性去实现响应的操作

			//下一步

			public abstract void next_activity();

			//上一步

			public abstract void pre_activity(); 

		这个操作就是模板设计模式



	通过模板设计模式封装子线程之前,之中,之后执行的方法

	创建一个类MyAsyncTask



		public abstract class MyAsyncTask {

			private Handler handler = new Handler(){

				public void handleMessage(android.os.Message msg) {

					postTask();

				};

			};

			/**
  • 在子线程之前执行的方法
     /
     public abstract void preTask();
     /
    *

  • 在子线程之中执行的方法
     /
     public abstract void doinBack();
     /
    *

  • 在子线程之后执行的方法
     /
     public abstract void postTask();
     /
    *

  • 执行
     */
     public void execute(){
     preTask();
     new Thread(){
     public void run() {
     doinBack();
     handler.sendEmptyMessage(0);
     };
     }.start();
     }
     }

      	对这3个方法都写了注释,我们知道了哪个在子线程之前,之中,之后执行,但代码不知道
    
      	所以创建一个方法execute,代表执行
    
      	在这个方法中调用在子线程之前执行的方法preTask,然后创建一个子线程调用在子线程之中执行的doinBack方法,并发送一个消息:	handler.sendEmptyMessage(0);
    
      	模仿之前写法,创建一个Handler,在handler的handleMessage中调用在子线程之后执行的postTask方法	
    
    
    
      ContactsActivity的onCreate方法中,new一个MyAsyncTask:
    
    
    
      	//异步加载框架
    
      	new MyAsyncTask() {
    
      		
    
      		@Override
    
      		public void preTask() {
    
      			//在子线程之前执行
    
      			loading.setVisibility(View.VISIBLE);
    
      		}
    
      		
    
      		@Override
    
      		public void postTask() {
    
      			//在子线程之后执行
    
      			//msg的默认标示what就是0
    
      			lv_contact_contacts.setAdapter(new Myadapter());
    
      			loading.setVisibility(View.INVISIBLE);//显示数据,隐藏进度条
    
      		}
    
      		
    
      		@Override
    
      		public void doinBack() {
    
      			//在子线程之中执行
    
      			//获取联系人
    
      			list = ContactsEngine.getAllContacts(getApplicationContext());
    
      		}
    
      	}.execute();
    
    
    
      	new的这个MyAsyncTask实现了这三个方法,调用执行方法.execute()
    
      
    
      		把在子线程之前执行的方法:
    
      			//在子线程之前执行
    
      			loading.setVisibility(View.VISIBLE);
    
      		拷贝到preTask方法中
    
      	
    
      		把在子线程之中执行的方法:
    
      			//在子线程之中执行
    
      			//获取联系人
    
      			list = ContactsEngine.getAllContacts(getApplicationContext());
    
      		拷贝到doinBack方法中
    
      		MyAsynTask中我们已经封装了一个handler.sendEmptyMessage(0);,所以这里拷贝时去掉
    
      	
    
      		最后把在子线程之后执行的方法:
    
      			//在子线程之后执行
    
      			//msg的默认标示what就是0
    
      			lv_contact_contacts.setAdapter(new Myadapter());
    
      			loading.setVisibility(View.INVISIBLE);//显示数据,隐藏进度条
    
      		拷贝到postTask方法中
    
      	
    
      		运行程序,点击选择联系人,和刚才操作一模一样,但是封装调用有条理很多,这种操作android中专用名词是异步加载框架
    
      	
    
      	AsyncTask框架比我们写的MyAsyncTask多了三个参数,即:
    
      		String	//参数1:子线程执行所需的参数
    
      		Integer //参数2:加载的进度
    
      		String  //参数3:执行的结果
    
      
    
      	这三个参数作用:提高扩展性
    
      
    
      	异步加载框架原理:可通过查看源码了解框架原理
    
      		它内部就是用了子线程来实现,封装了在子线程之前执行的方法onPreExecute,在子线程之中执行的方法doInBackground,在子线程之后执行的方法onPostExecute,通过在execute方法中调用onPreExecute,然后在创建子线程来执行我们要实现的耗时操作,其实就是在子线程中调用doInBackground实现耗时操作,最后通过给handler发送一个消息,到handler中去执行onPostExecute
    
      
    
      	百度面试:异步加载框架运行几个后就跟new线程一样? 5个 
    

Day04 04.修改进度条样式 # **

点击“选择联系人”,出现了进度条,对进度条样式不满意



系统怎么定义进度条样式:

	打开adt-bundle-windows-x86_64_20140101/sdk/platforms/android-16/data/res/values,以前我们经常看的是attrs.xml,现在不看了,找到styles.xml

	在这里按ctrl+F找到ProgressBar,在这里定义一个ProgressBar,这个就是ProgressBar样式

	android:indeterminateOnly : 有没有进度条   true:没有 	false:有



修改进度条样式步骤:

	1.查看源码的sdk\platforms\android-16\data\res\values\styles.xml

	2.根据源码,在res -> drawable -> xxxxx.xml中写我们的自定义样式

		  progressbar_drawable.xml

		

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

				<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"

				    android:drawable="@drawable/shenmabg"

				    android:pivotX="50%"

				    android:pivotY="50%"/> 



	3.在控件中使用相应属性,覆盖系统样式文件属性,即修改ProgressBar的样式



		activity_contact.xml		

			android:indeterminateDrawable="@drawable/progressbar_drawable"



		这就是如何改变控件样式的操作

Day04 05.打开系统联系人界面 #

从昨天到今天一直是用代码去获取联系人list

系统当中也有一个联系人界面,现在简单给大家介绍下怎么打开系统联系人界面获取相应操作



这个不要求会,非常简单,代码给大家准备好了,打开“打开系统联系人界面.txt”,看下就可以了,即:



	打开系统联系人界面.txt



				public void btn_contacts(View v){



			//		Intent intent = new Intent(this,ContactsActivity.class);

			//		startActivityForResult(intent, 0);

					Intent intent = new Intent();

					intent.setAction("android.intent.action.PICK");

					intent.addCategory("android.intent.category.DEFAULT");

					intent.setType("vnd.android.cursor.dir/phone_v2");

					startActivityForResult(intent, 1);

				}

			//  打开的ContactsActivity 退出时候调用

				@Override

				protected void onActivityResult(int requestCode, int resultCode, Intent data) {

					super.onActivityResult(requestCode, resultCode, data);

					if(data !=null){

					//	String num = data.getStringExtra("num");

					Uri uri = data.getData();

					String num = null;

					// 创建内容解析者

							ContentResolver contentResolver = getContentResolver();

							Cursor cursor = contentResolver.query(uri,

									null, null, null, null);

							while(cursor.moveToNext()){

								num = cursor.getString(cursor.getColumnIndex("data1"));

								

							}

							cursor.close();

							num = num.replaceAll("-", "");

							et_safe_num.setText(num);	

				}



	像在SetUp3Activity中写的一样,也是通过intent跳转,这个跳转到的是系统的联系人界面

	工作用到可直接把上边代码复制到需要的地方,固定写法

	用的比较多的是用代码去获取系统联系人方式,即ContactsEngine,所以ContactsEngine的代码必须会

Day04 06.设置向导流程完成 # ***

第3个界面完成,接着实现第4个界面“4.恭喜您,设置完成”

	这个界面就是一个checkbox,点击checkbox就变成了“你已经开启防盗保护”,再点击就是“你没有开启防盗保护”,只需要保存状态即可

	

	步骤:

	1.在SetUp4Activity中初始化CheckBox控件并设置点击事件

		private CheckBox cb_setup4_protected;

		@Override

		protected void onCreate(Bundle savedInstanceState) {

			super.onCreate(savedInstanceState);

			setContentView(R.layout.activity_setup4);

			cb_setup4_protected = (CheckBox) findViewById(R.id.cb_setup4_protected);

		}

		//当checkbox状态发生改变的时候调用

		cb_setup4_protected.setOnCheckedChangeListener(new OnCheckedChangeListener() {

				@Override

				public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

					//根据状态进行相应的设置

				}

			});



		1)CompoundButton buttonView:选中CompoundButton,ctrl+t查看源码,看它的继承树,CompoundButton是checkbox的父类,CompoundButton的子类有CheckBox,RadioButton,Switch,ToggleButton,在这里它代表的就是checkbox

		2)boolean isChecked:已经改变的状态即点击后的状态

		

		在onCheckedChanged中,isChecked等于true表示开启了防盗保护,else等于false表示关闭了防盗保护

		开启防盗保护时,checkbox文本要改变成你已开启了防盗保护,否则将checkbox的文本改变为“你已关闭了防盗保护”

		setChecked(true)/setChecked(false)不写都无所谓,为了程序严谨性加上了,避免出现选中了但有时checkbox不知道什么原因却没有选中的问题,这里手动设置下就能避免这类问题



		运行程序,效果正确,但是点击checkbox,状态变为选中,即你已开启了防盗保护

		点击“上一步”,再点击“下一步”回到“设置完成页面”,发现选中状态没有保存,重新变成了未选中状态



		问题:checkbox状态未保存的处理

		解决:设置回显操作



		1)SetUp4Activity中拿到sp保存状态,如果已经开启了防盗保护,就将protected设置putBoolean为true,否则为false,最后将编辑好的sp去commit保存

		2)根据保存的防盗保护状态进行回显操作

	

			@Override

			protected void onCreate(Bundle savedInstanceState) {

				super.onCreate(savedInstanceState);

				setContentView(R.layout.activity_setup4);

				cb_setup4_protected = (CheckBox) findViewById(R.id.cb_setup4_protected);

				

				//2.根据保存的防盗保护状态进行回显操作

				if (sp.getBoolean("protected", false)) {

					cb_setup4_protected.setText("您已经开启了防盗保护");

					cb_setup4_protected.setChecked(true);//实际设置checkbox状态

				}else{

					cb_setup4_protected.setText("您还没有开启防盗保护");

					cb_setup4_protected.setChecked(false);

				}

		

				//当checkbox状态发生改变的时候调用

				cb_setup4_protected.setOnCheckedChangeListener(new OnCheckedChangeListener() {

					//buttonView : checkbox

					//isChecked : 已经改变的状态,点击后的状态

					@Override

					public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

						//1.保存防盗保护状态

						Editor edit = sp.edit();

						//根据状态进行相应的设置

						if (isChecked) {

							//开启防盗保护

							//设置checkbox的文本

							cb_setup4_protected.setText("您已经开启了防盗保护");

							cb_setup4_protected.setChecked(true);//程序的严谨性

							edit.putBoolean("protected", true);

						}else{

							//关闭防盗保护

							//设置checkbox的文本

							cb_setup4_protected.setText("您还没有开启了防盗保护");

							cb_setup4_protected.setChecked(false);

							edit.putBoolean("protected", false);

						}

						edit.commit();

					}

				});

			}



		根据保存的防盗保护状态进行回显操作处,从sp中拿出protected值,默认为false,即默认情况下刚进入程序时用户没有开启防盗保护,所以这里为false,很多人会误认为是写错了,认为已经开启了防盗保护的话,这里应该设置为true

		如果是true,这里就要setText为“您已经开启了防盗保护”,否则为“您还没有开启防盗保护”

	

		根据保存的防盗保护状态进行回显操作这里的setChecked为实际设置checkbox状态的操作,必须要有

		而下边的根据状态进行相应的设置这里的setChecked是为了程序的严谨性,有没有都可以



		运行程序,选中checkbox,点击“上一步”,“下一步”回到“设置向导流程”界面,看到checkbox为选中状态,说明回显操作成功了



	---------------------------------------------



	第4个界面完成后点击“设置完成”跳转到手机防盗页面有安全号码“5556”,还有防盗保护是否开启,右边有个打开的小锁

		这是根据保存的“安全号码”和“防盗保护是否开启”状态设置的,接下来到lostFindActivity的onCreate的else处



	2.根据保存的安全号码和防盗保护状态设置相应显示操作,在lostfindActivity中的Oncreate的else中

		1)通过findViewById初始化安全号码控件

		2)根据保存的安全号设置安全号码

			从sp中去getString,保存的安全号码是safenum,默认的是一个空字符串

		3)把小锁初始化出来

		3)获取保存的防盗保护状态,从sp中get一个boolean值,默认是false:

		4)根据获取的状态设置相应图片,如果是ture,给imageview设置图片为lock,否则为unlock图片



		整体步骤如下:



			else{

			setContentView(R.layout.activity_lostfind);



			//根据保存的安全号码和防盗保护状态设置相应显示操作

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

			//根据保存的安全号设置安全号码

			tv_lostfind_safenum.setText(sp.getString("safenum", ""));

			ImageView tv_lostfind_islock = (ImageView) findViewById(R.id.tv_lostfind_islock);

			//1.获取保存的防盗保护是否开启状态

			boolean b = sp.getBoolean("protected", false);

			//2.根据获取的状态设置相应的图片

			if (b) {

				tv_lostfind_islock.setImageResource(R.drawable.lock);

			}else{

				tv_lostfind_islock.setImageResource(R.drawable.unlock);

			   }

			}



	-------------------------------------------

	上边的做完之后,之前做过一个监听手机重启之后SIM不一致时发送报警短信的操作,即BootCompletedReceiver.java

	如果用户没开启防盗保护,就没必要再去执行报警操作了,所以需要判断下



	3.BootCompletedReceiver中的onReceive中,根据保存的防盗保护是否开启状态增加判断,如果用户开启了防盗保护,就在手机重启之后SIM不一致时发送报警短信,反之,不需要

		

		@Override

		public void onReceive(Context context, Intent intent) {

			System.out.println("手机重启了......");

			SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);

	

			//根据保存的防盗保护是否开启状态,判断是否发送报警短信

			if (sp.getBoolean("protected", false)) {

	

				//判断SIM卡是否发生变化

				//1.获取保存的SIM卡 

				String sp_sim = sp.getString("sim", "");

				//2.获取当前的SIM卡

				TelephonyManager tel = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

				String sim = tel.getSimSerialNumber();//获取SIM卡序列号,唯一标示

				//3.判断SIM卡是否为空

				if (!TextUtils.isEmpty(sp_sim) && !TextUtils.isEmpty(sim)) {

					//4.判断SIM卡是否一致

					if (!sp_sim.equals(sim)) {//避免空指针异常

						//发送短信

						SmsManager smsManager = SmsManager.getDefault();//短信的管理者

						//destinationAddress : 收件人

						//scAddress : 服务中心的地址  一般null

						//text : 短信内容

						//sentIntent : 是否发送成功

						//deliveryIntent : 协议一般null

						/smsManager.sendTextMessage("5556", null, "da ge wo bei dao le,help me!!!", null, null);/

						smsManager.sendTextMessage(sp.getString("safenum", "5556"), null, "da ge wo bei dao le,help me!!!", null, null);

					}

				}

			}

		}



		从sp中获取protected的boolean值,默认为false,就是没有开启

			if (sp.getBoolean("protected", false)) {

		如果是true,将发送报警短信代码放进去执行,如果是false什么也不执行



		接收报警短信原来直接写了“5556”,改为从sp中获取保存的安全号码:sp.getString(key,defValue)

		默认值就写成5556,即:sp.getString("safenum", "5556")

Day04 07.接收短信 # ***

“手机防盗”页面LostFindActivity中已实现了“安全号码”,“防盗保护是否开启”,“重新进入设置向导”,接下来实现“手机防盗”界面中“功能简介”下边的这些操作了:



			“GPS追踪:#location#”

			"播放报警音乐:#alarm#"

			"远程删除数据:#wipedata#"

			"远程锁屏:#locakscreen#"



这些操作都要实现,#location#是一个指令,通过发送这个指令,手机端接收这个指令来执行相应操作



指令发送方式:



1.服务器发送,客户端接收,这种方式叫消息推送,用通知栏展示

		需要一直去连接服务器,市面上连接服务器两种方式:

			1)心跳连接(每隔半个小时给服务器发送一个空包)

			2)长连接(在后台一直发送消息,比较耗电,耗流量)

		以前用长连接连接服务器,现在都用心跳连接服务器

		工作时会用到第三方sdk实现消息推送,比如:极光推送、百度推送

		用第三方sdk比较简单,下载sdk,jar包,文件导入工程,根据开发文档就可实现第三方sdk的消息推送

		消息推送局限性:必须联网

	

2.通过短信方式接收指令,接收解析短信实现相应操作(这里指令发送形式采用第2种)

	既然要接收解析短信,就也需要去设置“短信到来的广播接收者”来接收短信



	1)在cn.itcast.mobilesafe.receiver包下,再创建一个广播接收者SmsReceiver,继承自BroadcastReceiver,拷贝SmsReceiver全类名到清单文件注册:

		<receiver android:name="cn.itcast.mobilesafexian02.receiver.BootCompletedReceiver">

        <!-- priority : 值越大优先级越高,优先级越高越先接收到广播事件 -->

        <intent-filter android:priority="1000" >

            <action android:name="android.intent.action.BOOT_COMPLETED" />

        </intent-filter>

    </receiver>



	    <receiver 

		android:name="cn.itcast.mobilesafexian02.receiver.SmsReceiver" >

	

	            <!-- priority : 要想拦截短信,优先级必须大于0,小于0是系统先接收到短信 -->

	            <intent-filter android:priority="1000" >

	                <action android:name="android.provider.Telephony.SMS_RECEIVED" />

	            </intent-filter>

	        </receiver>



	优先级都设置1000不会冲突,只有在事件相同时,才会去判断优先级,手机重启检测到有2个广播接收者,但是只有BOOT_COMPLETED接收手机重启的事件



	在action中找不到接收短信的事件,在一些高版本上不允许像这样去接收或发送短信,高版本中(4.4以上)要想接收发送短信必须去实现android集成的接口,接收和发送短信时都必须去提醒用户,才能去接收和发送短信

	打开课件ppt中的第28页,已经给大家准备好了,拷贝过来即可,即:

				android.provider.Telephony.SMS_RECEIVED //这个就是接收短信的广播



	2)SmsReceiver中的onReceive中接收解析短信

		接收解析短信的操作也给你们准备好了,都是固定的写法,即:

	

			@Override

			public void onReceive(Context context, Intent intent) {

				//接收解析短信

				//接收短信

				//为什么是数组呢?因为70个汉字一条短信,比如71汉字,那就是两条短信

				Object[] objs = (Object[]) intent.getExtras().get("pdus");

				for(Object obj:objs){

					//将短信转化成一个SmsMessage对象

					SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) obj);

					String body = smsMessage.getMessageBody();//获取短信的内容

					String sender = smsMessage.getOriginatingAddress();//获取发件人

					System.out.println("发件人:"+sender+"  短信内容:"+body);

					abortBroadcast();//拦截短信,android原生系统中可以实现,但是在国产的深度定制系统中可能不太好使,小米

				}

			}



		如上代码,接收短信是一个Object数组是因为在中国70个汉字算一条短信,发送了71个汉字,接收时就算接收了2条短信,所以它才会在这里用一个数组来接收

		for遍历是将短信转化为一个短信对象SmsMessage,通过smsMessage获取短信内容body,发件人sender, 把他们打印出来



		接收解析短信属于侵犯用户隐私,需要权限:

			android.permission.RECEIVE_SMS(Users Permission)



	运行程序,发送一条短信,点击DDMS,选择Emulator Control,在Telephony Actions下边的Incoming number处填5556,在下边的SMS处,填入短信内容“da ge ni hao! wo shi 5556”,点击Send发送,打印出了: 发件人:5556 短信内容:“da ge ni hao! wo shi 5556”,模拟器5554也接收到短信了



	abortBroadcast是拦截短信,将短信删除,重新发送一条短信,打印出来和上边相同的内容,但是模拟器5554却没有接收到短信



	要想去拦截短信:

	1)在清单文件中注册广播接收者时,优先级必须满足大于0,小于0是系统先接收到短信

	2)abortBroadcast在android原生系统可以实现,在国产的深度定制系统中可能不太好使,比如小米就把这个功能禁掉了

Day04 08.定位三种方式 # **

将SmsReceiver的abortBroadcast注释掉,在接收解析短信下边可进行判断

	这就表明已经可以根据指令执行相应操作了,但是5554模拟器的系统也接收到短信了,指令"#location#"是用来执行相应功能,如果系统也接收到了,小偷偷了你的手机也能看到,就会根据这个指令去做破换

	所以要把拦截短信功能添加到每个相应操作,直接添加到最后会导致不管是谁发送的短信都会拦截,只需要拦截指令的短信即可,其他短信不拦截,所以需要将拦截短信操作放到每个具体指令操作中:



		 //判断短信的内容是否是指令

			if ("#location#".equals(body)) {//避免空指针异常

				//GPS追踪

				System.out.println("GPS追踪");

				abortBroadcast();

			}else if("#alarm#".equals(body)){ //播放报警音乐

				//播放报警音乐

				System.out.println("播放报警音乐");

				abortBroadcast();

			}else if("#wipedata#".equals(body)){

				//远程删除数据

				System.out.println("远程删除数据");

			    abortBroadcast();

			}else if("#lockscreen#".equals(body)){

				//远程锁屏

				System.out.println("远程锁屏");

				abortBroadcast();

			}



	再运行程序,发送指令"#alarm#",打印出来内容是:

			“发件人:5556 短信内容:"#alarm#"”

			然后打印出来的是: “播放报警音乐”

	这时5554模拟器系统就不会再接收到指令短信了

	

	---------------------------------------------	



	根据发送短信的内容判断指令操作做完了

	接下来实现里边相应功能,先来实现GPS追踪功能

	

	android中3种定位方式: 	



	1.网络定位,wifi定位,俗称ip地址定位



		根据ip,查询ip对应的物理技术

		可能会很准,如果是动态ip(dhcp技术)就不准了



		我们国家数据库中存放的是ip地址和实际地址的映射,可以查询数据库中的ip地址,得到实际地址



		现在不许用ip地址定位,因为现在是ipv4形式192.168.1.100,每一段都是0-255的一段数据

		以前够用,但现在手机,pad,笔记本,智能家居等都需要一个ip地址,不够用了就出来新技术DHCP,动态获取ip地址



		比如雁塔区有50个ip,从192.168.1.1-192.168.1.50,每一个ip都有一个人或家庭,公司在用网,刚好有50个,可以通过ip地址查询到实际地址,现在变了,又增加了3个用户,但没有多余ip地址了,检测下同一时刻哪个ip没上网,就会把没上网的这3个ip给这3个新增用户去用



		数据库里保存的是192.168.1.1这个人,这个ip给新增的某个用户用了,查询地理位置时查询到的实际地理位置是在192.168.1.1原来这个人的位置,但现在实际使用ip地址的位置是在新增用户这里,这个偏差比较大,可能十几公里到几十公里,所以现在这个ip地位不是特别准确了



	2.基站定位

		基站:服务电话的设备,手机距离基站的远近决定了信号强弱

		基站定位和wifi定位局限性:不能定位海拔



	3.gps定位

		手机和gps定位卫星通讯,光波通讯,室内或地下室没办法定位,时间比较长,1分钟,比较费电,不需要联网

		agps技术: 需要联网 通过网络服务gps定位,修正定位坐标,准确度高

		百度定位sdk,百度地图sdk	

Day04 9.定位的具体代码# ***

接下来依次实现这4个功能

		“GPS追踪:#location#”

		"播放报警音乐:#alarm#"

		"远程删除数据:#wipedata#"

		"远程锁屏:#locakscreen#"

GPS追踪的实现:写一个示例工程,工程名为定位

	要实现定位,首先要获取一个位置的管理者locationManager,xxxManager一般是getSystemService获取,在它里边传一个LOCATION_SERVICE

	locationManager中有一个getProviders(boolean enableOnly)会返回一个List集合,表示获取所有定位方式,如果是true表示返回可用定位方式

	接下来遍历List<String>,在里边打印String

		既然是定位操作就需要权限

		android.permission.ACCESS_MOCK_LOCATION : 模拟位置的权限,模拟器是必须加的,真机可以不加

		android.permission.ACCESS_FINE_LOCATION : 精确位置的权限,真机必须加的

		android.permission.ACCESS_COARSE_LOCATION : 大概位置的权限,真机必须加的

		运行后打印出两种定位方式:passive gps

		passive是基站定位,gps是gps定位,没有打印出wife定位是因为模拟器不支持wife定位



	上边获取了所有定位方式,它里边还有一个操作getBestProvider(criteria,enabledOnly),这是获取最佳的定位方式



		有2个参数:

		criteria:设置定位的一些属性,通过定位属性,可以去设置一些返回的定位操作

		enabledOnly:boolean值,如果定位可用就返回

		第一个参数拿过来先写下: Criteria criteria = new Criteria();

		在这里边看到有一些方法:

			setAccuracy(设置精确度),setAltitudeRequired(设置可以定位海拔)

 		setAltitudeRequired返回ture表示可以定位海拔

		criteria.setAltitudeRequired(true);//只有gps可以定位海拔



		把getBestProvider的参数enableOnly也设置为true,返回一个String类型的bestProvider:

			String bestProvider = locationManager.getBestProvider(criteria, true);

		然后输出下bestProvider

		运行程序,打印出:

						passive

						gps

						最佳的定位方式:gps

		一行代码就可以拿到gps定位:

			criteria.setAltitudeRequired(true);//只有gps可以定位海拔



		定位方式拿到了后进行定位操作

		locationManager有一个方法requestLocationUpdates(string provider,long mineTime,float minDistance,LocationListener listener)

			有4个参数:

			string provider:定位方式

			long mineTime: 定位的最小时间间隔,就是每隔多少时间定位一次

			float minDistance:最小的定位距离,这个是每隔多少距离定位一次

			LocationListener listener:这个是一个监听

			分别给它设置参数:

				miniTime定位的最小时间间隔写0,它代表的是默认时间,不是代表时间间隔为0,其实也可以理解为0,设置成0在真机上是时时刻刻都在定位,只不过时间比较短

				minDistance最小的定位距离也设置成0

				第四个参数LocationListener,new一个MyLocationListener



	将MyLocationListener创建出来,里边实现四个方法

		主要看第一个方法,参数location表示当前位置,它里边有:

				getAccuracy(获取精确的位置)

				getAltitude(获取海拔)

				getLatitude(获取纬度,纬度是平行赤道的)

				getLongitude(获取经度,纬度是垂直赤道的)

				getTime(获取时间)

				getSpeed(获取速度)



		这里需要“获取精确的位置,获取海拔,获取经纬度”:

		到这定位操作就做完了,运行下“定位”项目	



	定位具体代码实现:



		public class MainActivity extends Activity{

			//位置的管理者

			private LocationManager locationManager;

	

			@Override 

			protected void onCreate(Bundle savedInstanceState) {

				super.onCreate(savedInstanceState);

				setContentView(R.layout.activity_main);

	

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

				

				//1.获取位置的管理者

				locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

				//2.获取定位方式

				//2.1获取所有的定位方式,true:返回可用的定位方式

				List<String> providers = locationManager.getProviders(true);

				for (String string : providers) {

					System.out.println(string);

				}

				//2.2获取最佳的定位方式

				Criteria criteria = new Criteria();

				//设置可以定位海拔,true:表示可以定位海拔

				criteria.setAltitudeRequired(true);//只有gps可以定位海拔

				//criteria : 设置定位属性

				//enabledOnly : 如果定位可用就返回

				String bestProvider = locationManager.getBestProvider(criteria, true);

				System.out.println("最佳的定位方式:"+bestProvider);

				//3.定位 

				//provider : 定位方式

				//minTime :定位的最小时间间隔

				//minDistance : 最小的定位距离

				//listener : LocationListener监听

				locationManager.requestLocationUpdates(bestProvider, 0, 0, new MyLocationListener());

			}

			

			//4.创建一个定位的监听

			private class MyLocationListener implements LocationListener{

				//定位位置发生变化的时候调用

				//location : 当前的位置

				@Override

				public void onLocationChanged(Location location) {

					//5.获取经纬度

					location.getAccuracy();//获取精确的位置

					location.getAltitude();//获取海拔

					double latitude = location.getLatitude();//获取纬度,平行

					double longitude = location.getLongitude();//获取经度

					tv_main_location.setText("longitude:"+longitude+"   latitude:"+latitude);

				}

				//定位状态发生变化的时候调用

				@Override

				public void onStatusChanged(String provider, int status, Bundle extras) {

					// TODO Auto-generated method stub

				}

				//定位可用的时候调用

				@Override

				public void onProviderEnabled(String provider) {

					// TODO Auto-generated method stub

				}

				//定位不可用的时候调用

				@Override

				public void onProviderDisabled(String provider) {

					// TODO Auto-generated method stub

				}

			}

		}

	

	到这,定位操作就做完了,运行程序,将模拟器先运行起来



	模拟器自己没办法去定位,这就是要加模拟位置权限原因,要自己手动给模拟器发送一个位置,它才能去定位位置

	打开DDMS,Emulator Control,下边有一个Location Controls,在这里填入经纬度,点击Send

		就直接在模拟器的定位项目的textView中显示出:

		longitude:-122.084095 latitude:37.422005

		把latitude改为38.422005,再点击send,模拟器上就改变了

		发送一次改变一次,可以再改回来,每发送一个模拟器就会重新定位一次

		表明模拟器实际上一直在进行定位,只不过拿不到位置,必须给它发送一个位置,才能定位到这个位置,这个就是GPS定位



	到真机上演示一下

	看标题栏上发现GPS没有开启,在高版本中这个GPS,Android已经不允许用代码去打开

	打开百度时提醒是否设置GPS,点击设置会进系统GPS,让用户打开,用完之后也不负责关,这个就交给用户去做了



	将“定位”运行到真机上,在真机上把GPS打开,这个GPS是通过光波用卫星和手机通讯,所以接下来还需要到窗台那进行定位操作,在真机的“定位”项目上,显示longitude:108.85661205 latitude:34.19107395

	把经纬度记下来上网打开浏览器,看下经纬度位置,百度是有一个拾取坐标系统,将坐标反查勾上,把经纬度输进去,即:108.85661205,34.19107395,然后点击百度查看,就是大概位置



		我们的坐标没有问题,之所以距离远是因为地图在输入坐的时,将坐标转化为了火星坐标



 	国家保密插件火星坐标,对真实坐标进行人为加偏处理

		开国时为了国土安全,规定所有地图使用的坐标都要进行人为加偏处理,网上称它为火星坐标

		所有电子地图,导航设备,都要加入国家保密插件才能使用,否则是违法行为,这个加密插件是由国家地理测绘局提供,像百度,腾讯,做地图都要到国家地理测绘局,经过插件算法把地图信息进行加偏,否则你是不能使用的,这个不是让你白使用的,要给钱去买人家算法,因为国家关系原因,火星坐标中没有国家机密建筑

		特种部队,军事基地,全部看不到,你看到的就是普通建筑或一片荒地

4.10.gps定位操作移植到手机卫士# ***

接下来将定位操作移植到手机卫士



	到手机卫士的SmsReceiver,当获取到这个指令后,就要去进行定位操作了

	不可在这里直接写定位操作,定位操作要连接定位卫星最少需1分钟,在for循环定位1分钟,再for循环定位1分钟

	四大组件都在主线程中,所以急广播接收者onReceive中的代码不能超过10秒钟,超过10秒钟就不执行这个代码去执行其他代码了



	这里就要用到服务service了,创建GPSService继承自service,把定位操作移植到GPSService

	在SmsReceiver中接收到GPS追踪指令后,开启服务GPSService接收定位操作



		public class GPSService extends Service {

		

			// 位置的管理者

			private LocationManager locationManager;

			private MyLocationListener myLocationListener;

			private SharedPreferences sp;

		

			@Override

			public IBinder onBind(Intent intent) {

				// TODO Auto-generated method stub

				return null;

			}

		

			@Override

			public void onCreate() {

				super.onCreate();

				sp = getSharedPreferences("config", MODE_PRIVATE);

				// 1.获取位置的管理者

				locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

				// 2.获取定位方式

				// 2.1获取所有的定位方式,true:返回可用的定位方式

				List<String> providers = locationManager.getProviders(true);

				for (String string : providers) {

					System.out.println(string);

				}

				// 2.2获取最佳的定位方式

				Criteria criteria = new Criteria();

				// 设置可以定位海拔,true:表示可以定位海拔

				criteria.setAltitudeRequired(true);// 只有gps可以定位海拔

				// criteria : 设置定位属性

				// enabledOnly : 如果定位可以就返回

				String bestProvider = locationManager.getBestProvider(criteria, true);

				System.out.println("最佳的定位方式:" + bestProvider);

				// 3.定位

				myLocationListener = new MyLocationListener();

				// provider : 定位方式

				// minTime :定位的最小时间间隔

				// minDistance : 最小的定位距离

				// listener : LocationListener监听

				locationManager.requestLocationUpdates(bestProvider, 0, 0,

						new MyLocationListener());

			}

		

			// 4.创建一个定位的监听

			private class MyLocationListener implements LocationListener {

				// 定位位置发生变化的时候调用

				// location : 当前的位置

				@Override

				public void onLocationChanged(Location location) {

					// 5.获取经纬度

					location.getAccuracy();// 获取精确的位置

					location.getAltitude();// 获取海拔

					double latitude = location.getLatitude();// 获取纬度,平行

					double longitude = location.getLongitude();// 获取经度

					

					//保存经纬度坐标

					Editor edit = sp.edit();

					edit.putString("longitude", longitude+"");

					edit.putString("latitude", latitude+"");

					edit.commit();

		//			Timer timer = new Timer();

		//			//task : 定时执行的任务,

		//			//when:延迟的时间,延迟多长时间执行定时任务

		//			//period : 每次执行的间隔时间

		//			timer.schedule(task, when, 10006030)

				}

		

				// 定位状态发生变化的时候调用

				@Override

				public void onStatusChanged(String provider, int status, Bundle extras) {

					// TODO Auto-generated method stub

				}

		

				// 定位可用的时候调用

				@Override

				public void onProviderEnabled(String provider) {

					// TODO Auto-generated method stub

				}

		

				// 定位不可用的时候调用

				@Override

				public void onProviderDisabled(String provider) {

					// TODO Auto-generated method stub

				}

			}

			@Override

			public void onDestroy() {

				super.onDestroy();

				//5.取消定位

				locationManager.removeUpdates(myLocationListener);//在高版本中已经无效了,因为高版本中规定必须由用户自己去打开和关闭gps

			}

		}



	到这定位操作已完全复制过来了,将显示经纬度的TextView删掉,不用它了

	添加权限:

	android.permission.ACCESS_MOCK_LOCATION : 模拟位置的权限,模拟器是必须加的,真机可以不加

	android.permission.ACCESS_FINE_LOCATION : 精确位置的权限,真机必须加的

	android.permission.ACCESS_COARSE_LOCATION : 大概位置的权限,真机必须加的

	服务Service有开启关闭,加一下onDestroy方法,即在服务关闭时可以去取消定位



	将定位移植到GPSService后,到SmsReceiver接收到指令后开启服务



		//判断短信的内容是否是指令

		if ("#location#".equals(body)) {//避免空指针异常

			//GPS追踪

			System.out.println("GPS追踪");

			

			//定位操作,服务

			//1.开启服务

			Intent service_intent  = new Intent(context,GPSService.class);

			context.startService(service_intent);

			//2.发送定位坐标

			//2.1获取保存的经纬度坐标

			String longitude = sp.getString("longitude", "");

			String latitude = sp.getString("latitude", "");

			//2.2发送坐标

			if (!TextUtils.isEmpty(longitude) && !TextUtils.isEmpty(latitude)) {

				SmsManager smsManager = SmsManager.getDefault();//短信的管理者

				smsManager.sendTextMessage(sp.getString("safenum", "5556"), null, "longitude:"+longitude+" latitude:"+latitude, null, null);

			}

			abortBroadcast();

		}



		intent处需要一个上下文,在广播接收者中上下文是context,不能写成this,否则报错

		然后通过context去开启服务



		四大组件创建完后都要配置,拷贝GPSService全类名过来:

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

		    </service>



		打开DDMS的Emulator Control,用SMS发送一个指令"#location#",点击Send

		打印出来了 "最佳定位方式:gps",表明service已开启成功



		在设置中的RUNNING处查看下,手机卫士西安02处的下边有一行小字,写着“1 process and 1 service”,开启了一个service,点开看下是GPSService,表明Service已开启成功



		接下来就可以找到SmsReceiver,在这里边就可以去发送这个定位坐标了



	在SmsReceiver广播中发送定位坐标,定位是在服务GPSService中定位的

	这个坐标也是在服务GPSService中获取的,要在SmsReceiver广播里去发送定位坐标,

	这个定位坐标拿不到,所以应该在GPSService服务中,用sp保存一下坐标

	然后在SmsReceiver广播中发送时,去sp中获取下,然后再去发送,就可以了



	在GPSService中的onCreate方法中,获取下SharedPreferences:

		private SharedPreferences sp;

		sp = getSharedPreferences("config", MODE_PRIVATE);

	 紧接着在GPSService的MyLocationListener中保存经纬度坐标:

	

		//保存经纬度坐标

		Editor edit = sp.edit();

		edit.putString("longitude", longitude+"");

		edit.putString("latitude", latitude+"");

		edit.commit();



 	经纬度坐标也保存成功就可到SmsReceiver中发送定位坐标了,这里也需要一个sp,在onReceive中获取出来:

		private SharedPreferences sp;

		sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);

	

	紧接着就可以从sp中获取保存的经纬度坐标:



		//2.发送定位坐标

		//2.1获取保存的经纬度坐标

		String longitude = sp.getString("longitude", "");

		String latitude = sp.getString("latitude", "");

		//2.2发送坐标

		if (!TextUtils.isEmpty(longitude) && !TextUtils.isEmpty(latitude)) {

			SmsManager smsManager = SmsManager.getDefault();//短信的管理者

			smsManager.sendTextMessage(sp.getString("safenum", "5556"), null, "longitude:"+longitude+" latitude:"+latitude, null, null);

		}



	发送坐标操作前边也讲过,直接从BootCompletedReceive中拷贝过来,这个其实就是发送短信	

	模拟器是英文的,不支持中文短信,所以只能发送一些英文短信



	第一次发送个指令开启了服务,开启服务定位和GPS卫星通讯需要1分,服务一直在后台运行着

	但是接下来的代码还是会执行的,紧接着就是获取经纬度坐标了,这时能获取出经纬度坐标吗?

	在获取时刚好获取完还没保存,你获取不到把一个空坐标发给安全号码没用

	所以简单判断一下,如果经纬度坐标不为空,那就发送经纬度

	这个操作,到这就做完了,但是这里有个小瑕疵

		用户第一次发送指令去定位坐标了,这时获取经纬度坐标获取出来是为空的字符串,那不会去发送短信,紧接着用户去发送第二条短信时,又去做定位操作,这时sp中有保存经纬度坐标吗?

			这时要还没有保存就完蛋了,第一次开启服务,这时去获取经纬度坐标,获取不出来是因为还没有保存

			那这个完了之后,用户又紧接着发送第二个指令了,发送第二个指令时还要走这个代码去开启服务定位

			第一次已经定位成功了,也就是说用户需要发送两次指令,才能定位坐标,这个就是一个问题

	有一个小瑕疵作为作业:

	如何能发送一次指令就能让用户收到定位坐标

4.11播放报警音乐# ***

手机丢失后定位手机坐标可确定小偷位置,播放报警音乐可根据声音确定小偷



	下边实现报警音乐:

		实现报警音乐要用到MediaPlayer,上课资料中准备了两个,alarm.wav,ylzs.mp3,原则是音乐片段要小,太大用户会不想下载app

		将ylzs.mp3拷贝到项目res的raw文件夹下,raw文件夹下的文件不会改动

		     用create方式创建MediaPlayer,参数需要context,还需要resid,设置成ylzs,调用start去播放

		运行程序打开ddms选中SMS,发送"#alarm#"指令,会打印“播放报警音乐”,听到开始播放月亮之上

		模拟器音量调到最小,再去发送指令,同样会打印“播放报警音乐”,但听不到音乐,因为把音量变成最小

		要在播放之前将系统音量调到最大

		系统中音量控制有“Music,video,games&other media”,还有通话,通知音量(Ringtone&notifications)

		还有一个闹钟(Alarms)音量,要用的是“Music,video,games&other media”这个音量

		首先获取音量管理者AudioManager,同样通过context.getSystemService获取:

		设置系统音量方法setStreamVolume(streamType,index,flags)

		参数streamType设置音量类型,选成STREAM_MUSIC,index设置音量大小,0最小,15最大

		可以写成15,但有些程序员不知道最大音量是15,audioManager有个获取最大音量方法getStreamMaxVolume(streamType),第三个参数flags指定信息,一般写成0就可以



		运行程序发送"#alarm#"指令可以播放,在这个基础上再重复发送指令就会出现重复播放



	重复发送指令出现重复播放问题

		原因是发送一条指令就会create一个MediaPlayer

		SmsReceiver每接收一个广播就会重新new一个广播接收者,执行完onReceiver就会销毁,涉及生命周期

		每发送一条指令相当于接收一条广播,会new出一个广播接收者,广播接收者中MediaPlayer又会执行播放

	

		解决:把MedioPlayer声明成成员变量并加static静态

		重新接收到广播后又会new出一个广播接收者,相当于还是创建了一个MediaPlayer,和写成局部变量没啥区别,static相当于把MediaPlayer放到静态内存中,静态内存和普通内存是区分开的

		播放报警音乐前判断mediaplayer如果不等于null,mediaPlayer中有release()释放操作



			private static MediaPlayer mediaPlayer;//把mediaplayer放到静态内存中

	

			if("#alarm#".equals(body)){

				//在播放之前,将系统的音量调整到最大

				//1.获取音量的管理者

				AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

				//设置系统的音量

				//streamType : 音量的类型

				//index : 音量的大小   0最小      15最大

				//flags : 指定的信息

				//getStreamMaxVolume : 获取最大音量  streamType : 音量的类型

				audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);

				//播放报警音乐

				if (mediaPlayer != null) {

					mediaPlayer.release();//释放资源

				} 

				mediaPlayer = MediaPlayer.create(context, R.raw.ylzs);

				//播放音乐

				mediaPlayer.start();

				System.out.println("播放报警音乐");

				abortBroadcast();

			}



		运行程序再发送指令,重复发送指令就把上一个音乐给关闭掉重新播放新音乐,把这个音量变小再发送指令,又可以把音量变成最大了,说明调整系统音量操作也做完了

4.12.超级管理员权限# ***

远程锁屏一键锁屏的实现

	物理按键实现解锁和加锁容易磨损失灵,市面上专门有软件实现“一键锁屏”



	创建项目一键锁屏

		超级管理员权限与root权限区别:

			root权限: linux最高权限,android以liunx为内核开发,所以root权限也是android最高权限

					  使用root可以删除任何程序,但对手机损伤比较大,不建议root

			超级管理员权限: 做一些比较危险的操作,锁屏、删除数据等,超级管理员权限比root权限小,不能卸载应用



		1)根据api文档实现获取超级管理员权限

			adt-bundle-windows-x86_64_20140101/sdk/docs下的index.html



		device_admin_sample.xml中就是超级管理员能执行的操作,比如密码操作(reset-password),锁屏操作(force-lock),禁用摄像头操作(disable-Camera)



		2)获取了超级管理员权限必须去激活,否则会报“Caused by:java.ang.SecurityExecption:no active adimin owned by uid 10047 for policy”

	

		模拟器设置中心Settings/Security/Device administrators里边有“一键锁屏”项目,点击一键锁屏看到和api的demo屏幕一样,点击Activate即可手动激活

		这时再去点击一键锁屏就锁屏了,说明超级管理员已获取成功



		3)在“一键锁屏”项目MainActivity的onCreate添加if判断,isAdminActive判断超级管理员是否激活

			需要一个ComponentName(pkg,cls)获取admin组件标示,参数pkg是上下文,参数cls是组件class文件,如果已经激活,那就锁屏

			运行程序直接锁屏了,解锁后展示的手机桌面,再点击app又锁屏了,解锁后展示的是app内部页面

			市面软件一键锁屏app解锁后直接回到了桌面

			在判断超级管理员激活的锁屏代码下边调用finish()即可



		4)很多用户不知道怎么手动激活,市面软件会在一键锁屏app中有个按钮,在activity_main.xml中再添加一个按钮,添加点击事件ditivate,在MainActivity中创建出ditivate方法



			在这个方法中通过intent来代码激活超级管理员



		手动将超级管理员权限取消,运行程序点击“点击激活超级管理员”按钮直接跳转到模拟器设置中心的超级管理员激活界面了,点击激活又返回了app内部,点击“一键锁屏”按钮就锁屏了



		app激活超级管理员权限后,app将无法卸载,用户体验差



		5)注销超级管理员权限 

			再来一个Button,text为"注销激活超级管理员,即可卸载",添加onClick为delete,在MainActivity中实现点击方法delete就可以注销超级管理员了

			判断超级管理员有没有激活,如果已经激活了,调用removeActiveAdmin()注销



			运行程序,点击“注销激活超级管理员,即可卸载”,发现app可以卸载了

	-----------------------------------------------------



	将实现超级管理员权限功能的代码嵌入手机卫士项目后的完整代码:



	1.获取超级管理员,创建一个广播接受者



	Admin.java

	 							

	package cn.itcast.mobilesafexian02.receiver;



		import android.app.admin.DeviceAdminReceiver;

		public class Admin extends DeviceAdminReceiver {

		}



		这个广播接收者创建出来即可,里边什么都不用写

	2.清单文件配置



	清单文件AndroidManifest.xml中:



		<receiver

            android:name="com.example.yijiansuoping.Admin"

            android:description="@string/sample_device_admin_description"

            android:label="@string/sample_device_admin"

            android:permission="android.permission.BIND_DEVICE_ADMIN" >

            <meta-data

                android:name="android.app.device_admin"

                android:resource="@xml/device_admin_sample" />

            <intent-filter>

                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />

            </intent-filter>

        </receiver>

	3.res -> xml -> device_admin_sample.xml

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

		<device-admin xmlns:android="http://schemas.android.com/apk/res/android">

		  <uses-policies>

		    <limit-password />

		    <watch-login />

		    <reset-password />

		    <force-lock />

		    <wipe-data />

		    <expire-password />

		    <encrypted-storage />

		    <disable-camera />

		  </uses-policies>

		</device-admin>

	4.使用

	public class SmsReceiver extends BroadcastReceiver {

	

		private DevicePolicyManager devicePolicyManager;

		private ComponentName componentName;

		

		@Override

		public void onReceive(Context context, Intent intent) {

			

			//1.获取设备的管理者

			devicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);

			componentName = new ComponentName(context, Admin.class);



			if("#wipedata#".equals(body)){

					//远程删除数据

					if (devicePolicyManager.isAdminActive(componentName)) {

						devicePolicyManager.wipeData(0);//跟恢复出厂设置相似

					}

					abortBroadcast();

				}else if("#lockscreen#".equals(body)){

					//远程锁屏

					System.out.println("远程锁屏");

					if (devicePolicyManager.isAdminActive(componentName)) {

						devicePolicyManager.lockNow();

					}

					abortBroadcast();

				}

	//			abortBroadcast();//拦截短信,android原生系统中可以实现,但是在国产的深度定制系统中可能不太好使,小米

			}

		}



	如上将“一键锁屏”项目MainActivity代码拷贝到手机卫士SmsReceiver中并修改

	运行程序,因为没有将用代码激活超级管理员权限的代码嵌入到手机卫士中

	要想使用超级管理员权限必须手动激活再实现锁屏操作,打开DDMS用短信发送指令"#lockscreen#",看到模拟器直接锁屏了,远程锁屏操作就已经实现了



	远程删除数据的实现:

	   将超级管理员权限的判断添加到远程删除数据处,如果超级管理员权限激活了就调用wipeData(flags)删除数据,同样里边需要指定信息,来个0即可

	   远程删除数据操作相当于恢复出厂设置,写完远程删除数据或远程锁屏记得添加对发件人sender的判断

		判断发件人sender是不是安全号码,是才执行相应操作,否则谁要想恶搞给你发送一个远程删除数据指令,手机直接会恢复出厂设置,这里没添加判断,不要乱用真机调试



	运行程序,打开DDMS,发送"#wipedata#",模拟器接收到短信指令后用关机效果来模拟远程删除数据,相当于恢复出厂设置

4.13.总结#

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值