手机卫士-06

手机卫士-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的讲解,看来是视频:重点

技术分享

技术分享

资料下载

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。