##Day11##
#11.1解锁操作# (发送广播的操作、服务中注册广播接受广播事件)
问题:打开API Demos时,弹出解锁密码,点击返回键退出API Demos,进入手机卫士时,本来应该显示手机卫士界面,界面却变成API Demos的解锁密码界面
解决:在退出API Demos时,API Demos的界面不可见了onStop(),让API Demos的解锁密码也不可见finish掉即可
在Watch Dog Activity.java中添加方法如下:
protected void onStop(){
super.onStop();
//让API Demos的解锁密码不可见
finish();
}
1.给button增加点击事件,在点击事件中判断密码输入是否正确,正确,finish输入密码界面,不正确提醒用户
首先,在Watch Dog Activity.java中点击布局文件activity_watchdog打开activity_watchdog.xml,给EditText控件添加id为ed_watchdog_password,然后给Button按钮设置点击事件unlock
然后在Watch Dog Activity.java中实现点击事件unlock方法,并将id ed_watchdog_password 初始化出来,即:
ed_watchdog_password = (EditText) findViewById(R.id.ed_watchdog_password)
public void unlock(View v){
//获取输入的密码
String password = ed_watchdog_password.getText().toString().trim();
//判断密码是否正确
if ("123".equals(password)) {
//关闭输入密码界面
finish();
}else{
Toast.makeText(getApplicationContext(), "密码错误", 0).show();
}
}
2.发现解锁之后,会重复显示输入密码界面()
原因:在服务中不断在监听当前打开的应用程序,判断包名在数据库中有没有,如果有 就重新走跳转到密码输入界面,所以就会不断弹出输入密码界面,解决办法:广播 (解锁后通知当前应用,不用再给加锁了,让activity给服务传递数据,有些同学会想到用startActivityforResult,但它是activity给activity传递数据用的,现在是activity给service传递数据,这个时候一般会用broadcast)
a.在点击事件中 增加发送广播操作
public void unlock(View v){
//获取输入的密码
String password = ed_watchdog_password.getText().toString().trim();
//判断密码是否正确
if ("123".equals(password)) {
//发送广播
Intent intent = new Intent();
//发送的广播事件 ""表示我这个广播是要进行解锁操作的
intent.setAction("content://cn.itcast.mobliesafexian02.unlock");
//上边接收到包名后,再将包名给服务传递回去,表示我这个打开的应用已经解锁了
intent.putExtra("packagename", packagename);
sendBroadcast(intent);
//关闭输入密码界面
finish();
}else{
Toast.makeText(getApplicationContext(), "密码错误", 0).show();
}
}
b.在服务中 注册广播进行接收
//注册广播接受者
unlockReceiver = new UnlockReceiver();
//设置过滤条件
IntentFilter intentFilter = new IntentFilter();
//设置接受的广播事件
intentFilter.addAction("content://cn.itcast.mobliesafexian02.unlock");
//注册广播接受者 有注册就有注销
registerReceiver(unlockReceiver, intentFilter);
private class UnlockReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//获取传递过来的包名
unlockPackageName = intent.getStringExtra("packagename");
}
}
然后在while循环进行判断 (如果包名不等于传递过来的包名)
//6.判断包名是否在数据库中
if (watchDogDao.queryLockApp(packageName)) {
//如果包名不等于传递过来的包名
if (!packageName.equals(unlockPackageName)) {
//跳转到密码输入界面
Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);
//给跳转到的activity指定一个任务栈
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//将包名传递给密码输入界面,用于显示图标和名称
intent.putExtra("packagename", packageName);
startActivity(intent);
}
}
最后在onDestroy方法中注销广播接收者
//如果广播接收者不为空(如果解锁接受者不为空,说明解锁了,那就达到目的了)
if(unlockReceiver !=null){
//注销广播接收者
unregisterReceiver(unlockReceiver);
//将广播接收者置为空
unlockReceiver = null;
}
3.增加锁屏之后,解锁,再次显示输入密码操作
//注册锁屏的广播接受者
screenOffReceiver = new ScreenOffReceiver();
//设置过滤条件
IntentFilter screenIntentFilter = new IntentFilter();
screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(screenOffReceiver, screenIntentFilter);
public class ScreenOffReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
unlockPackageName = null;
}
}
有注册就有注销,在onDestroy方法中注销:
if(screenOffReceiver != null){
unregisterReceiver(screenOffReceiver);
screenOffReceiver =null;
}
#11.2bug处理# (重点)
问题:打开应用程序,进入设置中心界面,以前都是点击退出键退出,现在不退出了,而是点击home键,然后打开API Demos 输入密码解锁后,弹出的是居然是设置中心界面(settingactivity界面),在点击退出,弹出的是功能列表界面(homeactivity界面),再返回,才是API Demos的界面
原因:我们打开的每一个应用程序都有一个任务栈,
① 比如我们首先是在桌面上,那么首先是“桌面的任务栈(launcher)”,
② 然后打开手机卫士,当前我们的任务栈就变成了“手机卫士的任务栈”,并且在launcher前面,
③ 然后打开“homeactivity界面”,那么在“手机卫士任务栈”中就有了“homeactivity”,然后打开“手机卫士界面”,就有了“手机卫士中设置中心(settingActivity)”在“手机卫士任务栈”中。
④ 这个做完之后,我们在点击home键,那么launcher任务栈又跑到了“手机卫士的任务栈”前边,
⑤ 这个时候,我们打开API Demos,就变成了API Demos任务栈,但是弹出输入密码键面,这个输入密码键面是属于手机卫士的,
⑥ 所以在“手机卫士的任务栈”中又有了一个watchdogactivity,这个时候我们看到的是watchdogactivity的界面,这就表明“手机卫士的任务栈”又跑到了前边,
⑦ 但是输入密码点击解锁之后,把watchdogactivity给finish掉了,所以弹出的是“手机卫士的任务栈”中的settingActivity界面,在把settingactivity退出,就是homeactivity界面了,再把homeactivity界面退出,就相当于把整个手机卫士应用退出了,就显示出API Demos界面了
解决:
当点击API Demos时,弹出的是watchdogactivity,这个watchdogactivity就直接放到了手机卫士的任务栈当中了,现在我们可以这样干:
让watchdogactivity单独占一个任务栈,即基础部分学的四种启动模式之一:singleInstance
此时 ⑤ 变成了 这个时候,我们打开API Demos,就变成了API Demos任务栈,但是弹出输入密码键面,这个输入密码键面是属于手机卫士的, 但是我们给watchdogactivity单独设置了一个任务栈,就变成了再apidemos任务栈前边有多了一个任务栈,这个任务栈是单独给watchdogactivity的
⑥ 点击解锁后,退出了watchdogactivity,这个任务也就没有了,所以弹出的是API Demos的界面
1. 4种启动模式 (重点)
android:launchMode="singleInstance"
<!-- 面试: 说一下android的四种启动模式以及它们的区别?
standard : 默认的启动
singleTop : 如果activity在任务栈顶部的,就不去创建新的
singleTask :如果任务栈中有activity,调用的时候,就会把,activity上部的其他activity给移除
singleInstance : 就是单独给一个activity设置一个任务栈
-->
但是还会有一个问题:
点击API Demos,输入密码解锁后进入了API Demos界面,然后点击home键,会弹出最近列表,其中有手机卫士应用,点击手机卫士应用,又变成了输入密码键面,
原因:
解决:如下2
2.从最近列表中移除应用显示
即在清单文件中对应的watchDogActivity配置中添加如下:
android:excludeFromRecents="true"
excludeFromRecents : 是否在最近列表中显示 true:不显示 false:显示 ,但是在清单文件中给哪个activity设置,activity调用打开的时候才会生效
#11.3解锁数据库优化操作# (重点)
问题:解决完上面的问题后,重新审视watchdogservice.java代码,发现还有一些问题,即开启线程时时刻刻监听用户打开的应用程序,并且我们在下边做了判断包名是否在数据库中的操作,即每隔500毫秒查询一次数据库,看下查询数据库中(queryLockApp)我们做了哪些操作,即-->获取数据库(getReadableDatabase),然后一直查询(query),再关闭数据库(close),每五百毫秒打开关闭一次数据库,是很耗费系统资源(cpu资源)的,所以一般我们不会这么干,前边我们在写数据的时候,还写过查询全部数据库,我们接下来就要用到这个查询全部数据库的操作了:
解决:
到watchdogservice.java中的run方法这里,调用一下查询全部的操作
//调用查询全部的操作,返回一个List集合(将数据库中的所有数据存放到集合中,也就是存放到内存中)
lockApps = watchDogDao.getAllLockApp();
并且在while循环中加入如下代码:
//查看包名是否包含在返回的集合中 得到一个boolean值
boolean islock = lockApps.contains(packageName);
//如果有的话,说明已经加锁了
//6.判断包名是否在数据库中
if (islock) {
好处:这样写,我只是始终打开子线程服务之后查询了一次数据库,之后一直将集合中的数据和打开应用程序的包名进行判断,看包名有没有,这样的操作可以解小打开关闭数据库的次数了,这个也是开发当中经常做的操作,即当遇到频繁的打开关闭数据库的操作的时候,将数据库中的所有数据存到集合里面,也就是说将原先一致查询数据库的操作改成将数据库的数据全部放到内存当中,然后在内存当中去进行查询操作,在内存当中查询比到数据库中查询要少消耗资源
问题:给api demos加锁是我们以前做的操作,但是如果打开软件管理,给三击加锁,这个时候点击三击应用,按理来说也应该弹出输入密码界面,但是却没有弹出
刚才我们将数据库中的所有数据存到了内存当中的集合,我们查询数据库的时候,三击的时候没有加锁,现在我们加锁了,但是数据库中的数据没有更新,所以接下来,我们还需要去更新集合了,即在加锁或者解锁的时候要更新集合,这里就要用到内容观察者了
判断一下,当加锁或者解锁的时候,去通知内容观察者内容发生变化了,注意,加锁解锁在哪里做比较合适,加锁的时候会调用添加包名的操作,所以可以在添加包名成功之后,去告诉内容观察者,数据已经变化,可以去更新数据了
1.在加锁或者解锁的时候通知内容观察者去更新数据 (写在watchdogdao.java中的添加包名中)
//告诉内容观察者,数据已经变化,可以去更新数据
ContentResolver resolver = context.getContentResolver();
Uri uri = Uri.parse("content://cn.itcast.mobliesafexian02.unlock.change");
//通知内容观察者,数据已经发生变化了
resolver.notifyChange(uri, null);
2.内容观察者就可以去执行更新操作了(写在watchdogservice.java中的oncreate方法中的子线程中)
//在加锁或者解锁的时候要更新集合,用内容观察者,查看到数据变化的时候会调用onChange方法
Uri uri = Uri.parse("content://cn.itcast.mobliesafexian02.unlock.change");
//更新操作 在内容解析者中注册内容观察者,参数:1:需要一个uri 2.notifyForDescendents 需要一个匹配 true或false都可以 3.observer :需要一个内容观察者 new一个内容观察者出来,在它的参数位置new一个Handler出来
getContentResolver().registerContentObserver(
uri, true, new ContentObserver(null) {
//实现onchange方法
public void onChange(boolean selfChange) {
//更新数据 将数据库中的所有数据存放到集合中,也就是存放到内存中
lockApps = watchDogDao.getAllLockApp();
};
});
#11.4获取短信的操作#
/**
* 获取短信
*/
public static void getAllSMS(Context context){
//1.获取内容解析者
ContentResolver resolver = context.getContentResolver();
//2.获取内容提供者地址sms sms表的地址
//3.生成查询的地址
Uri uri = Uri.parse("content://sms");
//4.查询数据
Cursor cursor = resolver.query(uri, new String[]{"address","date","type","body"}, null, null, null);
while(cursor.moveToNext()){
String address = cursor.getString(0);
String date = cursor.getString(1);
String type = cursor.getString(2);
String body = cursor.getString(3);
System.out.println("address:"+address+" date:"+date+" type:"+type+" body:"+body);
}
}
#11.5保存短信# (重点)
/**
* 获取短信
*/
public static void getAllSMS(Context context){
//1.获取内容解析者
ContentResolver resolver = context.getContentResolver();
//2.获取内容提供者地址sms sms表的地址
//3.生成查询的地址
Uri uri = Uri.parse("content://sms");
//4.查询数据
Cursor cursor = resolver.query(uri, new String[]{"address","date","type","body"}, null, null, null);
//1.xml序列器
XmlSerializer serializer = Xml.newSerializer();
try {
//2.文件保存位置及保存的编码格式
//os : 流信息
//encoding : 编码格式
serializer.setOutput(new FileOutputStream(new File("/mnt/sdcard/backup.xml")), "utf-8");
//3.设置文件头信息
//standalone : 是否已单独文件保存
serializer.startDocument("utf-8", true);
//4.根标签
serializer.startTag(null, "smss");
while(cursor.moveToNext()){
//5.设置每个短信的标签
serializer.startTag(null, "sms");
//6.设置每个数据的标签,成对出现
serializer.startTag(null, "address");
String address = cursor.getString(0);
//7.设置标签的文本内容
serializer.text(address);
serializer.endTag(null, "address");
serializer.startTag(null, "date");
String date = cursor.getString(1);
serializer.text(date);
serializer.endTag(null, "date");
serializer.startTag(null, "type");
String type = cursor.getString(2);
serializer.text(type);
serializer.endTag(null, "type");
serializer.startTag(null, "body");
String body = cursor.getString(3);
serializer.text(body);
serializer.endTag(null, "body");
System.out.println("address:"+address+" date:"+date+" type:"+type+" body:"+body);
serializer.endTag(null, "sms");
}
serializer.endTag(null, "smss");
serializer.endDocument();
//8.将数据刷新到的文件中
serializer.flush();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
#11.6进度条两种方式# (重点)
1.progressdialog
progressDialog = new ProgressDialog(this);
//设置不能取消
progressDialog.setCancelable(false);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
// progressDialog.setMax(max);//设置总进度,max:总进度
// progressDialog.setProgress(value);//设置当前进度,value :当前进度
progressDialog.show();
2.progerssbar,操作跟progressDialog相似
#11.7回调方法#(重点)
1.在处理的业务类中,编写一个接口,同时声明出要使用的方法
public interface BackUpSMSListener{
/**
* 设置总进度
* @param max
*/
void max(int max);
/**
* 设置当前进度
* @param progress
*/
void progress(int progress);
}
2.将接口以参数的形式,传递给要调用的业务方法,并在其中执行相关的接口的方法
public static void getAllSMS(Context context,BackUpSMSListener listener){
listener.max(count);
}
3.在调用业务方法的时候,在方法中实现接口操作,在实现方法中去实现相应的操作
SmsEngine.getAllSMS(getApplicationContext(),new BackUpSMSListener() {
@Override
public void progress(int progress) {
//progressDialog.setProgress(progress);
pb_atools_sms.setProgress(progress);
}
@Override
public void max(int max) {
//progressDialog.setMax(max);
pb_atools_sms.setMax(max);
}
});
#11.8还原短信#
/**
* 还原短信
* @param v
*/
public void restoresms(View v){
//内容解析者
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms");
ContentValues values = new ContentValues();
values.put("address", 95588);
values.put("date", System.currentTimeMillis()+"");
values.put("type", 1);
values.put("body", "zhuan zhang le $10000000000000000");
resolver.insert(uri, values);
}
#11.9可扩展的listview#
1.在布局文件中布局
<ExpandableListView
android:id="@+id/expandablelistview"
android:layout_width="match_parent"
android:layout_height="match_parent"
></ExpandableListView>
2.在代码中调用
expandablelistview = (ExpandableListView) findViewById(R.id.expandablelistview);
expandablelistview.setAdapter(new Myadapter());
adapter
private class Myadapter extends BaseExpandableListAdapter {
// 获取组的个数
@Override
public int getGroupCount() {
// TODO Auto-generated method stub
return 8;
}
// 获取每组有多少个孩子 groupPosition : 代表的组的位置,从0开始的
@Override
public int getChildrenCount(int groupPosition) {
// TODO Auto-generated method stub
return groupPosition+1;
}
// 获取组的样式
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
TextView textView = new TextView(getApplicationContext());
textView.setText(" 我是第"+groupPosition+"组");
return textView;
}
// 获取组的孩子的样式
//childPosition : 还在的位置
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
TextView textView = new TextView(getApplicationContext());
textView.setText("我是第"+groupPosition+"组的第"+childPosition+"个孩子");
textView.setTextColor(Color.RED);
return textView;
}
// 获取组的对象对应的数据
@Override
public Object getGroup(int groupPosition) {
// TODO Auto-generated method stub
return null;
}
// 获取组的孩子的对象的数据
@Override
public Object getChild(int groupPosition, int childPosition) {
// TODO Auto-generated method stub
return null;
}
// 获取组的id
@Override
public long getGroupId(int groupPosition) {
// TODO Auto-generated method stub
return 0;
}
// 获取组的孩子的id
@Override
public long getChildId(int groupPosition, int childPosition) {
// TODO Auto-generated method stub
return 0;
}
// 判断id是否稳定,判断有没有id ,有的true,没有的false
@Override
public boolean hasStableIds() {
// TODO Auto-generated method stub
return false;
}
// 设置孩子是否可以点击,false:可以
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
// TODO Auto-generated method stub
return false;
}
}
#11.10抽屉的效果# (重点)
1.第一种方式
<!-- handle : 抽屉的把手
content : 抽屉的内容
-->
<SlidingDrawer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:handle="@+id/handle"
android:content="@+id/content"
android:orientation="horizontal"
>
<LinearLayout
android:id="@+id/handle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/shenmabg"
/>
</LinearLayout>
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0000"
></FrameLayout>
</SlidingDrawer>
2.第二种方式
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="流量统计"
android:textSize="25sp"
android:gravity="center_horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:background="#8866ff00"
/>
</RelativeLayout>
<LinearLayout
android:layout_gravity="right"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0000"
></LinearLayout>
<LinearLayout
android:layout_gravity="left"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0000ff"
></LinearLayout>
</android.support.v4.widget.DrawerLayout>
代码中调用
dl = (DrawerLayout) findViewById(R.id.dl);
//打开左边的控件
dl.openDrawer(Gravity.LEFT);
//打开右边的控件
dl.openDrawer(Gravity.RIGHT);
#11.11杀毒发展史#
1.病毒:计算机程序,恶意行为,三类
1.恶搞,炫耀自己的技术,熊猫烧香,损人不利己
2.灰鸽子 : 盗号的,QQ号,游戏的账号
3.肉机:把你的电脑做成傀儡机,他通过程序控制你的电脑,
2.杀毒方式
1.黑名单方式,把一些已有病毒特征码保存到数据库,通过匹配数据库中特征进行操作,滞后性,裸机(没有任何防护的电脑,吸引病毒),蜜罐(网络的主干道上),云查杀:就是把你的电脑当成蜜罐,发现有病毒就上传
2.白名单,360,把已经检测过没问题的程序放到数据库中,当程序运行的时候,如果发现是在数据库中就不在去检测杀毒,可以及时的发现解决病毒
#11.12杀毒界面# (重点layer-list)
layer-list : framlayout,一个控件叠着另一个控件,在下面的显示的时候是在最上边
1.res-> drawable -> xxx.xml 注意:id不可以变
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@android:id/background"
>
<bitmap android:src="@drawable/security_progress_bg"
android:gravity="center" />
</item>
<item
android:id="@android:id/secondaryProgress"
>
<bitmap android:src="@drawable/security_progress"
android:gravity="center" />
</item>
<item
android:id="@android:id/progress"
>
<bitmap android:src="@drawable/security_progress"
android:gravity="center" />
</item>
</layer-list>
2.使用
<!-- progress : 设置progerssbar的进度 -->
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_antivirus_name"
android:layout_toRightOf="@+id/iv_antivirus_antivimageview"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:progressDrawable="@drawable/anti_progressbar_drawable"
android:progress="30"
android:layout_marginTop="5dp"
/>
#11.13扫描程序# (重点)
旋转动画 面试:动画有几类,有什么区别,每类有哪些操作,
//旋转动画
//参数1:开始的角度
//参数2:结束的角度
//剩下的参数:控制动画的位置
RotateAnimation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(2000);
//旋转的次数
rotateAnimation.setRepeatCount(Animation.INFINITE);//INFINITE : 一直旋转
//解决旋转停顿的问题
LinearInterpolator interpolator = new LinearInterpolator();
rotateAnimation.setInterpolator(interpolator);
iv_antivirus_scanner.startAnimation(rotateAnimation);
进度条及扫描效果的实现
/**
* 扫描程序
*/
private void scanner() {
//1.获取包的管理者
final PackageManager pm = getPackageManager();
tv_antivirus_name.setText("正在初始化64核扫描引擎...");
new Thread(){
public void run() {
//5.睡1秒,让程序延时1秒钟扫描
SystemClock.sleep(1000);
//2.获取安装的程序
List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
//3.设置progerssbar的总进度
pb_antivirus_progressbar.setMax(installedPackages.size());
//4.设置当前进度
int progress = 0;
for (PackageInfo packageInfo : installedPackages) {
//5.睡100毫秒,增加进度显示的真实性
SystemClock.sleep(100);
//4.设置当前进度
progress++;
pb_antivirus_progressbar.setProgress(progress);
//6.获取应用的名称
final String name = packageInfo.applicationInfo.loadLabel(pm).toString();
runOnUiThread(new Runnable() {
@Override
public void run() {
//7.设置扫描的文字
tv_antivirus_name.setText("正在扫描:"+name);
//9.设置在下方空白处显示扫描显示的应用成名称
TextView textView = new TextView(getApplicationContext());
textView.setText(name);
textView.setTextColor(Color.BLACK);
ll_antivirus_safeapk.addView(textView, 0);//index:将view添加到哪个位置
}
});
}
runOnUiThread(new Runnable() {
@Override
public void run() {
//8.设置扫描完成的文字和停止动画操作
tv_antivirus_name.setText("扫描完成,很安全");
//停止动画
iv_antivirus_scanner.clearAnimation();
}
});
};
}.start();
}