手机卫士day08

day08

  • 来电短信黑名单拦截

    • 演示金山卫士相关功能
    • 创建BlackNumberActivity
    • 布局文件

      <RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="50dp"
      android:background="#8866ff00" >
      
      <TextView
          android:id="@+id/textView1"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_centerVertical="true"
          android:layout_marginLeft="5dp"
          android:text="黑名单管理"
          android:textColor="#000"
          android:textSize="22sp" />
      
      <Button
          android:id="@+id/button1"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_alignParentRight="true"
          android:layout_centerVertical="true"
          android:layout_marginRight="5dp"
          android:text="添加" />
      

    • 数据库创建

      public class BlackNumberOpenHelper extends SQLiteOpenHelper {

          public BlackNumberOpenHelper(Context ctx) {
              super(ctx, "blacknumber.db", null, 1);//必须实现该构造方法
          }
      
          /**
           * 第一次创建数据库
           */
          @Override
          public void onCreate(SQLiteDatabase db) {
              // 创建表, 三个字段,_id, number(电话号码),mode(拦截模式:电话,短信,电话+短信)
              db.execSQL("create table blacknumber (_id integer primary key autoincrement, number varchar(20), mode integer)");
          }
      
          /**
           * 数据库升级
           */
          @Override
          public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      
          }
      }
      
    • 单元测试

      • 创建具备单元测试的Android项目, 拷贝清单文件的相关代码

        File->New->Project->Android Test Project

        <instrumentation
            android:name="android.test.InstrumentationTestRunner"
            android:targetPackage="com.itheima.mobilesafeteach" />
        
         <application>
            <uses-library android:name="android.test.runner" /> 
        </application>
        
    • 增删改查(crud)逻辑实现

      /**
       * 黑名单数据库封装
       * @author Kevin
       *
       */
      public class BlackNumberDao {
      
          private static BlackNumberDao sInstance;
          private BlackNumberOpenHelper mHelper;
      
          private BlackNumberDao(Context ctx) {
              mHelper = new BlackNumberOpenHelper(ctx);
          };
      
          /**
           * 获取单例对象
           * @param ctx
           * @return
           */
          public static BlackNumberDao getInstance(Context ctx) {
              if (sInstance == null) {
                  synchronized (BlackNumberDao.class) {
                      if (sInstance == null) {
                          sInstance = new BlackNumberDao(ctx);
                      }
                  }
              }
      
              return sInstance;
          }
      
          /**
           * 增加黑名单
           * @param number
           * @param mode
           */
          public void add(String number, int mode) {
              SQLiteDatabase db = mHelper.getWritableDatabase();
              ContentValues values = new ContentValues();
              values.put("number", number);
              values.put("mode", mode);
              db.insert("blacknumber", null, values);
              db.close();
          }
      
          /**
           * 删除黑名单
           * @param number
           */
          public void delete(String number) {
              SQLiteDatabase db = mHelper.getWritableDatabase();
              db.delete("blacknumber", "number=?", new String[] { number });
              db.close();
          }
      
          /**
           * 更新黑名单
           * @param number
           * @param mode
           */
          public void update(String number, int mode) {
              SQLiteDatabase db = mHelper.getWritableDatabase();
              ContentValues values = new ContentValues();
              values.put("mode", mode);
              db.update("blacknumber", values, "number=?", new String[] { number });
              db.close();
          }
      
          /**
           * 查找黑名单
           * @param number
           * @return
           */
          public boolean find(String number) {
              SQLiteDatabase db = mHelper.getWritableDatabase();
              Cursor cursor = db.query("blacknumber",
                      new String[] { "number", "mode" }, "number=?",
                      new String[] { number }, null, null, null);
      
              boolean result = false;
              if (cursor.moveToFirst()) {
                  result = true;
              }
      
              cursor.close();
              db.close();
              return result;
          }
      
          /**
           * 查找号码拦截模式
           * @param number
           * @return
           */
          public int findMode(String number) {
              SQLiteDatabase db = mHelper.getWritableDatabase();
              Cursor cursor = db.query("blacknumber", new String[] { "mode" },
                      "number=?", new String[] { number }, null, null, null);
      
              int mode = -1;
              if (cursor.moveToFirst()) {
                  mode = cursor.getInt(0);
              }
      
              cursor.close();
              db.close();
              return mode;
          }
      
          /**
           * 查找黑名单列表
           * @return
           */
          public ArrayList<BlackNumberInfo> findAll() {
              SQLiteDatabase db = mHelper.getWritableDatabase();
              Cursor cursor = db
                      .query("blacknumber", new String[] { "number", "mode" }, null,
                              null, null, null, null);
      
              ArrayList<BlackNumberInfo> list = new ArrayList<BlackNumberDao.BlackNumberInfo>();
              while (cursor.moveToNext()) {
                  String number = cursor.getString(0);
                  int mode = cursor.getInt(1);
                  BlackNumberInfo info = new BlackNumberInfo();
                  info.number = number;
                  info.mode = mode;
                  list.add(info);
              }
              cursor.close();
              db.close();
              return list;
          }
      
          /**
           * 黑名单对象
           * @author Kevin
           *
           */
          public class BlackNumberInfo {
              public String number;
              public int mode;
      
              @Override
              public String toString() {
                  return "BlackNumberInfo [number=" + number + ", mode=" + mode + "]";
              }
          }
      }
      
    • 增删改查单元测试

      public class TestBlackNumberDao extends AndroidTestCase {
      
          /**
           * 测试数据创建
           */
          public void testCreateDb() {
              BlackNumberOpenHelper helper = new BlackNumberOpenHelper(getContext());
              helper.getWritableDatabase();
          }
      
          /**
           * 测试增加黑名单
           */
          public void testAdd() {
              //添加100个号码,拦截模式随机
              Random random = new Random();
              for (int i = 0; i < 100; i++) {
                  int mode = random.nextInt(3) + 1;
                  if (i < 10) {
                      BlackNumberDao.getInstance(getContext()).add("1381234560" + i,
                              mode);
                  } else {
                      BlackNumberDao.getInstance(getContext()).add("138123456" + i,
                              mode);
                  }
              }
          }
      
          /**
           * 测试删除黑名单
           */
          public void testDelete() {
              BlackNumberDao.getInstance(getContext()).delete("13812345601");
          }
      
          /**
           * 测试更新黑名单
           */
          public void testUpdate() {
              BlackNumberDao.getInstance(getContext()).update("13812345600", 2);
          }
      
          /**
           * 测试查找黑名单
           */
          public void testFind() {
              boolean find = BlackNumberDao.getInstance(getContext()).find(
                      "13812345600");
              assertEquals(true, find);
          }
      
          /**
           * 测试查找黑名单拦截模式
           */
          public void testFindMode() {
              int mode = BlackNumberDao.getInstance(getContext()).findMode(
                      "13812345600");
              System.out.println("拦截模式:" + mode);
          }
      }
      
      • 使用命令行查看数据库文件

        1. 运行adb shell进入linux环境
        2. 切换至data/data/包名/databases
        3. 运行sqlite3 *.db,进入数据库
        4. 编写sql语句,进行相关操作.记得加分号(;)结束
        5. .quit退出sqlite,切换到adb shell
        
    • 介绍convertView的重用机制
    • 介绍ViewHolder的使用方法

      //使用static修饰内部类,系统只加载一份字节码文件,节省内存
      static class ViewHolder {
          public TextView tvNumber;
          public TextView tvMode;
      }
      }
      
      • 使用convertView和ViewHolder进行优化之后,重新使用traceview计算getView的执行时间,进行对比

      • 最终优化结果

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = null;
            ViewHolder holder = null;
            if (convertView == null) {
                view = View.inflate(BlackNumberActivity.this,
                        R.layout.list_black_number_item, null);
                System.out.println("listview创建");
        
                // viewHolder类似一个容器,可以保存findViewById获得的view对象
                holder = new ViewHolder();
                holder.tvNumber = (TextView) view.findViewById(R.id.tv_number);
                holder.tvMode = (TextView) view.findViewById(R.id.tv_mode);
                // 将viewHolder设置给view对象,保存起来
                view.setTag(holder);
            } else {
                view = convertView;
                holder = (ViewHolder) view.getTag();// 从view对象中得到之前设置好的viewHolder
                System.out.println("listview重用了");
            }
        
            BlackNumberInfo info = mBlackNumberList.get(position);
            holder.tvNumber.setText(info.number);
        
            switch (info.mode) {
            case 1:
                holder.tvMode.setText("拦截电话");
                break;
            case 2:
                holder.tvMode.setText("拦截短信");
                break;
            case 3:
                holder.tvMode.setText("拦截电话+短信");
                break;
            }
        
            return view;
        }
        
        static class ViewHolder {
            public TextView tvNumber;
            public TextView tvMode;
        }
        
    • 启动子线程在数据库读取数据

      当数据量比较大时,读取数据比较耗时,为了避免ANR,最好将该逻辑放在子线程中进行, 为了模拟数据量大时访问比较慢的情况,可以让线程休眠1-2秒后再加载数据
      
    • 加载中的进度条展示
    • 数据分批加载

      分批加载优势:避免一次性加载过多内容, 节省时间和流量
      sql语句: 
      select * from blacknumber limit 20 offset 0, 表示起始位置是0,加载条数为20, 等同于limit 0,20
      
      /**
       * 分页查找黑名单列表
       * 
       * @return
       */
      public ArrayList<BlackNumberInfo> findPart(int startIndex) {
          SQLiteDatabase db = mHelper.getWritableDatabase();
      
          Cursor cursor = db.rawQuery(
                  "select number,mode from blacknumber order by _id desc limit 20 offset ?",
                  new String[] { String.valueOf(startIndex) });
      
          ArrayList<BlackNumberInfo> list = new ArrayList<BlackNumberDao.BlackNumberInfo>();
          while (cursor.moveToNext()) {
              String number = cursor.getString(0);
              int mode = cursor.getInt(1);
              BlackNumberInfo info = new BlackNumberInfo();
              info.number = number;
              info.mode = mode;
              list.add(info);
          }
      
          cursor.close();
          db.close();
          return list;
      }
      
      /**
       * 获取黑名单数量
       * 
       * @return
       */
      public int getTotalCount() {
          SQLiteDatabase db = mHelper.getWritableDatabase();
          Cursor cursor = db.rawQuery("select count(*) from blacknumber", null);
      
          int count = 0;
          if (cursor.moveToNext()) {
              count = cursor.getInt(0);
          }
          cursor.close();
          db.close();
          return count;
      }
      
      --------------------------------------
      
      //监听listview的滑动事件
      lvList.setOnScrollListener(new OnScrollListener() {
      
      // 滑动状态发生变化
      // 1.静止->滚动 2.滚动->静止 3.惯性滑动
      @Override
      public void onScrollStateChanged(AbsListView view, int scrollState) {
          if (scrollState == SCROLL_STATE_IDLE) {
              //获取当前listview显示的最后一个item的位置
              int lastVisiblePosition = lvList.getLastVisiblePosition();
      
              //判断是否应该加载下一页
              if (lastVisiblePosition >= mBlackNumberList.size() - 1
                      && !isLoading) {
              int totalCount = BlackNumberDao.getInstance(
                      BlackNumberActivity.this).getTotalCount();
      
              //判断是否已经到达最后一页
              if (mStartIndex >= totalCount) {
                  Toast.makeText(BlackNumberActivity.this, "没有更多数据了",
                          Toast.LENGTH_SHORT).show();
                  return;
              }
      
              Toast.makeText(BlackNumberActivity.this, "加载更多数据...",
                      Toast.LENGTH_SHORT).show();
              System.out.println("加载更多数据...");
              initData();
              }
          }
      }
      
      -----------------------------------
      
      //加载数据
      private void initData() {
          pbLoading.setVisibility(View.VISIBLE);//显示进度条
          isLoading = true;
          new Thread() {
              @Override
              public void run() {
                  try {
                      Thread.sleep(2000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
      
                  // 第一页数据
                  if (mBlackNumberList == null) {
                      mBlackNumberList = BlackNumberDao.getInstance(
                              BlackNumberActivity.this).findPart(mStartIndex);
                  } else {
                      mBlackNumberList.addAll(BlackNumberDao.getInstance(
                              BlackNumberActivity.this).findPart(mStartIndex));
                  }
      
                  mHandler.sendEmptyMessage(0);
              }
          }.start();
      }
      
      -----------------------------------
      
      private int mStartIndex;//下一页的起始位置
      private boolean isLoading;// 表示是否正在加载
      
      private Handler mHandler = new Handler() {
          public void handleMessage(android.os.Message msg) {
              pbLoading.setVisibility(View.GONE);//隐藏进度条
              // 第一页数据
              if (mAdapter == null) {
                  mAdapter = new BlackNumberAdapter();
                  lvList.setAdapter(mAdapter);
              } else {
                  mAdapter.notifyDataSetChanged();//刷新adapter
              }
      
              mStartIndex = mBlackNumberList.size();
              isLoading = false;
          };
      };
      
    • 添加黑名单

      /**
       * 添加黑名单
       * 
       * @param view
       */
      public void addBlackNumber(View v) {
          AlertDialog.Builder builder = new AlertDialog.Builder(this);
          View view = View.inflate(this, R.layout.dialog_add_black_number, null);
      
          final AlertDialog dialog = builder.create();
          dialog.setView(view, 0, 0, 0, 0);
      
          final EditText etBlackNumber = (EditText) view
                  .findViewById(R.id.et_black_number);
          final RadioGroup rgMode = (RadioGroup) view.findViewById(R.id.rg_mode);
      
          Button btnOK = (Button) view.findViewById(R.id.btn_ok);
          Button btnCancel = (Button) view.findViewById(R.id.btn_cancel);
      
          btnOK.setOnClickListener(new OnClickListener() {
      
              @Override
              public void onClick(View v) {
                  String number = etBlackNumber.getText().toString().trim();
                  if (!TextUtils.isEmpty(number)) {
                      int checkedRadioButtonId = rgMode.getCheckedRadioButtonId();
                      int mode = 1;
                      // 根据当前选中的RadioButtonId来判断是哪种拦截模式
                      switch (checkedRadioButtonId) {
                      case R.id.rb_call:
                          mode = 1;
                          break;
                      case R.id.rb_sms:
                          mode = 2;
                          break;
                      case R.id.rb_all:
                          mode = 3;
                          break;
      
                      default:
                          break;
                      }
      
                      // 保存数据库
                      BlackNumberDao.getInstance(getApplicationContext()).add(
                              number, mode);
      
                      // 向列表第一个位置增加黑名单对象,并刷新listview
                      //注意: 分页查询时需要逆序排列,保证后添加的最新数据展示在最前面
                      BlackNumberInfo info = new BlackNumberInfo();
                      info.number = number;
                      info.mode = mode;
                      mBlackNumberList.add(0, info);
      
                      mAdapter.notifyDataSetChanged();
      
                      dialog.dismiss();
                  } else {
                      Toast.makeText(getApplicationContext(), "输入内容不能为空!",
                              Toast.LENGTH_SHORT).show();
                  }
              }
          });
      
          btnCancel.setOnClickListener(new OnClickListener() {
      
              @Override
              public void onClick(View v) {
                  dialog.dismiss();
              }
          });
      
          dialog.show();
      }
      
    • 删除黑名单
      holder.ivDelete.setOnClickListener(new OnClickListener() {

          @Override
          public void onClick(View v) {
              //从数据库中删除
              BlackNumberDao.getInstance(getApplicationContext()).delete(
                      info.number);
              //从内存列表中删除并刷新listview
              mBlackNumberList.remove(info);
              mAdapter.notifyDataSetChanged();
          }
      });
      
    • 创建黑名单拦截服务

      • 拦截短信逻辑实现

        逻辑类似手机防盗页面拦截特殊短信指令的代码, 只不过该广播是动态注册,不是静态注册. 动态注册的好处是可以随服务的开启或关闭来决定是否监听广播,而且在同等优先级的前提下,动态注册的广播比静态注册的更先接收到广播(可以通过打印日志进行验证)
        
    • 设置页面增加黑名单拦截开关

      通过此开关来开启和关闭服务, 逻辑类似来电归属地显示的开关
      
    • 短信拦截优化

      • 通过关键词智能拦截(介绍)

        • 金山卫士智能拦截简介
        • 金山卫士关键词数据库

          查看第四天资料,金山卫士apk解压文件,assets目录下找firewall_sys_rules.db, 该数据制定了短信和来电的拦截规则
          
          短信拦截规则: 根据关键词对短信内容进行过滤.
          比如fapiao
          
          //对短信内容进行关键词过滤
          String messageBody = msg.getMessageBody();
          if (messageBody != null && messageBody.contains("fapiao")) {
              abortBroadcast();
          }
          
      • 分词(介绍)

        单纯依靠关键词进行过滤有时会出现一些问题, 比如:
        laogong, nikan,wode toufapiaobupiaoliang....
        
        所以有时候会对每一句话进行分词处理,比如可以将上述语句先拆分成不同的词语:laogong,nikan,wode,toufa,piaobupiaoliang, 然后在这些词汇中对关键词再进行过滤
        
        lucene分词检索框架
        
      • 短信拦截的兼容性处理

        4.4以上系统手机对短信权限进一步限制,导致无法拦截短信,可以通过监听短信数据库的变化,及时删除最新入库的垃圾短信来实现短信拦截的目的. 为了避免误删旧的短信,需要和短信广播结合起来使用
        
    • 来电拦截
      1. 挂断电话的API早期版本endCall()是可以使用的,现在不可以用了;但本身挂断电话这个功能是存在的

      2. 很多服务都是获取远程服务的代理对象IBinder,再调用里面的方法的.例如:
              IBinder b = ServiceManager.getService(ALARM_SERVICE);
              IAlarmManager service = IAlarmManager.Stub.asInterface(b);
              return new AlarmManager(service);
      3. 于是我们跟踪TelephoneyManager,查看它的对象到底是如何创建的.我们跟踪到了这样一个方法: 
              private ITelephony getITelephony() {
              return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
              }
      该方法返回一个ITelephony对象, 查看ITelephony对象的方法,发现有endCall方法
      4. 于是我们将获取ITelephony的代码拷贝到自己的项目中,发现无法导包,因为根本有没有ServiceManager这个类,但我们知道它肯定存在,因为TelephonyManager就引用了该类,只不过android系统隐藏了这个类,
      5. 为了调用隐藏类的方法,我们想到了反射
      
      • 通过反射获取endCall方法

        /**
         * 挂断电话 
         * 注意加权限: <uses-permission
         * android:name="android.permission.CALL_PHONE"/>
         */
        public void endCall() {
            try {
                // 获取ServiceManager
                Class clazz = BlackNumberService.class.getClassLoader().loadClass(
                        "android.os.ServiceManager");
                Method method = clazz.getDeclaredMethod("getService", String.class);// 获取方法getService
                IBinder binder = (IBinder) method.invoke(null,
                        Context.TELEPHONY_SERVICE);// 方法时静态的,不需要传递对象进去
        
                ITelephony telephony = ITelephony.Stub.asInterface(binder);// 获取ITelephony对象,前提是要先配置好aidl文件
                telephony.endCall();//挂断电话
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        注意加权限: <uses-permission
          android:name="android.permission.CALL_PHONE"/>
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值