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¬ifications)
还有一个闹钟(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.总结#