手机卫士-06
课1
高级工具中归属地查询的优化
实现电话号码的查询:eg 0759 把address中的data2数据
在LocationDao.java中继续添加代码,配合数据库和正则表达式进行查询并返回值 //判断当前的电话号码是手机还是座机:1、如果是手机电话号码1[3\5\7\8]9,2、电话区号0,还有110之类,使用switch来判断位数,以此对号码进行分类查询 需要使用正则表达式来匹配手机号码的规则 老师资料里有正则表达式的资料 手机电话号码1[3\5\7\8]9前三位的出现的情况:^1[3578]\d9$ 电话号码0 代码number(string).matchs(正则表达式,而且注意转义字符)
LocationDao.java
public class LocationDao {
private static SQLiteDatabase db;
/**
* 返回地理位置
*
* @param number
* 查询的电话号码
* @return
*/
public static String getLocation(String number) {
String location = number;
// 获取到Database
// 第一个参数是数据库的路径,第二个参数是工厂默认不要。第三个参数是标记。设置只读
db = SQLiteDatabase.openDatabase(
"/data/data/com.itheima.mobile47/files/address.db", null,
SQLiteDatabase.OPEN_READONLY);
//判断当前的电话号码是手机还是
/**
* 如果是手机电话号码:
*
*
*/
//由于需要转义。所以必须2个斜杠
if(number.matches("^1[3578]\\d{9}$")){
Cursor cursor = db.rawQuery(
"select location from data2 where id =( select outkey from data1 where id = ?) ",
new String[] { number.substring(0, 7) });
if(cursor.moveToNext()){
location = cursor.getString(0);
}
cursor.close();
return location;
}else{
/**
* 电话区号都是0开头
* 110 警察
* 120 急救
*/
switch (number.length()) {
case 3:
if(number.equals("110")){
location = "警察";
}
if(number.equals("120")){
location = "急救";
}
break;
case 4:
return "模拟器";
case 5:
if(number.equals("10086")){
location = "移动客服";
}
if(number.equals("10000")){
location = "电信客服";
}
default:
if(number.startsWith("0") && number.length()>6){
Cursor cursor = db.rawQuery("select location from data2 where area = ?", new String[]{number.substring(1, 3)});
if(cursor.moveToNext()){
location = cursor.getString(0);
}
cursor.close();
cursor = db.rawQuery("select location from data2 where area = ?", new String[]{number.substring(1, 4)});
if(cursor.moveToNext()){
location = cursor.getString(0);
}
cursor.close();
break;
}
}
return location;
}
}
}
在打电话时的界面出现(显示)归属地查询的结果 CallsafeService.java中拨打电话时加入toast的代码 因此在来电时显示归属地
模仿金山实现来电时显示归属的自定义小框框
课2
把金山的资源拿过来,背景颜色 settingCenterActivity.class进行代码设置 电话归属地的设置,添加布局内容:activitysettingcenter.xml
activitysettingcenter.xml
<RelativeLayout
android:onClick="changeStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:clickable="true"
android:background="@drawable/list_selector">
<TextView
android:id="@+id/tv_adress_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="归属地提示框风格"
android:textColor="#000"
android:textSize="20sp"/>
<TextView
android:id="@+id/tv_style_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_adress_style"
android:text="苹果绿"
android:textColor="#77000000"
android:textSize="18sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@drawable/address_style" />
</RelativeLayout>
给控件设置一个监听器settingviewshowaddress控件
settingCenterActivity.class
private String[] items = {"苹果绿","古德白","脑残粉","武藤蓝","金属灰"};
/**
* 修改归属地风格
* @param view
*/
public void changeStyle(View view){
AlertDialog.Builder builder = new Builder(this);
//设置icon图标
builder.setIcon(R.drawable.main_icon);
builder.setTitle("归属地提示风格");
//设置单选的事件
builder.setSingleChoiceItems(items, SharedPreferencesUtil.getInt(SettingCenterActivity.this, "which", 0), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
tv_style_name.setText(items[which]);
SharedPreferencesUtil.saveInt(SettingCenterActivity.this, "which", which);
dialog.dismiss();
}
});
builder.show();
}
新建服务:ShowLocationService.java 当点击事件后,就开启服务 ShowLocationService.java(展示地理位置的服务(电话归属地)) 该服务时在拨打电话时进行归属地显示:在电话响的时候就调用LocationDao的方法获得地理位置 然后设计窗口去把归属地展示在界面中 查看系统土司的源码是怎么实现的 查看土司中注入的布局资源文件的源码 然后继续观察土司的源代码,发现也在使用了远程的服务 (难点) - 自定义类型toast的框框,然后在ShowLocationService.java使用
新建show_toast.xml布局文件,然后在ShowLocationService.java里使用,注入进去
show_toast.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:background="@drawable/call_locate_green"
android:gravity="center"
android:orientation="horizontal" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/main_icon" />
<TextView
android:id="@+id/tv_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="归属地"
android:textSize="24sp" />
</LinearLayout>
再继续观察toast的源码,学习怎么调用远程服务TN 把一段系统的源码复制过来
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
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.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
}
获取WindowManager,用来控制复制过来的系统源码,wm.addView(view);把系统的窗体挂载过来
然后测试时发现窗体只是显示出来,但隐藏不了。 继续观察源码如何隐藏 发现使用WindowManager的removeView方法去关闭窗口 然后调用LocationDao去获取归属地并赋值到窗口中的textView 然后实现框框的滑动效果 继续观察复制过来的源码:params.flags中的内容,把设置了的属性取消掉,还有修改一些别的属性,并给注入了框框xml的view加入touch事件。 然后在事件里如何改变框框的位置。(在wm(WindowManager)可以改变框框的位置updateViewLayout方法)
首先获取手触碰屏幕的x和y轴 (目的把框框的位置置为左上角) !!限定框框的移动范围!! 再获取复制过来的源码中框框的x和y轴(在源码块) 在手指按住屏幕移动的case里,判断框框的x和y轴是否有越出屏幕的框框范围,也就是说不能让自定义的小框框超出屏幕边界。
课3
继续实现手触摸屏幕的坐标(触摸前后的差值) 在把坐标值放在框框(源码变量)的x和y中 然后再不断更新view的位置 测试的时候虽然自定义toast的位置可以移动,但是只是小范围,因为params.graviry没设置好,因此设置完就ok了 课下好好消化一下ShowLocationService.java
ShowLocationService.java
public class ShowLocationService extends Service {
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
MyPhoneStateListener listener = new MyPhoneStateListener();
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
// 获取到窗体服务
wm = (WindowManager) getSystemService(WINDOW_SERVICE);
}
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
private WindowManager wm;
private int[] items = { R.drawable.call_locate_green,
R.drawable.call_locate_gray, R.drawable.call_locate_orange,
R.drawable.call_locate_blue, R.drawable.call_locate_white };
private class MyPhoneStateListener extends PhoneStateListener {
private View view;
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
// 闲置
case TelephonyManager.CALL_STATE_IDLE:
if (view != null) {
wm.removeView(view);
}
break;
// 响铃
case TelephonyManager.CALL_STATE_RINGING:
// 获取到归属地的位置
String address = LocationDao.getLocation(incomingNumber);
Toast.makeText(ShowLocationService.this, "归属地:" + address, 1)
.show();
view = View.inflate(ShowLocationService.this,
R.layout.show_toast, null);
int result = SharedPreferencesUtil.getInt(ShowLocationService.this, "which", 0);
view.setBackgroundResource(items[result]);
TextView tv_address = (TextView) view
.findViewById(R.id.tv_address);
// 把位置设置到textview
tv_address.setText(address);
//吐司的参数
final WindowManager.LayoutParams params = mParams;
//设置吐司的位置
params.gravity = Gravity.LEFT | Gravity.TOP;
//当手指离开屏幕的时候存的值
mParams.x = SharedPreferencesUtil.getInt(ShowLocationService.this, "lastx", 0);
mParams.y = SharedPreferencesUtil.getInt(ShowLocationService.this, "lasty", 0);
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;//吐司天生是没有焦点的
params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
wm.addView(view, params);
//设置吐司的触摸监听
view.setOnTouchListener(new OnTouchListener() {
private int startX;
private int startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
// 当手指按住屏幕的时候,调用的方法
case MotionEvent.ACTION_DOWN:
// 首先获取到我手指触摸到屏幕的x轴和y轴
// 得到按下x轴的位置
startX = (int) event.getRawX();
// 得到按下y轴的位置
startY = (int) event.getRawY();
System.out.println("x轴的位置:" + startX + "y轴的位置:"
+ startY);
break;
// 当手指按住屏幕移动的时候,调用的方法
case MotionEvent.ACTION_MOVE:
// 获取到当前的x轴和y轴的值
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
// 当前的x轴减去开始的x轴。当前的y轴减去开始的y轴。得到之间差值
int dx = (int) (newX - startX);
int dy = (int) (newY - startY);
mParams.x += dx;
mParams.y += dy;
//避免让吐司跑到外面去
if (params.x < 0) {
params.x = 0;
}
if (params.y < 0) {
params.y = 0;
}
// 当当前的view大于屏幕的宽度的时候。然后把view的宽度设置成屏幕的最大值
if (params.x > (wm.getDefaultDisplay().getWidth() - view
.getWidth())) {
params.x = wm.getDefaultDisplay().getWidth()
- view.getWidth();
}
if (params.y > (wm.getDefaultDisplay().getHeight() - view
.getHeight())) {
params.y = wm.getDefaultDisplay().getHeight()
- view.getHeight();
}
System.out.println("当前x轴的位置:" + newX + "当前y轴的位置:"
+ newY);
// 更新view的位置
wm.updateViewLayout(view, params);
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
// 当手指抬起的时候调用的方法
case MotionEvent.ACTION_UP:
SharedPreferencesUtil.saveInt(ShowLocationService.this, "lastx", (int)event.getRawX());
SharedPreferencesUtil.saveInt(ShowLocationService.this, "lasty", (int)event.getRawY());
break;
}
return true;
}
});
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
break;
}
super.onCallStateChanged(state, incomingNumber);
}
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
}
}
课下总结技巧,如何获得屏幕的设置信息
继续把移动框框后的手指抬起来的调用的方法:把坐标值存在sp中
继续修改框框的背景颜色
首先在设置中心里加上修改背景的条目:SettingCenterAvtivity.xml(归属地提示框风格) 把箭头素材复制到drawable里 设置箭头点击时调用的选择器address_style.xml
address_style.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/jiantou1_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/jiantou1_pressed" /> <!-- focused -->
<item android:drawable="@drawable/jiantou1_disable" /> <!-- default -->
</selector>
思考如何让该条目进行点击状态联动 在RelativeLayout里进行设置clickable、focusable 给其加上一个id,在activity里进行调用(点击事件) 在SettingCenterActivity.java里的changeStyle里控制框框的背景颜色:点击按钮后弹出对话框,选择背景颜色。对对应的选项进行背景配置操作 把选择了的值保存在sp中,并dismiss掉对话框 把选项缓存在sp中之后,就联动到选择条目中:修改SettingCenterAvtivity.xml中的tvstylename
继续在ShowLocationService.java去改变框框背景颜色:该变view的颜色 view.setBackgoundResource(xxx);
课4
介绍所有使用的开源项目的链接,所有炫酷效果的控件 https://github.com/Trinea/android-open-project
谷歌大会发布新的编程工具AndroidStudio
继续实现高级工具中的短信备份
介绍:手机备份时和把短信备份到云端,这就是功能需求 ToolsActivity.class 在activity_tools.xml继续实现布局
activity_tools.xml
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_selector"
android:clickable="true"
android:drawableLeft="@android:drawable/star_big_on"
android:enabled="true"
android:focusable="true"
android:onClick="smsbackup"
android:text="短信备份"
android:textSize="24sp" />
<ProgressBar
android:id="@+id/pb"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
在ToolsActivity.class里加上短信备份的方法smsbackup(View view)
ToolsActivity.class
private class TaskRunnable implements Runnable{
@Override
public void run() {
boolean result = SmsBackUpParser.getBackUp(ToolsActivity.this,pb);
if (result) {
Looper.prepare();
Toast.makeText(ToolsActivity.this, "备份成功", 0).show();
Looper.loop();
} else {
Looper.prepare();
Toast.makeText(ToolsActivity.this, "备份失败", 0).show();
Looper.loop();
}
}
}
/**
* 备份短信
*
* @param view
*/
public void smsbackup(View view) {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(1);
TaskRunnable task = new TaskRunnable();
newFixedThreadPool.execute(task);
}
ThreadPoolManager.java
public class ThreadPoolManager {
private ExecutorService service;
private ThreadPoolManager(){
int num = Runtime.getRuntime().availableProcessors();
service = Executors.newFixedThreadPool(num*2);
}
private static ThreadPoolManager manager;
public static ThreadPoolManager getInstance(){
if(manager==null)
{
manager= new ThreadPoolManager();
}
return manager;
}
public void addTask(Runnable runnable){
service.submit(runnable);
}
}
写一个备份短信的工具类
SmsBackupParser.java
public class SmsBackUpParser {
private static Cursor cursor;
static String CRYPT_SEED = "hoge";
public static boolean getBackUp(Context context, ProgressBar pb) {
// 判断当前是否有SD卡存在
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
// 在SD卡的根目录创建backup.xml用来保存短信
File file = new File(Environment.getExternalStorageDirectory(),
"backup.xml");
ContentResolver resolver = context.getContentResolver();
String smsUrl = "content://sms/";
Uri uri = Uri.parse(smsUrl);
cursor = resolver.query(uri, new String[] { "address", "date",
"body", "type" }, null, null, null);
XmlSerializer serializer = Xml.newSerializer();
int progress = 0;
try {
FileOutputStream os = new FileOutputStream(file);
serializer.setOutput(os, "utf-8");
// 获取到总共有多少条短信
String size = String.valueOf(cursor.getCount());
serializer.startDocument("utf-8", true);
serializer.startTag(null, "smss");
serializer.attribute(null, "size", size);
// serializer.setProperty("size", size);
pb.setMax(Integer.parseInt(size));
while (cursor.moveToNext()) {
serializer.startTag(null, "sms");
serializer.startTag(null, "address");
serializer.text(cursor.getString(0));
serializer.endTag(null, "address");
serializer.startTag(null, "date");
serializer.text(cursor.getString(1));
serializer.endTag(null, "date");
serializer.startTag(null, "body");
serializer.text(Crypto.encrypt(CRYPT_SEED,
cursor.getString(2)));
serializer.endTag(null, "body");
serializer.startTag(null, "type");
serializer.text(cursor.getString(3));
serializer.endTag(null, "type");
serializer.endTag(null, "sms");
progress++;
SystemClock.sleep(2000);
pb.setProgress(progress);
}
serializer.endTag(null, "smss");
serializer.endDocument();
os.flush();
os.close();
cursor.close();
} catch (Exception e) {
// TODO: handle exception
}
return true;
} else {
return false;
}
}
}
查看源码的provider里的短信provider的Url是什么,看清单文件,在找到短信的provider的源码观察静态代码块,知道了如何获取系统短信时,但是通过query来查询时发现需要了解系统存储的短信的数据库结构大概是什么,所以需要在存储空间中找出db来观察,观察后根据需要就在query里查询需要的短信部分组成数据:例如 Cursor cursor = resolver.query(uri,new String[]{"address,"data","body","type"}null,null,null);
把游标cursor搜出来的信息封装在自定义的短信信息类中:新建SmsInfo.java
SmsInfo.java
package com.itheima.mobile47.bean;
public class SmsInfo {
private String address;
private String date;
private String body;
private String type;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
把SmsBackupParser.java获取到的SmsInfo对象封装到list集合里。 然后通过xml来备份或json备份 获得短信内容后然后在smsbackupParser.java里继续备份短信,返回布尔值 然后备份好后发现xml文件里的body是明文,因此需求就是把短信的body加密 去github里搜text encrypt,文本加密的开源项目,老师已经down了下来准备给我们。
serializer.text(Crypto.encrypt(CRYPT_SEED,cursor.getString(2)));
在smsbackparser里继续使用加密开源代码 然后如何解密,看源代码例子
在短信备份成功后弹出土司,不太美观,所以我们使用progressBar改善下界面
在activity_tools.xml里加上布局代码 在toolsActivity.java里使用控件,然后在smsbackupparser.java里调用配合使用。 把老师准备的工具类线程池工具类调过来。然后在ToolsActivity.java里使用,去模拟短信很多的情况下让progressBar的滚动效果 looper、message、handler的讲解,看来是视频:重点