入门android开发


调节调试窗口字体:
https://blog.csdn.net/qq_32452623/article/details/52403725


1、单元测试(未代码验证)

报出如下错误日志打印:
java.lang.RuntimeException: Method setUp in android.test.AndroidTestCase not mocked. See http://g.co/androidstudio/not-mocked for details.

	at android.test.AndroidTestCase.setUp(AndroidTestCase.java)
	at junit.framework.TestCase.runBare(TestCase.java:139)
	at junit.framework.TestResult$1.protect(TestResult.java:122)
	at junit.framework.TestResult.runProtected(TestResult.java:142)
	at junit.framework.TestResult.run(TestResult.java:125)
	at junit.framework.TestCase.run(TestCase.java:129)
	at junit.framework.TestSuite.runTest(TestSuite.java:252)
	at junit.framework.TestSuite.run(TestSuite.java:247)
	at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:86)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)
	
Process finished with exit code -1

2、存储方式
ram:运行内存
rom:内部存储 /data/data/package_name
sd: 外部存储

3、QQ登录案例(本地存储 文件存储密码)

    private void writeData() {
        //用文件存储密码
        try {
            File file = new File("/data/data/com.gordon.junit/cache/pwd.txt");
            FileOutputStream fos = new FileOutputStream(file);
            //账号和密码
            String userName = "gordon";
            String passWord = "****";
            fos.write((userName+"&"+passWord).getBytes());
            fos.close();
            Toast.makeText(this,"success",Toast.LENGTH_SHORT).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(this,"FileNotFoundException",Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(this,"IOException",Toast.LENGTH_SHORT).show();
        }
    }

读取数据

private void readData() {
        File file = new File("/data/data/com.gordon.junit/cache/pwd.txt");
        API可替换File file = new File(this.getCacheDir(),"pwd.txt");
        API可替换File file = new File(getFilesDir(),"pwd.txt");
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
            String s = br.readLine();
            String[] split = s.split("&");
            Toast.makeText(this,"userName"+split[0],Toast.LENGTH_SHORT).show();
            br.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

输入框密文显示密码

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        密文
        android:inputType="textPassword" />

4、context 上下文
文件读写权限

5、QQ登录之API方式

读文件API
文件默认在files目录下
    public void qqLoginRead() {
        try {
            FileInputStream fis = this.openFileInput("config.txt");
            BufferedReader br = new BufferedReader(new InputStreamReader(fis));
            String s = br.readLine();
            String[] split = s.split("&");
            String userName = split[0];
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
写文件API
    public void qqLoginWrite() {
        try {
            FileOutputStream fos = this.openFileOutput("config.txt", 0);
            //账号和密码
            String userName = "gordon";
            String passWord = "****";
            fos.write((userName+"&"+passWord).getBytes());
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

6、QQ登录之流方式
文件在cache目录下

API可替换File file = new File(this.getCacheDir(),"pwd.txt");

文件在files目录下

API可替换File file = new File(getFilesDir(),"pwd.txt");

7、QQ登录之SD卡存储方式
写SD卡

    public void sdCardWrite() {
        File file = new File("/mnt/sdcard/waibu.txt");
        try {
            FileOutputStream fos = new FileOutputStream(file);
            String userName = "gordon";
            String passWord = "****";
            fos.write((userName+"&"+passWord).getBytes());
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

读SD卡

    public void sdCardRead() {
        File file = new File("/mnt/sdcard/waibu.txt");
        try {
            FileInputStream fis = new FileInputStream(file);
            BufferedReader br = new BufferedReader(new InputStreamReader(fis));
            String s = br.readLine();
            String[] split = s.split("&");
            String userName = split[0];
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

读写SD卡时需要在AndroidManifest.xml文件加权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

8、SD卡使用问题
1 判断是否有sd卡

    public void checkSdCard() {
        String state = Environment.getExternalStorageState();
        if(Environment.MEDIA_MOUNTED.equals(state)) {
            //插入了SD卡
        }else if(Environment.MEDIA_UNMOUNTED.equals(state)) {
            //拔出了SD卡
        }
    }

2 sd卡路径不是/mnt/sdcard 或者是/storage

    public void getSdCardAddress() {
        File file = Environment.getExternalStorageDirectory();
        String address = file.getAbsolutePath();
    }

3 可用空间等

    public void getSdCardFreeSpace() {
        File file = Environment.getExternalStorageDirectory();
        long freeSpace = file.getFreeSpace();
        //格式化大小
        String formatFileSize = Formatter.formatFileSize(this, freeSpace);
    }

9、QQ登录之SharedPreference

SharedPreference文件保存目录在/data/data/package_name/shared_
prefs
写文件:底层是map集合的方式存储数据

    /**
     * 1 获取sp
     * 2 拿到编辑器
     * 3 设置要存储的数据
     * 4 提交
     */
    public void qqLoginSPWrite() {
        SharedPreferences sp = this.getSharedPreferences("config", 0);
        SharedPreferences.Editor edit = sp.edit();
        edit.putString("qq","5057");
        edit.putString("pwd", "5057");
        edit.commit();
    }

读文件:

    public void qqLoginSPRead() {
        SharedPreferences sp = this.getSharedPreferences("config", 0);
        sp.getString("qq","123");
    }

用途:
1 存储密码 配置信息
2 自动添加后缀.xml

10、生成xml和解析
特点:
1 用标签存储数据
2 跨平台读取数据
生成xml文件:

    /**
     * 1 创建序列化器
     * 2 设置参数
     * 3 生成文件
     */
    public void xmlWrite() {
        XmlSerializer serializer = Xml.newSerializer();
        try {
            FileOutputStream fos = this.openFileOutput("student.xml", 0);
            serializer.setOutput(fos,"UTF-8");
            //生成xml文件
            serializer.startDocument("UTF-8", true);
            //根标签
            serializer.startTag(null, "stu");
            //姓名
            serializer.startTag(null, "name");
            serializer.text("gordon");
            serializer.endTag(null, "name");
            //学号
            serializer.startTag(null, "number");
            serializer.text("110");
            serializer.endTag(null, "number");
            serializer.endTag(null, "stu");
            serializer.endDocument();
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

解析xml文件:
解析xml文件的方式:
1 sax基于事件
2 dom&dom4j 用dom树解析
3 pull类似sax(效率高) android使用

    /**
     * 1 创建解析器
     * 2 设置参数
     * 3 解析
     */
    public void xmlRead() {
        try {
            XmlPullParser parser = Xml.newPullParser();
            FileInputStream fis = null;
            fis = openFileInput("student.xml");
            parser.setInput(fis,"UTF-8");
            int type = parser.getEventType();
            String name = null;
            String number = null;
            while (type != XmlPullParser.END_DOCUMENT) {
                String tag = parser.getName();
                switch (type){
                    case XmlPullParser.START_TAG:
                        if("name".equals(tag)) {
                            name = parser.nextText();
                            System.out.println(name);
                        }else if("number".equals(tag)) {
                            number = parser.nextText();
                            System.out.println(number);
                        }
                        break;
                    default:
                        break;
                }
                type = parser.next();
            }
            fis.close();
            //在此view设置数据
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

11、帧动画

12、国际化

13、Android数据存储方式 5种(按照数据存储的位置划分)

14、Sqlite数据库
轻量级、开源的、微小型数据库
创建文件:

    public void createFiles() throws IOException {
        //1 在内存中创建一个指向d盘的文件对象
        File file = new File("D:\\file.txt");
        //2 在硬盘上创建文件并写入内容
        FileOutputStream fos = new FileOutputStream(file);
        fos.write("hello".getBytes());
        fos.close();
    }

创建数据库:

public class MyDbOpenHelper extends SQLiteOpenHelper{

    public MyDbOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    public MyDbOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
        super(context, name, factory, version, errorHandler);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

1 创建一个类MyDbOpenHelper 继承 SQLiteOpenHelper
2 重写其构造方法

    public MyDbOpenHelper(Context context) {
        super(context, "student.db", null, 1);
    }

3 在view中调用

   1 在内存中创建数据库帮助类的对象
   MyDbOpenHelper helper = new MyDbOpenHelper(this);
   2 在磁盘上创建数据库文件
   helper.getWritableDatabase();

此时会在package_name/databases/下生成 student.db和student.db-journal两个文件
4 创建数据库表
第一次创建数据库的时候调用,适合初始化数据库的表
不检查类型和长度如:name varchar(20)

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table stu(_id integer primary key autoincrement, name varchar(20), number varchar(20))");
    }

只有当数据库版本升级后才会调用以下方法

	如将版本号变成2
  public MyDbOpenHelper(Context context) {
        super(context, "student.db", null, 2);
    }
此时会调用如下方法
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
	可修改数据库的表,如增加列
    }

问题:
1、此数据库不能降级 3->1,会出错
2、数据库版本号必须>=1(无效的参数异常)

5 操作数据库表(增、删、改、查)
sql语句:

insert into stu(‘name’, ‘name’) values(‘林青霞’, ‘100’)

select * from stu

update stu set name=‘liuyifei’ while

delete from stu while id=1

    public void insert() {
        MyDbOpenHelper helper = new MyDbOpenHelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL("insert into stu('name', 'number') values (?, ?)", new Object[]{"gordon", "110"});
        db.close();
    }

    public void delete() {
        MyDbOpenHelper helper = new MyDbOpenHelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL("delete from stu");
        db.close();
    }

    public void update() {
        MyDbOpenHelper helper = new MyDbOpenHelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL("update stu set name=?", new String[]{"gordon1"});
        db.close();
    }

    public void query() {
        MyDbOpenHelper helper = new MyDbOpenHelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        Cursor cursor = db.rawQuery("select * from stu", null);
        while (cursor.moveToNext()) {
            String _id = cursor.getString(0);//0: _id 1: name
        }
        db.close();
    }

6 api操作数据库:

    public void apiInsert() {
        MyDbOpenHelper helper = new MyDbOpenHelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("name", "xiaoshen");
        long id = db.insert("stu", null, values);
        if (id == -1) {
            //插入失败
        }else {

        }
        db.close();
    }

    public void apiDelete() {
        MyDbOpenHelper helper = new MyDbOpenHelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        int id = db.delete("stu", null, null);
        if(id == 0) {
            //删除失败
        }else {

        }
        db.close();
    }

    public void apiUpdate() {
        MyDbOpenHelper helper = new MyDbOpenHelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("name", "nimei");
        int id = db.update("stu", values, null, null);
        if(id == 0) {
            //更新失败
        }else {

        }
        db.close();
    }

    /**
     * table            表名
     * columns          要查询的列
     * selection        查询条件
     * selectionArgs    查询条件的占位符
     * groupBy          分组
     * having           条件
     * orderBy          排序
     */
    public void apiQuery() {
        MyDbOpenHelper helper = new MyDbOpenHelper(this);
        //加锁线程安全
        SQLiteDatabase db = helper.getWritableDatabase();
        //未加锁效率高
        //SQLiteDatabase readDb = helper.getReadableDatabase();
        Cursor cursor = db.query("stu", new String[]{"name", "num", "_id"}, null, null, null, null, null);
        while (cursor.moveToNext()) {
            String _id = cursor.getString(0);//0: _id 1: name
        }
        db.close();
    }

7 操作命令行进行增删改查
cmd

8 数据库事务
修改数据库要么同时成功,要么同时失败

    public void shiwu() {
        MyDbOpenHelper helper = new MyDbOpenHelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        try {
            db.beginTransaction();
            ContentValues values = new ContentValues();
            values.put("name", 0+1000000);
            db.update("stu", values, "name=?", new String[]{"kehu"});

            ContentValues values1 = new ContentValues();
            values1.put("name", 1000000-1000000);
            db.update("stu", values1, "name=?", new String[]{"yinhang"});
            db.setTransactionSuccessful();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            db.endTransaction();
        }
        db.close();
    }

15、ListView

16、网络编程

HTTP协议:
GET
Request URL:http://localhost:8080/web/LoginServlet?qq=123&pwd=asd
Request Method:GET
Status Code:200 OK
ccept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-CN,zh;q=0.8
Connection:keep-alive
Cookie:JSESSIONID=63630741907F82A937A9C96CCB43868E
Host:localhost:8080
Referer:http://localhost:8080/web/login.jsp
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36

POST
Request URL:http://localhost:8080/web/LoginServlet
Request Method:POST
qq=123&pwd=ASD
Status Code:200 OK
Remote Address:[::1]:8080
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:14
Content-Type:application/x-www-form-urlencoded
Cookie:JSESSIONID=63630741907F82A937A9C96CCB43868E
Host:localhost:8080
Origin:http://localhost:8080
Referer:http://localhost:8080/web/login.jsp
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36

1 打开tomcat服务器
2 webapps目录下zhbj查看图片
http://192.168.91.1:8080/zhbj/10008/1452327318UU92.jpg
3 点击检查->Network
4 访问一个网页
5 查看其请求头等信息

17、网络通信
1 写一个url
2 用这个url打开http连接
3 设置请求参数
4 获取状态码
2xx 响应成功 3xx重定向 4xx资源错误 5xx服务器错误
5 获取服务器返回的二进制输入流
注:在AndroidManifest.xml 添加网络权限

 <uses-permission android:name="android.permission.INTERNET"/>

直接操作流访问网络

    public void openUrl() {
        try {
            //1
            URL url = new URL("https://www.baidu.com");
            //2 打开http
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            //3 设置请求参数(默认是get)
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(3000);
            //4 获取状态码
            int code = conn.getResponseCode();
            if(code == 200) {
                //5 获取服务器返回的二进制输入流
                InputStream is = conn.getInputStream();
                //6 把流转换为位图对象
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                //7 显示在view
            }else {
                //请求失败
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

图片内存大小计算:
4=12808004
在windows下大小为603kb
android4.4在主线程操作网络异常(2.3.2无)
获取当前线程名

String currentThreadName = Thread.currentThread().getName();

开启子线程

       new Thread() {
            @Override
            public void run() {
                super.run();
                
            }
        }.start();

18、消息机制编写步骤

这里写图片描述

    //1 在主线程中创建Handler
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //3 在handlerMessage()中修改UI
            switch (msg.what) {
                case 1:
                    String s = String.valueOf(msg.obj);
                    break;
                default:
                    break;
            }
        }
    };
   public void start() {
        new Thread() {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 101; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //2 在子线程中用handler发消息
                    Message message = new Message();
                    message.obj = i;
                    mHandler.sendMessage(message);
                }
            }
        }.start();
    }

19、将流转换为文字(操作字符串–工具类—01)

public class StringUtils {

    /**
     * 从流转换为字符串
     * @param is 输入流
     * @return null 失败
     */
    public static String parseStream2Str(InputStream is) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int len = -1;
        byte[] buffer = new byte[1024*8];
        String str = null;
        try {
            while ((len = is.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
                str = new String(baos.toByteArray());
            }
            return str;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

对比:

     //2.0 在子线程中用handler发消息
      Message message = new Message();
      message.obj = i;
      mHandler.sendMessage(message);

      //2.1 从消息池中获取消息(效率高)
      Message msg = Message.obtain();
      msg.obj = "string";
      msg.what = 2;(区别哪个发送的)
      mHandler.sendMessage(msg);

Handler MessageQueue Message Looper关系:
任何带有界面的操作系统都运行在一个死循环中
用户操作时,用Handler发送一个消息给系统的MessageQueue消息队列,所有的消息在消息队列中排队,looper轮询器不断从消息队列中取出消息,给Handler的HandlerMessage方法,然后在这个方法中更新ui

20、Handler之api方式

public class ToastUtils {

    public void showToastInAnyThread(final Activity context, final String msg) {
        context.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

21、常见数据适配器

    public void setArrayAdapter() {
        /**
         * 1 context
         * 2 listview 条目布局文件
         * 3 参2布局文件中的控件
         * 4 string数组,data
         */
        lvArrayAdapter.setAdapter(new ArrayAdapter<String>(MainActivity.this, R.layout.array_adapter_list_item, R.id.tv_text, str));
    }

22、复杂ListView
23、访问网络
在values目录下创建一个config.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <String name="networkIp">http://www.baidu.com</String>
</resources>

网络请求工具类

public class NetService {

    public static List<NewsBean> requestNetwork(Context context) {
        try {
            String path = context.getResources().getString(R.string.networkIp);
            //1
//            URL url = new URL("https://www.baidu.com");
            URL url = new URL(path);
            //2 打开http
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            //3 设置请求参数(默认是get)
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(3000);
            //4 获取状态码
            int code = conn.getResponseCode();
            if(code == 200) {
                //5 获取服务器返回的二进制输入流
                InputStream is = conn.getInputStream();
                //解析xml文件

                //7 显示在view
            }else {
                //请求失败
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

24、展示图片
SmartImageView(开源框架)
https://github.com/JackCho/SmartImageView
25、自定义控件 SmartImageView

package widget;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.util.AttributeSet;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * author: Gordon
 * created on: 2017/12/5 23:11
 * description:
 */

public class SmartImageView extends android.support.v7.widget.AppCompatImageView {

    private static final int MSG_SUCC = 0;
    private static final int MSG_ERR = 1;
    private static final int MSG_ERR_CODE = 2;

    public SmartImageView(Context context) {
        super(context);
    }

    public SmartImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public SmartImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_SUCC:
                    //7 显示在view(在主线程中)
                    setImageBitmap((Bitmap) msg.obj);
                    break;
                case MSG_ERR:
                     Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
                    setImageBitmap(bitmap);
                    break;
                case MSG_ERR_CODE:
                    break;
            }
        }
    };

    public void setImageURL(final String path) {
        new Thread() {
            @Override
            public void run() {
                super.run();
                //获取网络数据
                URL url = null;
                try {
                    url = new URL(path);
                    //2 打开http
                    HttpURLConnection conn = (HttpURLConnection)url.openConnection();
                    //3 设置请求参数(默认是get)
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(3000);
                    //4 获取状态码
                    int code = conn.getResponseCode();
                    if(code == 200) {
                        //5 获取服务器返回的二进制输入流
                        InputStream is = conn.getInputStream();
                        //6 把流转换为位图对象
                        Bitmap bitmap = BitmapFactory.decodeStream(is);
                        Message message = Message.obtain();
                        message.obj = bitmap;
                        message.what = MSG_SUCC;
                        mHandler.sendMessage(message);
                        //7 显示在view(在主线程中)
                    }else {
                        //请求失败
                        Message errorMessage = Message.obtain();
                        errorMessage.obj = code;
                        errorMessage.what = MSG_ERR_CODE;
                        mHandler.sendMessage(errorMessage);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    mHandler.sendEmptyMessage(MSG_ERR);
                }
            }
        }.start();
    }
}

26、http之get/post请求提交数据到服务器

/**
     * GET:
     */
    private void requestNetWorkByGet(String userName, String passWord) {
        String path = "http://192.168.3.100:8080/web/Login?userName=+" + userName + "&passWord" + passWord;
        //获取网络数据
        URL url = null;
        try {
            url = new URL(path);
            //2 打开http
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            //3 设置请求参数(默认是get)
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(3000);
            //4 获取状态码
            int code = conn.getResponseCode();
            if(code == 200) {
                //5 获取服务器返回的二进制输入流
                InputStream is = conn.getInputStream();
                String textByGet = StringUtils.parseStream2String(is);
                //handler主线程显示数据
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

/**
     * POST:
     */
    private void requestNetWorkByPost(String userName, String passWord) {
        String path = "http://192.168.3.100:8080/web/Login";
        //获取网络数据
        URL url = null;
        try {
            url = new URL(path);
            //2 打开http
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            //3 设置请求参数(默认是get)
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(3000);
            //3.1 多了两个请求头
            String text = "userName=" + userName + "&passWord = " + passWord;
            conn.setRequestProperty("Content-Length", String.valueOf(text.length()));
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            //3.2 用二进制输入流提交数据
            conn.setDoOutput(true);//声明要给服务器提交数据了
            conn.getOutputStream().write(text.getBytes());
            //4 获取状态码
            int code = conn.getResponseCode();
            if(code == 200) {
                //5 获取服务器返回的二进制输入流
                InputStream is = conn.getInputStream();
                String textByPost = StringUtils.parseStream2String(is);
                //handler主线程显示数据
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

27、中文乱码问题
客户端和服务端所用的编码方式不同

28、开源框架httpclient向服务器提交数据
6.0之前移除了httpclient

    /**
     * 开源框架httpclient向服务器提交数据GET
     */
    private void requestNetWorkByHttpClientGet(String userName, String passWord) {
        String path = "http://192.168.3.100:8080/web/Login?userName=+" + userName + "&passWord" + passWord;
        //1 打开浏览器
        HttpClient client = new DefaultHttpClient();
        //2 输入网址
        HttpGet url = new HttpGet(path);
        //3 回车
        HttpResponse response = client.execute(url);
        //获取状态行
        StatusLine statusLine = response.getStatusLine();
        //获取状态码
        int code = statusLine.getStatusCode();
        if(code == 200) {
            HttpEntity entity = response.getEntity();
            InputStream is = entity.getContent();
            String textByPost = StringUtils.parseStream2String(is);
        } else {
            
        }
    }
/**
     * 开源框架httpclient向服务器提交数据POST
     */
    private void requestNetWorkByHttpClientPost(String userName, String passWord) {
        String path = "http://192.168.3.100:8080/web/Login";
        //1 打开浏览器
        HttpClient client = new DefaultHttpClient();
        //2 输入网址
        HttpPost url = new HttpPost(path);
        //发送post数据
        List<NameValuePair> parameters = new ArrayList<>();
        parameters.add(new BasicNameValuePair("userName", userName));
        parameters.add(new BasicNameValuePair("passWord", passWord));
        UrlEncodedFormEntity requestEntity = new UrlEncodedFormEntity(parameters, "utf-8");
        url.setEntity(requestEntity);
        //3 回车
        HttpResponse response = client.execute(url);
        //获取状态行
        StatusLine statusLine = response.getStatusLine();
        //获取状态码
        int code = statusLine.getStatusCode();
        if(code == 200) {
            HttpEntity entity = response.getEntity();
            InputStream is = entity.getContent();
            String textByPost = StringUtils.parseStream2String(is);
        } else {

        }
    }

29、开源框架Asynchttpclient向服务器提交数据

    /**
     * 开源框架Asynchttpclient向服务器提交数据GET
     */
    private void requestNetWorkByAsyncHttpClient(String userName, String passWord) {
        String path = "http://192.168.3.100:8080/web/Login?userName=+" + userName + "&passWord" + passWord;
        AsyncHttpClient client = new AsyncHttpClient();
        client.get(path, new AsyncHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                String str = new String(responseBody);
            }

            @Override
            public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
            }
        });
    }
    /**
     * 开源框架Asynchttpclient向服务器提交数据POST
     */
    private void requestNetWorkByAsyncHttpClientPost(String userName, String passWord) {
        String path = "http://192.168.3.100:8080/web/Login";
        AsyncHttpClient client = new AsyncHttpClient();
        //设置post数据
        RequestParams params =  new RequestParams();
        params.put("userName", userName);
        params.put("passWord", passWord);
        client.post(path, params, new AsyncHttpResponseHandler() {

            @Override
            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                
            }

            @Override
            public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {

            }
        });
    }

30、上传任意文件
(注:) Header[]导包一直不成功,未解决

            public void onSuccess(int i, org.apache.http.Header[] headers, byte[] bytes) {

            }`
/**
 * 1 创建对象
 * 2 设置要上传的文件
 * 3 调用post方法上传
 */

public void upload(String url) {
    //1 创建对象
    AsyncHttpClient client = new AsyncHttpClient();
    //2 设置要上传的文件
    RequestParams params = new RequestParams();
    String filePath = "/mnt/sdcard/12.txt";
    File file = new File(filePath);
    try {
        params.put("file", file);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    //3 调用post方法上传
    client.post(url, params, new AsyncHttpResponseHandler() {

        @Override
        public void onProgress(int bytesWritten, int totalSize) {
            super.onProgress(bytesWritten, totalSize);
            pbProgressBar.setMax(totalSize);
            pbProgressBar.setProgress(bytesWritten);
        }

        @Override
        public void onStart() {
            super.onStart();
        }

        @Override
        public void onFinish() {
            super.onFinish();
        }

        @Override
        public void onSuccess(int i, org.apache.http.Header[] headers, byte[] bytes) {

        }

        @Override
        public void onFailure(int i, org.apache.http.Header[] headers, byte[] bytes, Throwable throwable) {

        }
    });
}
31、多线程下载

private static int threadCount = 3;
private static String path = "http://192.168.25.76:8080/download/123.mp4";

/**
 * 多线程下载:
 * 1 获取服务器资源大小
 * 2 开启多个线程下载服务器对应的一块资源
 * 3 每个线程干完活,整个服务器资源下载完毕
 */
public void multiThreadDownLoad() {
    try {
        URL url = new URL(path);
        //2 打开http
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        //3 设置请求参数(默认是get)
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(3000);
        conn.setReadTimeout(3000);
        //4 获取状态码
        int code = conn.getResponseCode();
        // 1 获取服务器资源大小
        int fileLength = 0;
        if(code == 200) {
            fileLength = conn.getContentLength();
            //创建一个空文件
            RandomAccessFile raf = new RandomAccessFile("D:\\DOWNLOAD.tmp", "rw");
            raf.setLength(fileLength);
            raf.close();
        }
        //2 开启多个线程下载服务器对应的一块资源
        int blockSize = fileLength / threadCount;
        for (int threadId = 0; threadId < threadCount; threadId++) {
            int startIndex = threadId*blockSize;
            int endIndex = (threadId+1)*blockSize-1;
            //最后一个线程,修正结束位置
            if(threadId == threadCount - 1) {
                endIndex = fileLength - 1;
            }
            new DownLoadThread(startIndex, endIndex, threadId).start();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 获取文件名
 * @return
 */
static String getFilePath() {
    int index = path.lastIndexOf("/") + 1;
    return "D:\\" + path.substring(index);
}

static class DownLoadThread extends Thread {

    int startIndex;
    int endIndex;
    int threadId;

    public DownLoadThread(int startIndex, int endIndex, int threadId) {
        super();
        this.startIndex = startIndex;
        this.endIndex = endIndex;
        this.threadId = threadId;
    }

    @Override
    public void run() {
        super.run();
        //获取网络数据
        URL url = null;
        try {
            url = new URL(path);
            //2 打开http
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            //3 设置请求参数(默认是get)
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(3000);
            //重要,设置要请求的数据范围
            conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
            //4 获取状态码
            int code = conn.getResponseCode();
            if(code == 206) {
                //5 获取服务器返回的二进制输入流
                InputStream is = conn.getInputStream();
                //每个线程下载的文件
                RandomAccessFile raf = new RandomAccessFile(getFilePath(), "wr");
                int len = -1;
                byte[] buffer = new byte[1024 * 8];
                //重要:定位到每个线程开始写的位置
                raf.seek(startIndex);
                while ((len = is.read(buffer)) != -1) {
                    raf.write(buffer, 0, len);
                }
                raf.close();
            }
            //3 每个线程干完活,整个服务器资源下载完毕
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
32、多线程下载原理
![这里写图片描述](https://img-blog.csdn.net/20171210220401405?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ29yZG9uX3N1bg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

33、断点下载原理
断点时存储下载的位置,下一次下载前从上一次断点的位置开始下载

int lastDownPos += len;

34、多线程断点下载

    private static int threadCount = 3;
    private static String path = "http://192.168.25.76:8080/download/123.mp4";

    /**
     * 多线程下载:
     * 1 获取服务器资源大小
     * 2 开启多个线程下载服务器对应的一块资源
     * 3 每个线程干完活,整个服务器资源下载完毕
     */
    public void multiThreadDownLoad() {
        try {
            URL url = new URL(path);
            //2 打开http
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            //3 设置请求参数(默认是get)
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(3000);
            //4 获取状态码
            int code = conn.getResponseCode();
            // 1 获取服务器资源大小
            int fileLength = 0;
            if(code == 200) {
                fileLength = conn.getContentLength();
                //创建一个空文件
                RandomAccessFile raf = new RandomAccessFile("D:\\DOWNLOAD.tmp", "rw");
                raf.setLength(fileLength);
                raf.close();
            }
            //2 开启多个线程下载服务器对应的一块资源
            int blockSize = fileLength / threadCount;
            for (int threadId = 0; threadId < threadCount; threadId++) {
                int startIndex = threadId*blockSize;
                int endIndex = (threadId+1)*blockSize-1;
                //最后一个线程,修正结束位置
                if(threadId == threadCount - 1) {
                    endIndex = fileLength - 1;
                }
                new DownLoadThread(startIndex, endIndex, threadId).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取文件名
     * @return
     */
    static String getFilePath() {
        int index = path.lastIndexOf("/") + 1;
        return "D:\\" + path.substring(index);
    }

    /**
     * 获取断点进度的临时文件
     * @return
     */
    static String getTmpFilePath(int threadId) {
        return getFilePath()+threadId+".txt";
    }

    static class DownLoadThread extends Thread {

        int startIndex;
        int endIndex;
        int threadId;
        //断点位置
        int lastDownPos;

        public DownLoadThread(int startIndex, int endIndex, int threadId) {
            super();
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.threadId = threadId;
            lastDownPos = startIndex;//初始化
        }

        @Override
        public void run() {
            super.run();
            File tmpFile = new File(getTmpFilePath(threadId));
            if(tmpFile != null && tmpFile.length() > 0) {
                try {
                    FileInputStream fis = new FileInputStream(tmpFile);
                    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
                    String str = br.readLine();
                    //下载起始位置
                    startIndex = Integer.valueOf(str);
                    lastDownPos = startIndex;
                    br.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            //获取网络数据
            URL url = null;
            try {
                url = new URL(path);
                //2 打开http
                HttpURLConnection conn = (HttpURLConnection)url.openConnection();
                //3 设置请求参数(默认是get)
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(3000);
                conn.setReadTimeout(3000);
                //重要,设置要请求的数据范围
                conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
                //4 获取状态码
                int code = conn.getResponseCode();
                if(code == 206) {
                    //5 获取服务器返回的二进制输入流
                    InputStream is = conn.getInputStream();
                    //每个线程下载的文件
                    RandomAccessFile raf = new RandomAccessFile(getFilePath(), "wr");
                    int len = -1;
                    byte[] buffer = new byte[1024 * 8];
                    //重要:定位到每个线程开始写的位置
                    raf.seek(startIndex);
                    while ((len = is.read(buffer)) != -1) {
                        raf.write(buffer, 0, len);
                        //断点:存储断点的位置
                        lastDownPos += len;
                        RandomAccessFile rwd = new RandomAccessFile(getTmpFilePath(threadId), "rwd");
                        rwd.write(String.valueOf(lastDownPos).getBytes());
                        rwd.close();
                    }
                    raf.close();
                }
                //3 每个线程干完活,整个服务器资源下载完毕
                boolean isDelete = tmpFile.delete();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

35、移植java项目到android

自定义进度条
<?xml version="1.0" encoding="utf-8"?>
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pb_horizontal"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

</ProgressBar>
    ProgressBar pb = (ProgressBar) View.inflate(this, R.layout.progressbar, null);
    LinearLayout linearLayout = new LinearLayout(this);
    linearLayout.addView(pb);
    linearLayout.removeAllViews();

36、开源框架进行多线程断点下载(xUtils)
HttpUtils 进行文件下载

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EditText etPath = (EditText) findViewById(R.id.et_path);
        final TextView tvPb = (TextView) findViewById(R.id.tv_pb);
        final ProgressBar pb = (ProgressBar) findViewById(R.id.pb);
        Button btDownload = (Button) findViewById(R.id.bt_download);
        final String url = etPath.getText().toString().trim();
        btDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                HttpUtils httpUtils = new HttpUtils();
                httpUtils.download(url, "/mnt/sdcard/gui.exe", false, new RequestCallBack<File>() {
                    @Override
                    public void onSuccess(ResponseInfo<File> responseInfo) {
                        File file = responseInfo.result;
                        String path = file.getAbsolutePath();

                    }

                    @Override
                    public void onFailure(HttpException e, String s) {

                    }

                    @Override
                    public void onStart() {
                        super.onStart();
                    }

                    @Override
                    public void onLoading(long total, long current, boolean isUploading) {
                        super.onLoading(total, current, isUploading);
                        pb.setMax((int)total);
                        pb.setProgress((int) current);

                        int precent = (int) (current * 100 / total);
                        tvPb.setText(precent+"%");
                    }
                });
            }
        });
    }
}

37、意图设置action

    public void callPhone(View view) {
        //1 创建意图对象
        Intent intent = new Intent();
        //2 设置动作
        intent.setAction(Intent.ACTION_CALL);
        //3 设置数据
        intent.setData(Uri.parse("tel://10086"));
        //4 打开电话拨号界面
        startActivity(intent);
    }
添加拨打电话的权限
<uses-permission android:name="android.permission.CALL_PHONE"/>
注:在6.0之前没有问题,但是在6.0之后如果没有进行权限处理会报错
12-12 21:37:10.340 6763-6763/com.gordon.jcday06 E/AndroidRuntime: FATAL EXCEPTION: main
                                                                  Process: com.gordon.jcday06, PID: 6763
                                                                  java.lang.IllegalStateException: Could not execute method for android:onClick
                                                                      at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
                                                                      at android.view.View.performClick(View.java:5646)
                                                                      at android.view.View$PerformClick.run(View.java:22459)
                                                                      at android.os.Handler.handleCallback(Handler.java:761)
                                                                      at android.os.Handler.dispatchMessage(Handler.java:98)
                                                                      at android.os.Looper.loop(Looper.java:156)
                                                                      at android.app.ActivityThread.main(ActivityThread.java:6523)
                                                                      at java.lang.reflect.Method.invoke(Native Method)
                                                                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
                                                                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
                                                                   Caused by: java.lang.reflect.InvocationTargetException
                                                                      at java.lang.reflect.Method.invoke(Native Method)
                                                                      at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
                                                                      at android.view.View.performClick(View.java:5646) 
                                                                      at android.view.View$PerformClick.run(View.java:22459) 
                                                                      at android.os.Handler.handleCallback(Handler.java:761) 
                                                                      at android.os.Handler.dispatchMessage(Handler.java:98) 
                                                                      at android.os.Looper.loop(Looper.java:156) 
                                                                      at android.app.ActivityThread.main(ActivityThread.java:6523) 
                                                                      at java.lang.reflect.Method.invoke(Native Method) 
                                                                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942) 
                                                                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832) 
                                                                   Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.CALL dat=tel:xxxxxxxxxxxxx cmp=com.android.server.telecom/.components.UserCallActivity } from ProcessRecord{e27260f 6763:com.gordon.jcday06/u0a129} (pid=6763, uid=10129) requires android.permission.CALL_PHONE
                                                                      at android.os.Parcel.readException(Parcel.java:1665)
                                                                      at android.os.Parcel.readException(Parcel.java:1618)
                                                                      at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3097)
                                                                      at android.app.Instrumentation.execStartActivity(Instrumentation.java:1539)
                                                                      at android.app.Activity.startActivityForResult(Activity.java:4391)
                                                                      at android.support.v4.app.BaseFragmentActivityApi16.startActivityForResult(BaseFragmentActivityApi16.java:54)
                                                                      at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:67)
                                                                      at android.app.Activity.startActivityForResult(Activity.java:4335)
                                                                      at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:720)
                                                                      at android.app.Activity.startActivity(Activity.java:4697)
                                                                      at android.app.Activity.startActivity(Activity.java:4665)
                                                                      at com.gordon.jcday06.MainActivity.callPhone(MainActivity.java:25)
                                                                      at java.lang.reflect.Method.invoke(Native Method) 
                                                                      at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) 
                                                                      at android.view.View.performClick(View.java:5646) 
                                                                      at android.view.View$PerformClick.run(View.java:22459) 
                                                                      at android.os.Handler.handleCallback(Handler.java:761) 
                                                                      at android.os.Handler.dispatchMessage(Handler.java:98) 
                                                                      at android.os.Looper.loop(Looper.java:156) 
                                                                      at android.app.ActivityThread.main(ActivityThread.java:6523) 
                                                                      at java.lang.reflect.Method.invoke(Native Method) 
                                                                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942) 
                                                                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832) 

解决方法:
package com.gordon.jcday06;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button callPhone = (Button) findViewById(R.id.call_phone);
        callPhone.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
                } else {
                    callPhone();
                }
            }
        });
    }

    public void callPhone() {
        //1 创建意图对象
        Intent intent = new Intent();
        //2 设置动作
        intent.setAction(Intent.ACTION_CALL);
        //3 设置数据
        intent.setData(Uri.parse("tel://10086"));
        //4 打开电话拨号界面
        startActivity(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    callPhone();
                } else {
                    Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }
}

38、开启一个activity(隐式意图)

    public void secondActivity(View view) {
        Intent intent = new Intent();
        intent.setAction("ni mei a");
        startActivity(intent);
    }
 <activity android:name=".SecondActivity">
     <intent-filter>
         <action android:name="ni mei a"></action>
         //注:必须定义category
         <category android:name="android.intent.category.DEFAULT"/>
     </intent-filter>
 </activity>

如果清单文件设置了data,在代码中一定要设置data
    public void secondActivity(View view) {
        Intent intent = new Intent();
        intent.setAction("ni mei a");
        intent.setData(Uri.parse("suibian://"));
        startActivity(intent);
    }

        <activity android:name=".SecondActivity">
            <intent-filter>
                <action android:name="ni mei a"></action>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="suibian"/>
            </intent-filter>
        </activity>
        注:data和type不能共存
        intent.setData(Uri.parse("suibian://"));
        intent.setType("text/nihao");
显式意图
    public void secondActivity(View view) {
        Intent intent = new Intent(this, ThirdActivity.class);
        startActivity(intent);
    }

39、显式与隐式区别
显式:
隐式:可以跳到其他应用界面

40、 Intent跳转时传递数据(不可传递object类型)
数据类型:
八大基本类型
Serializable
Parcelable
bundle
Intent

第1页传入数据
    public void secondClick(View view) {
        Intent intent = new Intent(MainActivity.this, SecondActivity.class);
        intent.putExtra("name", "goudan");
        intent.putExtra("age", 16);
        startActivity(intent);
    }
第2页获取的数据
        Intent intent = getIntent();
        String name = intent.getStringExtra("name");
        int age = intent.getIntExtra("age", 20);
Bundle数据:
        Intent intent = new Intent(MainActivity.this, SecondActivity.class);
        Bundle bundle = new Bundle();
        bundle.putString("name", "gordon");
        bundle.putString("age", "20");
        intent.putExtra("b", bundle);
        startActivity(intent);
获取的数据
        Intent intent = getIntent();
        Bundle bundle = intent.getBundleExtra("b");
        String name = bundle.getString("name");
        String age = bundle.getString("age");

41、
URI : 统一资源标识符
URL :统一资源定位符 是URI的子集

42、activity销毁时传递数据

    //1 销毁时传递数据
    public void startActivityForResult(Intent intent) {
        startActivityForResult(intent, 1);
    }

    //3 等待返回的数据
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        String phoneNum = data.getStringExtra("phoneNum");
        etNum.setText(phoneNum);
    }

ConstactActivity.java

//2 返回的数据
Intent intent = new Intent();
intent.putExtra("phoneNum", constactNumber[position]);
setResult(10, intent);
finish();

43、activity的生命周期
onCreate
onStart
onResume
onPause
onStop
onDestroy

开启:onCreate onStart onResume
关闭:onPause onStop onDestroy
最小化:onPause onStop
最大化:onRestart onStart onResume

44、透明界面

<activity
android:name=".TranslucentActivity"            android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"></activity>

45、横竖屏切换的生命周期

onPasue onStop onDestroy

onCreate onStart onResume

固定屏幕朝向

android:screenOrientation="landscape" 横屏
android:screenOrientation="portrait"  竖屏
android:screenOrientation="sensor"

不敏感屏幕朝向

android:configChanges="orientation|keyboard|screenSize"

46、任务栈
1 标准模式

2 单一顶部模式:开启目标activity,系统会去任务栈的顶部查找,如果栈顶有复用,如果没有则创建新的

android:launchMode="singleTop"

应用:系统浏览器书签

3 单一任务模式
开启目标activity,系统会去整个任务栈查找,如果找到这个activity存在就清理这个activity上面的所有activity,如果没有就在栈顶创建一个实例对象

4 单一实例模式
开启目标activity,系统为这个activity单独创建任务栈
应用:系统来电界面

47、广播接收者

第1步:清单文件注册
<receiver
     android:name=".broadcast.SDReceiver"
     android:enabled="true"
     android:exported="true">
     <intent-filter>
         <action android:name="android.intent.action.MEDIA_MOUNTED"></action>
<action android:name="android.intent.action.MEDIA_UNMOUNTED"></action>
<data android:scheme="file"></data>
   </intent-filter>
</receiver>

第2步:
public class SDReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        String action = intent.getAction();
        if("android.intent.action.MEDIA_MOUNTED".equals(action)) {

        } else if("android.intent.action.MEDIA_UNMOUNTED".equals(action)) {

        }
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

48、服务

  Intent intent = new Intent(this, BgService.class);
        startService(intent);

49、线程和进程

50、服务的生命周期

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

服务可以被开启多次,每次开启执行onStartCommand方法

08-01 go

08-02 服务的生命周期之start

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("onCreate...");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("onStartCommand...");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("onDestroy...");
    }
}

停止服务:
        stopService(intent);
开启服务:
        intent = new Intent(this, MyService.class);
        startService(intent);
1、长期运行在后台
2、服务可以被多次开启,每次开启都调用onStartCommand
3、服务只能被停止一次,多次停止无效

08-03 服务的生命周期之bind

    /**
     * bindServiceIntent,
     * conn, activity and service bind
     * falg, BIND_AUTO_CREATE 连接时如果有服务对象就复用,没有创建新的服务对象
     */
    private void bindService() {
        Intent bindServiceIntent = new Intent(this, MyBindService.class);
        bindService(bindServiceIntent, new MyConn(),BIND_AUTO_CREATE);
    }

    private class MyConn implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }

08-04 绑定服务调方法

public class MyBindService extends Service {
    public MyBindService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("MyBindService:onStartCommand...");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("绑定服务成功后调用onBind...");
        return null;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        System.out.println("服务解绑前调用onBind...");
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("MyBindService:onCreate...");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("MyBindService:onDestroy...");
    }
}

绑定服务:
onCreate
onBind

解绑服务:
onUnbind
onDestroy

不能长期运行在后台
只能被绑定一次,多次绑定无效
只能被解绑一次,多次解绑抛出异常

    private void unbindService() {
        unbindService(conn);
    }

    /**
     * bindServiceIntent,
     * conn, activity and service bind
     * falg, BIND_AUTO_CREATE 连接时如果有服务对象就复用,没有创建新的服务对象
     */
    private void bindService() {
        bindServiceIntent = new Intent(this, MyBindService.class);
        conn = new MyConn();
        bindService(bindServiceIntent, conn,BIND_AUTO_CREATE);
    }

    private class MyConn implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
activity和service是同生共死,此时如果activity先销毁会抛出异常,一定要在onDestroy()中解绑服务

08-05 绑定服务调服务里的方法

1、绑定服务
2、在服务里创建内部类,调用服务里的方法
    public void methodInService() {
        System.out.println("我是服务里的方法");
    }
    
    public class ServiceConnClass extends Binder {
        public void playMusic() {
            methodInService();
        }
    }
    
3、在onBind() 返回
    @Override
    public IBinder onBind(Intent intent) {
        return new ServiceConnClass();
    }
4、activity的conn里的
    private class MyConn implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MyBindServiceCall.ServiceConnClass mService = (MyBindServiceCall.ServiceConnClass)service;
            //调用,在其他需要调用的地方
            mService.playMusic();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
通过服务里的内部类,调用服务里的方法

08-06 采用日志观察绑定服务调用方法的流程
08-07 绑定服务抽取接口

1、绑定service
        Intent intent = new Intent(this, ThirdActivityConnService.class);
        conn = new MyConn();
        bindService(intent, conn,BIND_AUTO_CREATE);
2、定义内部类
    public void zhuanqian() {
        System.out.println("赚钱");
    }

    private class InnerService extends Binder implements IThirdActivityConnService {

        @Override
        public void qianshouMM() {
            zhuanqian();
        }

        public void xiSangNa() {
            System.out.println("洗桑拿");
        }
    }
3、定义接口
public interface IThirdActivityConnService {
    void qianshouMM();
}
4、强转,调接口中的方法
    private class MyConn implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = (IThirdActivityConnService)service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }

08-08 混合方式开启服务

08-09 MediaPlayer 简单介绍

08-10 音乐播放器

08-11 start开启远程服务

远程服务
package cn.nubia.remoteserviceaidldemo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class RemoteService extends Service {
    public RemoteService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("远程服务 onCreate...");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("远程服务 onStartCommand...");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("远程服务 onDestroy...");
    }
}

注册清单文件:
        <service
            android:name=".RemoteService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="chu.lian"/>
            </intent-filter>
        </service>

本地服务
        Button startService = (Button) findViewById(R.id.start_server_service);
        startService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startServerService();
            }
        });
        Button stopService = (Button) findViewById(R.id.stop_server_service);
        stopService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopServerService();
            }
        });

    private void stopServerService() {
        stopService(intent);
    }

    private void startServerService() {
        intent = new Intent();
        intent.setAction("chu.lian");
        startService(intent);
    }

    <Button
        android:id="@+id/start_server_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开启远程服务" />

    <Button
        android:id="@+id/stop_server_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停止远程服务" />

点击本地服务报错
2018-10-02 21:58:16.144 13783-13783/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.localserviceaidldemo, PID: 13783
    java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=chu.lian }
        at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1644)
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1685)
        at android.app.ContextImpl.startService(ContextImpl.java:1657)
        at android.content.ContextWrapper.startService(ContextWrapper.java:644)
        at cn.nubia.localserviceaidldemo.MainActivity.startServerService(MainActivity.java:40)
        at cn.nubia.localserviceaidldemo.MainActivity.access$000(MainActivity.java:9)
        at cn.nubia.localserviceaidldemo.MainActivity$1.onClick(MainActivity.java:21)
        at android.view.View.performClick(View.java:6291)
        at android.view.View$PerformClick.run(View.java:24931)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
        
解决办法:必须指定具体的包名
    private void startServerService() {
        intent = new Intent();
        intent.setAction("chu.lian");
        intent.setPackage("cn.nubia.remoteserviceaidldemo");
        startService(intent);
    }

08-12 bind绑定远程服务

多次解绑服务抛出异常
2018-10-02 22:26:22.855 22936-22936/cn.nubia.localserviceaidldemo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.localserviceaidldemo, PID: 22936
    java.lang.IllegalArgumentException: connection is null
        at android.app.ContextImpl.unbindService(ContextImpl.java:1815)
        at android.content.ContextWrapper.unbindService(ContextWrapper.java:697)
        at cn.nubia.localserviceaidldemo.MainActivity.unbindServerService(MainActivity.java:72)
        at cn.nubia.localserviceaidldemo.MainActivity.access$300(MainActivity.java:12)
        at cn.nubia.localserviceaidldemo.MainActivity$4.onClick(MainActivity.java:48)
        at android.view.View.performClick(View.java:6291)
        at android.view.View$PerformClick.run(View.java:24931)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

package cn.nubia.localserviceaidldemo;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private Intent intent;
    private MyConn conn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startService = (Button) findViewById(R.id.start_server_service);
        startService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startServerService();
            }
        });
        Button stopService = (Button) findViewById(R.id.stop_server_service);
        stopService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopServerService();
            }
        });

        Button bindService = (Button) findViewById(R.id.bind_server_service);
        bindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindServerService();
            }
        });

        Button unbindService = (Button) findViewById(R.id.unbind_server_service);
        unbindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindServerService();
            }
        });


        intent = new Intent();
        intent.setAction("chu.lian");
        intent.setPackage("cn.nubia.remoteserviceaidldemo");
    }

    private void stopServerService() {
        stopService(intent);
    }

    private void startServerService() {
        startService(intent);
    }

    private void bindServerService() {
        conn = new MyConn();
        bindService(intent, conn,BIND_AUTO_CREATE);
    }

    private void unbindServerService() {
        unbindService(conn);
    }

    public class MyConn implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }
}

08-13 调用远程组件里的方法

1、在远程服务中创建接口
package cn.nubia.remoteserviceaidldemo;

interface IRemoteService {

    void eat();
    void drink();
    void wan();
    void le();
}

2、远程服务中内部类实现接口
    private class RemoteServiceInner extends Binder implements IRemoteService {

        @Override
        public void eat() {
            eatone();
        }

        @Override
        public void drink() {
            drinkone();
        }

        @Override
        public void wan() {
            wanone();
        }

        @Override
        public void le() {
            leone();
        }
    }

3、在onBind 返回 内部类
    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("远程服务 onBind...");
        Toast.makeText(this, "远程服务 onBind...", Toast.LENGTH_SHORT).show();
        return new RemoteServiceInner();
    }
    
4、在远程服务端创建aidl文件,将服务端的接口文件改名为IRemoteService.aidl
package cn.nubia.remoteserviceaidldemo;

interface IRemoteService {

    void eat();
    void drink();
    void wan();
    void le();
}
5、aidl 在本地获取远程服务对象

08-14 aidl编写步骤

android studio中aidl文件的位置:
https://blog.csdn.net/glen1943/article/details/51777031
1、在远程服务端将.java接口,改为.aidl
2、去掉权限修饰符
3、找到.aidl对应的.java文件,路径:
generated/source/aidl/debug/和java相同的包名

在这里插入图片描述

4、修改内部类继承Stub
    private class RemoteServiceInner extends IRemoteService.Stub {

        @Override
        public void eat() {
            eatone();
        }

        @Override
        public void drink() {
            drinkone();
        }

        @Override
        public void wan() {
            wanone();
        }

        @Override
        public void le() {
            leone();
        }
    }
5、在另一个apk调用远程服务的方法
创建和远程服务一样的包
将远程服务的.aidl文件复制到创建的包中
android studio 需要创建在main下创建与java同级的文件夹并且包名相同(客户端)

在这里插入图片描述

6、强转
    public class MyConn implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iRemoteService = IRemoteService.Stub.asInterface(service);

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
7、调方法
        Button callDrink = (Button) findViewById(R.id.call_drink);
        callDrink.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    iRemoteService.drink();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

08-15 远程服务应用场景

1、android源码
2、手机制造厂商的服务
3、 超级大的公司写远程服务给其他程序员用

08-16 支付宝

1、创建一个服务AlipayService.java
2、在清单文件中添加隐示意图的action
        <service
            android:name=".service.AlipayService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.PAY"></action>
            </intent-filter>
        </service>
3、创建内部类继承Binder实现接口
    @Override
    public IBinder onBind(Intent intent) {
        return new AlipayServiceInner();
    }


    private class AlipayServiceInner extends Binder implements IAlipayService {

        @Override
        public int logoinAlipay(String userName, String passWord, String sfzh) {
            if (userName.equals("gordon") && passWord.equals("123456") && sfzh.equals("610302xxxx")) {
                return 1;
            } else {
                return 0;
            }
        }
    }
4、另一个应用Wkings调用支付宝接口logoinAlipay()
开启远程服务:隐示意图开启服务
        intent = new Intent();
        intent.setAction("android.intent.action.PAY");
        intent.setPackage("cn.nubia.alipay");
        startService(intent);
5、在Alipay中,创建.aidl,在main下创建java的同级目录aidl

6、将内部类继承修改为
    private class AlipayServiceInner extends IAlipayService.Stub {

        @Override
        public int logoinAlipay(String userName, String passWord, String sfzh) {
            if (userName.equals("gordon") && passWord.equals("123456") && sfzh.equals("610302xxxx")) {
                return 1;
            } else {
                return 0;
            }
        }
    }
7、删除接口文件
远程服务端目录

在这里插入图片描述

客户端
1、绑定服务开启
    private void startAlipay() {
        startService(intent);
        conn = new WkingsServiceConnection();
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    public class WkingsServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
2、将Alipay的整个aidl包拷贝到Wkings main目录下

在这里插入图片描述

3、拿到远程接口对象
    public class WkingsServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iAlipayService = IAlipayService.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
4、调方法
    private void loginAlipay() {
        try {
            int state = iAlipayService.logoinAlipay("gordon", "123456", "610302xxxx");
            if(state == 0) {
                Toast.makeText(this, "账号不存在", Toast.LENGTH_SHORT).show();
            } else if (state == 1) {
                Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

09-02 内容提供者的概念

数据库的创建
java中创建普通文件:
1、在内存中创建一个指向d盘的文件对象
File file = new File("D:\suibian.txt");
2、在硬盘上创建文件并写入内容
FileOutputStream fos = new FileOutputStream(file);
fos.write("hehe".getBytes());
fos.close();
sqlite数据库:
1、在内存中创建数据库帮助类的对象
2、在磁盘上创建数据库文件

09-03 数据库的复习

package cn.nubia.contentproviderdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class BankDbOpenHelper extends SQLiteOpenHelper {

    /**
     *  @param context
     *
     */
    public BankDbOpenHelper(Context context) {
        //super(context, name, factory, version);
        super(context, "bank.db", null, 1);
    }

    /**
     * 第一次创建数据库时调用,做初始化
     * @param db
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table account(_id integer primary key autoincrement, name varchar(20),money varchar(20))");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

2、调用
        BankDbOpenHelper helper = new BankDbOpenHelper(this);
        helper.getWritableDatabase();
创建数据库成功
路径:data/data/包名/databases/bank.db

在这里插入图片描述
09-04 内容提供者的原理

-rw-rw--- 不可读不可写

09-05 内容提供者的编写步骤

1、创建一个类继承ContentProvider 
package cn.nubia.contentproviderdemo;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;

public class BankProvider extends ContentProvider {
    public BankProvider() {
    }

    @Override
    public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        return false;
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // TODO: Implement this to handle query requests from clients.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

2、在清单文件中配置authorities暗号上半句
        <provider
            android:name=".BankProvider"
            android:authorities="tian.wang.gai.di.hu"
            android:enabled="true"
            android:exported="true"></provider>
            
3、暗号下半句在BankProvider中
    
    //暗号下半句
    static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);

    /**
     * authority:暗号上半句,在清单文件中配置
     * path:暗号下半句
     * code:匹配码 如果匹配成功 1,不匹配:默认-1
     */
    static {
        matcher.addURI("tian.wang.gai.di.hu", "bao.ta.zhen.he.yao", 1);
    }
4、实现增删改查

在这里插入图片描述

在onCreate中初始化
    @Override
    public boolean onCreate() {
        mHelper = new BankDbOpenHelper(getContext());
        db = mHelper.getWritableDatabase();
        return false;
    }

增:
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        int code = matcher.match(uri);
        if (code == URI_SUCC) {
            long id = db.insert("account", null, values);
            return Uri.parse("id:" + id);
        } else {
            throw new IllegalArgumentException("无权操作");
        }
    }
    
user 调用另一个程序中的内容提供者
内容解析器调用内容提供者修改数据库
                //1 获取内容解析器
                ContentResolver resolver = getContentResolver();
                //2 指定URI
                Uri uri = Uri.parse("content://tian.wang.gai.di.hu/bao.ta.zhen.he.yao");
                //3 操作数据库
                ContentValues values = new ContentValues();
                values.put("name","gordon");
                values.put("money",1000000);
                Uri id = resolver.insert(uri, values);

异常:如果只创建了user没有内容提供者
2018-10-03 16:20:46.900 3486-3486/cn.nubia.user E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.user, PID: 3486
    java.lang.IllegalArgumentException: Unknown URI content://tian.wang.gai.di.hu/bao.ta.zhen.he.yao
        at android.content.ContentResolver.insert(ContentResolver.java:1553)
        at cn.nubia.user.MainActivity$1.onClick(MainActivity.java:31)
        at android.view.View.performClick(View.java:6291)
        at android.view.View$PerformClick.run(View.java:24931)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

09-06 虚拟短信

        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                ContentResolver resolver = getContentResolver();
                Uri uri = Uri.parse("content://sms/");
                ContentValues values = new ContentValues();
                values.put("address","95533");
                values.put("read",0);
                values.put("type",1);
                values.put("body","我是测试短信");
                values.put("data", System.currentTimeMillis());
                resolver.insert(uri,values);
            }
        }.start();
        
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.WRITE_SMS"/>

09-07 利用内容提供者查询数据库

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        int code = matcher.match(uri);
        if (code == URI_SUCC_ACCOUNT) {
            Cursor cursor = db.query("account", projection, selection, selectionArgs, null, null, sortOrder);
            return cursor;
        } else {
            throw new IllegalArgumentException("无权查询");
        }
    }

09-08 利用内容提供者修改数据库

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        int code = matcher.match(uri);
        if (code == URI_SUCC_ACCOUNT) {
            int id = db.update("account", values, selection, selectionArgs);
            return id;
        } else {
            return 0;
        }
    }

09-09 利用内容提供者删除数据库

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int code = matcher.match(uri);
        if (code == URI_SUCC_ACCOUNT) {
            int id = db.delete("account", selection, selectionArgs);
            return id;
        } else {
            return 0;
        }
    }
user:

package cn.nubia.user;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button insertDb = (Button) findViewById(R.id.insert_db);
        insertDb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //1 获取内容解析器
                ContentResolver resolver = getContentResolver();
                //2 指定URI
                Uri uri = Uri.parse("content://tian.wang.gai.di.hu/account");
                //3 操作数据库
                ContentValues values = new ContentValues();
                values.put("name", "gordon");
                values.put("money", 1000000);
                Uri id = resolver.insert(uri, values);
                Toast.makeText(MainActivity.this, uri.toString() + ",id:" + id, Toast.LENGTH_SHORT).show();
            }
        });

        Button delteDb = (Button) findViewById(R.id.delte_db);
        delteDb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //1 获取内容解析器
                ContentResolver resolver = getContentResolver();
                //2 指定URI
                Uri uri = Uri.parse("content://tian.wang.gai.di.hu/account");
                //3 操作数据库
                int id = resolver.delete(uri, "name=?", new String[]{"gordon"});
            }
        });

        Button updateDb = (Button) findViewById(R.id.update_db);
        updateDb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //1 获取内容解析器
                ContentResolver resolver = getContentResolver();
                //2 指定URI
                Uri uri = Uri.parse("content://tian.wang.gai.di.hu/account");
                //3 操作数据库
                ContentValues values = new ContentValues();
                values.put("money", 1);
                resolver.update(uri, values, "_id=?", new String[]{"1"});
            }
        });

        Button queryDb = (Button) findViewById(R.id.query_db);
        queryDb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //1 获取内容解析器
                ContentResolver resolver = getContentResolver();
                //2 指定URI
                Uri uri = Uri.parse("content://tian.wang.gai.di.hu/account");
                //3 操作数据库
                Cursor cursor = resolver.query(uri, new String[]{"name", "money"}, null, null, null);
                while (cursor.moveToNext()) {
                    //String name = cursor.getString(0);
                    String name = cursor.getString(cursor.getColumnIndex("name"));
                    String money = cursor.getString(cursor.getColumnIndex("money"));
                }
            }
        });
    }
}

ContentProvider:

package cn.nubia.contentproviderdemo;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

public class BankProvider extends ContentProvider {

    //暗号下半句
    static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);

    private static final int URI_SUCC_ACCOUNT = 4;

    /**
     * authority:暗号上半句,在清单文件中配置
     * path:暗号下半句
     * code:匹配码 如果匹配成功 1,不匹配:默认-1
     */
    static {
        //推荐使用表名
        matcher.addURI("tian.wang.gai.di.hu", "account", URI_SUCC_ACCOUNT);
    }

    private BankDbOpenHelper mHelper;
    private SQLiteDatabase db;

    public BankProvider() {
    }

    @Override
    public boolean onCreate() {
        mHelper = new BankDbOpenHelper(getContext());
        db = mHelper.getWritableDatabase();
        return false;
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * @param uri    content://tian.wang.gai.di.hu/bao.ta.zhen.he.yao
     * @param values
     * @return
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        int code = matcher.match(uri);
        if (code == URI_SUCC_ACCOUNT) {
            long id = db.insert("account", null, values);
            return Uri.parse("id:" + id);
        } else {
            throw new IllegalArgumentException("无权增加");
        }
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int code = matcher.match(uri);
        if (code == URI_SUCC_ACCOUNT) {
            int id = db.delete("account", selection, selectionArgs);
            return id;
        } else {
            return 0;
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        int code = matcher.match(uri);
        if (code == URI_SUCC_ACCOUNT) {
            int id = db.update("account", values, selection, selectionArgs);
            return id;
        } else {
            return 0;
        }
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        int code = matcher.match(uri);
        if (code == URI_SUCC_ACCOUNT) {
            Cursor cursor = db.query("account", projection, selection, selectionArgs, null, null, sortOrder);
            return cursor;
        } else {
            throw new IllegalArgumentException("无权查询");
        }
    }
}

BankDbOpenHelper:
package cn.nubia.contentproviderdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class BankDbOpenHelper extends SQLiteOpenHelper {

    /**
     *  @param context
     *
     */
    public BankDbOpenHelper(Context context) {
        //super(context, name, factory, version);
        super(context, "bank.db", null, 1);
    }

    /**
     * 第一次创建数据库时调用,做初始化
     * @param db
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table account(_id integer primary key autoincrement, name varchar(20),money varchar(20))");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

09-10 _getType方法

返回的数据类型
vnd.android.cursor.item 单行数据
vnd.android.cursor.dir/   多行数据

09-11 学习内容提供者的目的
09-12 短信数据库的表结构&URI
09-13 利用内容提供者读取系统短信
09-14 备份短信
09-15 插入短信
09-16 利用内容提供者操作多张表
09-17 通讯录表结构&URI
09-18 读取通讯录联系人
09-19 插入通讯录联系人
09-20 内容观察者原理
09-21 内容观察者编写步骤
09-22 短信窃听器

项目一:手机安全卫士-MobileSecurityGuard

快捷键:

Ctrl+alt+f:生成全局变量
Ctrl+d:复制一行
1. Ctrl+P 查看参数

1. Ctrl+G

同时按下Ctrl+G快捷键弹出快速定位框,在框中输入行数点击OK即可快速切换到对应的行数,如图2.17所示。

2. Ctrl+E

同时按下Ctrl+E快捷键,弹出最近打开文件列表,可以快速选择最近曾经打开的文件

3. Ctrl+/

选中某一行,同时按下Ctrl+/快捷键可以注释这一行,如图2.19所示。

4. Ctrl+F

同时按下Ctrl+F快捷键,将在编辑页的顶部弹出类内快速搜索栏,可以快速定位类内的某个单词,支持联想查找

输入prote,将会高亮显示protected,同时注意到搜索栏中有三个复选框,选中第一个Match Case复选框将会对大小写敏感。

5. Ctrl+R:

Ctrl+F快捷键常和Ctrl+R快捷键使用,用来快速查找并全部替换

先使用快捷键Ctrl+F搜索出所有protected,然后使用快捷键Ctrl+R弹出替换栏,在替换栏输入框中输入替换后的单词并点击Replace all按钮即可将类中所有的protected替换成public,十分快捷。不过,在实际开发中要谨慎使用,避免引入不容易察觉的问题。

6. Ctrl+J

同时按下Ctrl和J快捷键,弹出快捷代码框

对于一些常用的代码Android Studio中进行了封装,直接选中即可快速生成,在开发中十分实用,这里以打印log和弹出Toast为例。首先按下Ctrl+J快捷键,弹出如图2.24所示的快捷代码框,然后直接输入logd这一快捷代码的“命令”,如图2.25所示。

打印Log需要TAG,在类的最上方输入快捷代码logt,即可快速生成一个TAG,如图2.27所示。

同样,先输入Ctrl+J键,弹出快捷代码框,然后直接输入toast,如图2.29所示。

按下Enter键,或者有了Toast以后按下Ta

快速生成了一行Toast语句,在引号中输入要Toast显示的信息即可,是不是十分快捷方便?

7. Ctrl+F12:

在类中方法比较多的情况下,同时按下Ctrl和F12键可以快速查看类中所有的方法,弹出这个框的同时可以直接输入想要搜索的方法,进行快速匹配。


1. Ctrl+Alt+T


选中一块代码,同时按下Ctrl、Alt和T键,弹出“包裹”弹出框,选择需要包裹的类型即可包裹选中的代码,

2. Ctrl+Alt+L

对当前类的所有代码进行格式化

2. Ctrl+Alt+V

此快捷键可以快速声明一个变量,本地变量赋值

3. Ctrl+Alt+H

点中某一个方法按下这个快捷键,在左边栏上弹出此方法的调用关系,此快捷键在开发中十分常用。



4. Ctrl+Alt+O


这个快捷键可以自动导包或删除无用的包,这时候按下快捷键即可自动删除这些无用的包

1. Ctrl+Shift+/


和Ctrl+/类似,都是实现注释代码的功能,Ctrl+Shift+/实现代码块的注释,再次按下这个快捷键将反注释掉这部分代码

这个快捷键在开发中经常使用,可以通过关键字快速搜索需要的信息,选中第一个复选框对大小写敏感。点击右边的标签即可查看关键字的预览


3. Ctrl+Shift+加号/减号


若方法是收起的,同时按下Ctrl+Shift+加号会将方法展开,


相反,若方法是展开的,同时按下Ctrl+Shift+减号则会收起方法

1.Alt+Insert

同时按下Alt和Insert键,弹出快速代码生成框,有构造方法、getter/setter方法、toString方法等
 Android Studio快速代码生成框
这里以生成构造方法为例,选择Constructor选项
可以看出,自动生成了包含两个属性的构造方法,很是方便快捷,生成getter/setter方法和生成构造方法比较类似,同样选中这两个属性并按下快捷键,选中Getter and Setter,如图2.52所示。
选择OK键即生成这两个属性的getter和setter方法

2. Alt+鼠


按下Alt键并结合鼠标可以同时选中多
 Android Studio多行选中
上图中一次选中了多行,此时可以进行多行编辑


3. Ctrl+鼠标左键
此快捷键可以查看鼠标选中的类或方法

MSG 01-06 闪屏界面开发

1	展示品牌,logo
2	初始化数据
3	合法性校验 检测是否有网络 检测是否已经登录
4	检测版本更新

<code:>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/splash"
    tools:context=".SplashActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="45dp"
        android:text="190.10.12"
        android:textColor="#fff"
        android:textSize="18sp" />

</RelativeLayout>

-->隐藏ActionBar
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

MSG 01-07 修改应用名称和图标

android:icon="@mipmap/heima"
android:label="@string/app_name"

MSG 01-08 版本信息获取和展示

在app/build.gradle

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "cn.nubia.mobilesecurityguard"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0);
versionName = packageInfo.versionName;

MSG 01-09 渐变动画

    private void startAlphaAnim(View view) {
        AlphaAnimation alphaAnimation = new AlphaAnimation(0.5f, 1);
        alphaAnimation.setDuration(3000);
        view.startAnimation(alphaAnimation);
    }

MSG 01-10 版本升级流程

MSG 01-11 版本升级服务器
{“versionName”:“nubia X20”, “versionCode”:910, “updateInfo”:“fix a big bug”}

部署tomcat服务器:
webapps/ROOT/update.json

MSG 01-12 使用okhttputils获取网络数据

MSG 01-13 JsonObject解析

MSG 01-14 判断版本更新、升级对话框

    private void showUpdateDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("版本更新");
        builder.setMessage(updateInfo);
        builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        });
        builder.setNegativeButton("忽略更新", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //延迟2秒,进入主界面
                enterActivity();
            }
        });
        builder.show();
    }

MSG 01-15 跳到主界面

    private void enterActivity() {
        startActivity(new Intent(this, HomeActivity.class));
        finish();
    }

MSG 动画

        rlRoot = (RelativeLayout) findViewById(R.id.rl_root);
        startAlphaAnim(rlRoot);

    private void startAlphaAnim(View view) {
        AlphaAnimation alphaAnimation = new AlphaAnimation(0.5f, 1);
        alphaAnimation.setDuration(3000);
        view.startAnimation(alphaAnimation);
    }

MSG 01-16 使用okhttp下载文件

6.0之后下载文件需要动态权限,如果没有赋予报错:

下载失败java.io.FileNotFoundException: /storage/emulated/0/MSGuard.apk (Permission denied)

    private void isPermissioned () {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            //申请WRITE_EXTERNAL_STORAGE权限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    0);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        doNext(requestCode,grantResults);
    }

    private void doNext(int requestCode, int[] grantResults) {
        if (requestCode == 0) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission Granted
                downloadApk();
            } else {
                // Permission Denied
                mHandler.sendEmptyMessageDelayed(0, 2000);
            }
        }
    }


    private void downloadApk() {
        System.out.println("正在下载新版本!");
        System.out.println("downloadApk新版本路径在:" + Environment.getExternalStorageDirectory().getAbsolutePath());
        OkHttpUtils.get().url(downloadApkUrl).build().execute(new FileCallBack(Environment.getExternalStorageDirectory().getAbsolutePath(), "MSGuard.apk") {
            @Override
            public void onError(Call call, Exception e, int id) {
                System.out.println("下载失败" + e);
            }

            @Override
            public void onResponse(File response, int id) {
                System.out.println("下载完成!");
                Toast.makeText(SplashActivity.this, "新版本下载成功!路径为:" + Environment.getExternalStorageDirectory().getAbsolutePath().toString(), Toast.LENGTH_SHORT).show();
            }
        });
    }

MSG 01-17 apk打包
详见文件

MSG 01-18 跳到系统安装界面

    //https://blog.csdn.net/itxiaolong3/article/details/72868199
    private void installApk(File response) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(response), "application/vnd.android.package-archive");
        startActivity(intent);
    }
6.0后报错:

https://blog.csdn.net/qq_36961698/article/details/78436478

    --------- beginning of crash
09-12 21:12:15.187 15753-15753/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 15753
    android.os.FileUriExposedException: file:///storage/emulated/0/MSGuard.apk exposed beyond app through Intent.getData()
        at android.os.StrictMode.onFileUriExposed(StrictMode.java:1975)
        at android.net.Uri.checkFileUriExposed(Uri.java:2363)
        at android.content.Intent.prepareToLeaveProcess(Intent.java:9975)
        at android.content.Intent.prepareToLeaveProcess(Intent.java:9929)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1622)
        at android.app.Activity.startActivityForResult(Activity.java:4751)
        at android.support.v4.app.BaseFragmentActivityApi16.startActivityForResult(BaseFragmentActivityApi16.java:54)
        at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:67)
        at android.app.Activity.startActivityForResult(Activity.java:4691)
        at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:720)
        at android.app.Activity.startActivity(Activity.java:5112)
        at android.app.Activity.startActivity(Activity.java:5080)
        at cn.nubia.mobilesecurityguard.SplashActivity.installApk(SplashActivity.java:181)
        at cn.nubia.mobilesecurityguard.SplashActivity.access$700(SplashActivity.java:34)
        at cn.nubia.mobilesecurityguard.SplashActivity$5.onResponse(SplashActivity.java:172)
        at cn.nubia.mobilesecurityguard.SplashActivity$5.onResponse(SplashActivity.java:161)
        at com.zhy.http.okhttp.OkHttpUtils$3.run(OkHttpUtils.java:186)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

修改:
https://blog.csdn.net/qq_36961698/article/details/78436478

报错:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:instantRunMainApkResourcesDebug'.
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:100)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:70)
	at org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter.execute(OutputDirectoryCreatingTaskExecuter.java:51)
	at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:62)
	at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:54)
	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:60)
	at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:97)
	at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:87)
	at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:52)
	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54)
	at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
	at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker$1.run(DefaultTaskGraphExecuter.java:248)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:199)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:110)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:241)
	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:230)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.processTask(DefaultTaskPlanExecutor.java:123)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.access$200(DefaultTaskPlanExecutor.java:79)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:104)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:98)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionPlan.execute(DefaultTaskExecutionPlan.java:626)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionPlan.executeWithTask(DefaultTaskExecutionPlan.java:581)
	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.run(DefaultTaskPlanExecutor.java:98)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
	at java.lang.Thread.run(Thread.java:745)
Caused by: org.gradle.tooling.BuildException: Exception while generating InstantRun main resources APK
	at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler$transform$2.invoke(BuildElements.kt:133)
	at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler$transform$2.invoke(BuildElements.kt:110)
	at kotlin.sequences.SequencesKt___SequencesKt$onEach$1.invoke(_Sequences.kt:1260)
	at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:148)
	at kotlin.sequences.FilteringSequence$iterator$1.calcNext(Sequences.kt:108)
	at kotlin.sequences.FilteringSequence$iterator$1.hasNext(Sequences.kt:132)
	at kotlin.sequences.TransformingSequence$iterator$1.hasNext(Sequences.kt:152)
	at kotlin.sequences.SequencesKt___SequencesKt.toCollection(_Sequences.kt:633)
	at kotlin.sequences.SequencesKt___SequencesKt.toMutableList(_Sequences.kt:663)
	at kotlin.sequences.SequencesKt___SequencesKt.toList(_Sequences.kt:654)
	at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler.transform(BuildElements.kt:140)
	at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler.into(BuildElements.kt:115)
	at com.android.build.gradle.internal.scope.BuildElementActionScheduler.into(BuildElementActionScheduler.kt:32)
	at com.android.build.gradle.tasks.ir.InstantRunMainApkResourcesBuilder.doFullTaskAction(InstantRunMainApkResourcesBuilder.kt:77)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:46)
	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:39)
	at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:26)
	at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:780)
	at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:747)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:121)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:199)
	at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:110)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:110)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:92)
	... 32 more
Caused by: java.io.IOException: Exception while generating InstantRun main resources APK
	at com.android.build.gradle.tasks.ir.InstantRunMainApkResourcesBuilder.processSplit(InstantRunMainApkResourcesBuilder.kt:106)
	at com.android.build.gradle.tasks.ir.InstantRunMainApkResourcesBuilder$doFullTaskAction$1.invoke(InstantRunMainApkResourcesBuilder.kt:76)
	at com.android.build.gradle.tasks.ir.InstantRunMainApkResourcesBuilder$doFullTaskAction$1.invoke(InstantRunMainApkResourcesBuilder.kt:51)
	at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler$transform$$inlined$forEach$lambda$1.call(BuildElements.kt:121)
	at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler$transform$$inlined$forEach$lambda$1.call(BuildElements.kt:110)
	at java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1424)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinTask.externalAwaitDone(ForkJoinTask.java:326)
	at java.util.concurrent.ForkJoinTask.doJoin(ForkJoinTask.java:391)
	at java.util.concurrent.ForkJoinTask.join(ForkJoinTask.java:719)
	at com.android.ide.common.internal.WaitableExecutor.waitForAllTasks(WaitableExecutor.java:215)
	at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler.transform(BuildElements.kt:125)
	... 52 more
Caused by: com.android.ide.common.process.ProcessException: Failed to execute aapt
	at com.android.builder.core.AndroidBuilder.processResources(AndroidBuilder.java:809)
	at com.android.builder.core.AndroidBuilder.processResources(AndroidBuilder.java:797)
	at com.android.build.gradle.internal.transforms.InstantRunSplitApkBuilder.generateSplitApkResourcesAp(InstantRunSplitApkBuilder.java:375)
	at com.android.build.gradle.tasks.ir.InstantRunMainApkResourcesBuilder.processSplit(InstantRunMainApkResourcesBuilder.kt:91)
	... 63 more
	Suppressed: java.lang.RuntimeException: Some file processing failed, see logs for details
		at com.android.builder.internal.aapt.QueuedResourceProcessor.waitForAll(QueuedResourceProcessor.java:121)
		at com.android.builder.internal.aapt.QueuedResourceProcessor.end(QueuedResourceProcessor.java:141)
		at com.android.builder.internal.aapt.v2.QueueableAapt2.close(QueueableAapt2.java:104)
		at kotlin.io.CloseableKt.closeFinally(Closeable.kt:63)
		at com.android.build.gradle.tasks.ir.InstantRunMainApkResourcesBuilder.processSplit(InstantRunMainApkResourcesBuilder.kt:88)
		... 63 more
Caused by: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: com.android.builder.internal.aapt.v2.Aapt2Exception: AAPT2 error: check logs for details
	at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:503)
	at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:482)
	at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:79)
	at com.android.builder.internal.aapt.AbstractAapt.link(AbstractAapt.java:34)
	at com.android.builder.core.AndroidBuilder.processResources(AndroidBuilder.java:807)
	... 66 more
Caused by: java.util.concurrent.ExecutionException: com.android.builder.internal.aapt.v2.Aapt2Exception: AAPT2 error: check logs for details
	at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:503)
	at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:462)
	at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:79)
	at com.android.builder.internal.aapt.v2.QueueableAapt2.lambda$makeValidatedPackage$1(QueueableAapt2.java:166)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	... 1 more
Caused by: com.android.builder.internal.aapt.v2.Aapt2Exception: AAPT2 error: check logs for details
	at com.android.builder.png.AaptProcess$NotifierProcessOutput.handleOutput(AaptProcess.java:443)
	at com.android.builder.png.AaptProcess$NotifierProcessOutput.err(AaptProcess.java:395)
	at com.android.builder.png.AaptProcess$ProcessOutputFacade.err(AaptProcess.java:312)
	at com.android.utils.GrabProcessOutput$1.run(GrabProcessOutput.java:104)

AndroidManifest.xml中将provider放在application标签外了

错误:
 Process: cn.nubia.mobilesecurityguard, PID: 27974
    java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
        at android.support.v4.content.FileProvider.parsePathStrategy(FileProvider.java:584)
        at android.support.v4.content.FileProvider.getPathStrategy(FileProvider.java:558)
        at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:400)
        at cn.nubia.mobilesecurityguard.SplashActivity.installApkFile(SplashActivity.java:194)
        at cn.nubia.mobilesecurityguard.SplashActivity.access$700(SplashActivity.java:37)
        at cn.nubia.mobilesecurityguard.SplashActivity$5.onResponse(SplashActivity.java:175)
        at cn.nubia.mobilesecurityguard.SplashActivity$5.onResponse(SplashActivity.java:164)
        at com.zhy.http.okhttp.OkHttpUtils$3.run(OkHttpUtils.java:186)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
09-12 21:44:47.478 27974-27974/cn.nubia.mobilesecurityguard I/Process: Sending signal. PID: 27974 SIG: 9

https://blog.csdn.net/qq_31588719/article/details/77224049
报错2:
    --------- beginning of crash
09-16 12:15:19.304 4554-4554/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 4554
    java.lang.StringIndexOutOfBoundsException: length=19; index=20
        at java.lang.String.substring(String.java:1939)
        at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:721)
        at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:401)
        at cn.nubia.mobilesecurityguard.SplashActivity.installApkFile1(SplashActivity.java:207)
        at cn.nubia.mobilesecurityguard.SplashActivity.access$700(SplashActivity.java:37)
        at cn.nubia.mobilesecurityguard.SplashActivity$5.onResponse(SplashActivity.java:176)
        at cn.nubia.mobilesecurityguard.SplashActivity$5.onResponse(SplashActivity.java:164)
        at com.zhy.http.okhttp.OkHttpUtils$3.run(OkHttpUtils.java:186)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
09-16 12:15:19.319 4554-4554/cn.nubia.mobilesecurityguard I/Process: Sending signal. PID: 4554 SIG: 9

未解决

MSG 01-19 下载进度更新

    private void downloadApk() {
        System.out.println("正在下载新版本!");
        //展示下载进度
        showDownloadProgress();
        System.out.println("downloadApk新版本路径在:" + Environment.getExternalStorageDirectory().getAbsolutePath());
        OkHttpUtils.get().url(downloadApkUrl).build().execute(new FileCallBack(Environment.getExternalStorageDirectory().getAbsolutePath(), "MSGuard.apk") {
            @Override
            public void onError(Call call, Exception e, int id) {
                System.out.println("下载失败" + e);
            }

            @Override
            public void onResponse(File response, int id) {
                System.out.println("下载完成!");
                Toast.makeText(SplashActivity.this, "新版本下载成功!路径为:" + Environment.getExternalStorageDirectory().getAbsolutePath().toString(), Toast.LENGTH_SHORT).show();
                //跳到系统安装界面
                String path = Environment.getExternalStorageDirectory().getAbsolutePath();
                //installApkFile2(SplashActivity.this,path);
            }

            @Override
            public void inProgress(float progress, long total, int id) {
                super.inProgress(progress, total, id);
                //progress:
                //total:
                int precent = (int) ((progress*100)/total);
                progressDialog.setProgress(precent);
            }
        });
    }

    private void showDownloadProgress() {
        progressDialog = new ProgressDialog(this);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setTitle("正在下载,请稍候...");
        progressDialog.show();
    }

下载完成
progressDialog..dimiss()

MSG 01-20 签名冲突

包名相同且签名相同才属于同一个应用

测试时需要打同样签名的包

MSG 01-21 细节处理 取消弹窗、取消安装

如果弹窗被取消,用户点击返回键或弹窗外侧

        //监听弹窗被取消的事件(点击返回键或弹窗外侧)
        builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                enterActivity();
            }
        });
立即更新,取消安装时
    private void installApkFile(Context context, String filePath) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(context,  "cn.nubia.mobilesecurityguard.fileprovider", new File(filePath));
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        **startActivityForResult(intent, 0);**
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        System.out.println("requestCode = " + requestCode);
        System.out.println("用户取消了安装!");
        finish();
    }

MSG 02-02 主界面头布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity.HomeActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:background="@drawable/blue_bkg">

        <ImageView
            android:id="@+id/iv_logo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="20dp"
            android:src="@mipmap/heima" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@id/iv_logo"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="努比亚安全卫士"
                android:textSize="25sp"
                android:textStyle="bold" />

            <cn.nubia.mobilesecurityguard.view.FocusedTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="努比亚安全卫士,时刻保护你的安全! www.nubia.cn"
                android:textColor="#f00"
                android:textSize="18sp" />

            <cn.nubia.mobilesecurityguard.view.FocusedTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="努比亚安全卫士,时刻保护你的安全! www.nubia.cn"
                android:textColor="#f00"
                android:textSize="18sp" />
        </LinearLayout>

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_margin="10dp"
            android:background="@drawable/btn_setting_normal"
            android:scaleType="center"
            android:src="@drawable/setting" />

    </RelativeLayout>

    <GridView
        android:id="@+id/gv_home"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        每个item 横、竖距离大小
        android:horizontalSpacing="10dp"
        android:verticalSpacing="10dp"
        android:numColumns="2">

    </GridView>

</LinearLayout>

MSG 02-03 启动logo旋转动画

    /**
     *   动画: 属性、补间
     *   属性动画,绕Y旋转
     */
    private void startLogoAnimal() {

        //ivLogo.setRotationY(180);
        /**
         * 1 执行动画的对象
         * 2 要修改的属性
         * 3 可变参数,值得变化范围
         */
        ObjectAnimator animator = ObjectAnimator.ofFloat(ivLogo, "rotationY", 0, 360);
        animator.setDuration(2000);
        //无限循环
        animator.setRepeatCount(ObjectAnimator.INFINITE);
        //正向执行一次后逆向执行一次
        animator.setRepeatMode(ObjectAnimator.REVERSE);
        animator.start();
    }

MSG 02-04 跑马灯效果

        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:singleLine="true"

MSG 02-05 自定义控件的三个构造方法

    //开发者,通过代码创建一个对象时会走
    public FocusedTextView(Context context) {
        this(context, null);
        System.out.println("构造方法1");
    }

    //当控件有属性时,由系统底层调用
    public FocusedTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
        System.out.println("构造方法2");
    }

    //当控件配有样式时
    public FocusedTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        System.out.println("构造方法3");
        initView();
    }

MSG 02-06 自定义有焦点的textview

1、写一个自定义控件继承TextView
    /**
     *  android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:singleLine="true"
     */
    private void initView() {
        setEllipsize(TextUtils.TruncateAt.MARQUEE);
        setFocusable(true);
        setFocusableInTouchMode(true);
        setSingleLine();
    }

    //当前的TextView是否有焦点
    @Override
    public boolean isFocused() {
        //return super.isFocused();
        return true;
    }
2、xml中替换TextView
            <cn.nubia.mobilesecurityguard.view.FocusedTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="努比亚安全卫士,时刻保护你的安全! www.nubia.cn"
                android:textColor="#f00"
                android:textSize="18sp" />

MSG 02-07 首页GridView填充数据

    private String[] titles = new String[] { "手机防盗", "通讯卫士", "软件管理", "进程管理",
            "流量统计", "手机杀毒", "缓存清理", "高级工具" };

    private Integer[] icons = new Integer[] { R.drawable.sjfd, R.drawable.srlj,
            R.drawable.rjgj, R.drawable.jcgl, R.drawable.lltj, R.drawable.sjsd,
            R.drawable.hcql, R.drawable.cygj };

    private String[] descs = new String[] { "远程定位手机", "全面拦截骚扰", "管理您的软件",
            "管理运行进程", "流量一目了然", "病毒无处藏身", "系统快如火箭", "工具大全" };

1、将包含数据封装对象在集合中
private ArrayList<HomeInfo> mList;
2、创建对象
public class HomeInfo {
    public String title;
    public String des;
    public int imageId;
}
3、创建适配器
    class HomeAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return mList.size();
        }

        //返回条目对象
        @Override
        public HomeInfo getItem(int position) {
            return mList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = View.inflate(HomeActivity.this, R.layout.item_home, null);
            ImageView ivIcon = (ImageView) view.findViewById(R.id.iv_image);
            TextView tvTitle = (TextView) view.findViewById(R.id.tv_title);
            TextView tvDes = (TextView) view.findViewById(R.id.tv_des);

            HomeInfo info = mList.get(position);

            ivIcon.setImageResource(info.imageId);
            tvTitle.setText(info.title);
            tvDes.setText(info.des);

            return view;
        }
    }
4、初始化数据
    private void initData() {
        mList = new ArrayList<>();

        for (int i = 0; i < titles.length; i++) {
            HomeInfo homeInfo = new HomeInfo();
            homeInfo.title = titles[i];
            homeInfo.des = descs[i];
            homeInfo.imageId = icons[i];
            mList.add(homeInfo);
        }
        gvHome.setAdapter(new HomeAdapter());
    }

MSG 02-08 状态选择器

1、在drawable下创建状态选择器home_item_selector.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/home_item_press"/>
    <item android:drawable="@drawable/home_item_normal"/>
</selector>

2、settings按钮监听点击事件
1、
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_margin="10dp"
            android:background="@drawable/settings_selector"
            android:scaleType="center"
            可点击的属性
            android:clickable="true"
            android:src="@drawable/setting" />
 2、
        ivSettings = (ImageView) findViewById(R.id.iv_settings);
        ivSettings.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("点击了设置按钮...");
            }
        });

MSG 02-09 9patch图的使用

android studio 修改 9 patch图  

MSG 02-10 设置中心页面开发

                Intent intent = new Intent(getApplicationContext(), SettingActivity.class);
                startActivity(intent);
xml
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/global_blue"
        android:gravity="center"
        android:textColor="@color/white"
        android:textSize="22sp"
        android:text="@string/setting_center" />

MSG 02-11 抽取标题栏样式

1、在styles.xml中,抽取样式

    <!--标题栏样式-->
    <style name="TitleStyle">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">50dp</item>
        <item name="android:background">@color/global_blue</item>
        <item name="android:gravity">center</item>
        <item name="android:textColor">@color/white</item>
        <item name="android:textSize">22sp</item>
        <item name="android:text">@string/setting_center</item>
    </style>

2、用法
    <TextView
        style="@style/TitleStyle"
        android:text="@string/setting_center" />

MSG 02-12 设置页面条目布局开发
在这里插入图片描述

    <RelativeLayout
        android:id="@+id/rl_first_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@drawable/first_selector"
        android:padding="10dp">

        <TextView
            android:id="@+id/tv_first_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:text="@string/auto_update_setting"
            android:textSize="18sp" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:src="@drawable/off" />

    </RelativeLayout>

MSG 02-13 自定义组合控件—填充布局对象

1、将如上的relativeLayout布局单独抽取xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl_middle_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp"
    android:background="@drawable/first_selector"
    android:padding="10dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:text="@string/undefine_up"
        android:textSize="18sp" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:src="@drawable/off" />

</RelativeLayout>

2、由于上面的跟布局是RelativeLayout,定义类继承RelativeLayout
public class SettingItemView extends RelativeLayout {


    public SettingItemView(Context context) {
        this(context, null);
    }

    public SettingItemView(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public SettingItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

两种方法实现添加布局
    private void initView() {

        //给当前空布局添加布局对象 方法1
        //参三:null加载的控件没有父控件
        View view = View.inflate(getContext(), R.layout.setting_item_view, null);
        addView(view);
        //给当前空布局添加布局对象 方法2
        //2 参三,以本类为父类SettingItemView this 在加载布局时,以当前类为父类
        View view2 = View.inflate(getContext(), R.layout.setting_item_view, this);
    }
}

MSG 02-14 自定义组合控件-修改标题和背景

     View view = View.inflate(getContext(), R.layout.setting_item_view, null);
        tvTitle = view.findViewById(R.id.tv_title);
        ivToggle = view.findViewById(R.id.iv_toggle);
设置title
    //公开一个方法,修改标题
    public void setTitle(String title) {
        tvTitle.setText(title);
    }
在需要用到的地方
autoUpdateSetting = (SettingItemView) findViewById(R.id.auto_update_setting);
        harassmentInterceptionSetting = (SettingItemView) findViewById(R.id.Harassment_interception_setting);

设置值
        autoUpdateSetting.setTitle("自动更新设置");
        autoUpdateSetting.setBackgroundResource(R.drawable.first_selector);
        harassmentInterceptionSetting.setTitle("骚扰拦截设置");
        autoUpdateSetting.setBackgroundResource(R.drawable.last_selector);

MSG 02-15 自定义组合控件-开关控制

1、在自定义组合控件类中定义一个boolean
    //标记当前开关状态
    private boolean isOpen;
    
    //当前开关的状态
    public boolean isToggleOn() {
        return isOpen;
    }

    //修改当前开关状态,开关图片
    public void setToggleOn(boolean isOpen) {
        this.isOpen = isOpen;
        if(isOpen) {
            ivToggle.setImageResource(R.drawable.on);
        } else {
            ivToggle.setImageResource(R.drawable.off);
        }
    }
    
    //开关 方法1
    public void toggle() {
        if(isOpen) {
            setToggleOn(false);
        } else {
            setToggleOn(true);
        }
    }
	//开关 方法2
	public void toggle() {
        //setToggleOn(!isOpen);
    }

点击事件
1、
		public class SettingActivity extends AppCompatActivity implements View.OnClickListener
2、
	    autoUpdateSetting = (SettingItemView) findViewById(R.id.auto_update_setting);
        harassmentInterceptionSetting = (SettingItemView) findViewById(R.id.harassment_interception_setting);
        autoUpdateSetting.setOnClickListener(this);
        harassmentInterceptionSetting.setOnClickListener(this);
3、
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.auto_update_setting:
                System.out.println("点击自动更新设置");
                autoUpdateSetting.toggle();
                break;
            case R.id.harassment_interception_setting:
                System.out.println("点击骚扰拦截设置");
                harassmentInterceptionSetting.toggle();
                break;
            default:
                break;
        }
    }

MSG 02-16 自动更新开关控制

更新开关状态存储
        //初始化sp
        config = getSharedPreferences("config", MODE_PRIVATE);
        config.edit().putBoolean("auto_update_setting",autoUpdateSetting.isToggleOn()).commit();

根据拿到的值
        //根据sp的值,更新开关状态
        boolean autoUpdateSettingBeforeState = config.getBoolean("auto_update_setting", true);
        boolean harassmentInterceptionSettingBeforeState = config.getBoolean("harassment_interception_setting", true);

        autoUpdateSetting.setToggleOn(autoUpdateSettingBeforeState);
        harassmentInterceptionSetting.setToggleOn(harassmentInterceptionSettingBeforeState);

在闪屏界面判断是否开启了版本更新
        SharedPreferences config = getSharedPreferences("config", MODE_PRIVATE);
        boolean isAutoUpdateSetting = config.getBoolean("auto_update_setting", true);
        if (isAutoUpdateSetting) {
            checkVersion();
        }

MSG 02-17 sp工具类封装

package cn.nubia.mobilesecurityguard.gordon_utils;

import android.content.Context;
import android.content.SharedPreferences;

/**
 * SharedPreferences 封装类
 */
public class PrefUtils {

    /**
     *
     * @param context
     * @param fileName 存储的文件名
     * @return
     */
    private static SharedPreferences getSharedPreferencesObject(Context context, String fileName) {
        SharedPreferences sp = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
        return sp;
    }

    /**
     * 真值
     * @param context
     * @param fileName 存储的文件名
     * @param key
     * @param value
     */
    public static void putBoolean(Context context, String fileName, String key, boolean value) {
        getSharedPreferencesObject(context, fileName).edit().putBoolean(key,value).commit();
    }

    /**
     *
     * @param context
     * @param fileName 存储的文件名
     * @param key
     * @param defValue
     */
    public static boolean getBoolean(Context context, String fileName, String key, boolean defValue) {
        boolean value = getSharedPreferencesObject(context, fileName).getBoolean(key, defValue);
        return value;
    }

    /**
     * 整数
     * @param context
     * @param fileName 存储的文件名
     * @param key
     * @param value
     */
    public static void putInt(Context context, String fileName, String key, int value) {
        getSharedPreferencesObject(context, fileName).edit().putInt(key,value).commit();
    }

    /**
     *
     * @param context
     * @param fileName 存储的文件名
     * @param key
     * @param defValue
     */
    public static int getInt(Context context, String fileName, String key, int defValue) {
        int value = getSharedPreferencesObject(context, fileName).getInt(key, defValue);
        return value;
    }

    /**
     * 字符串
     * @param context
     * @param fileName 存储的文件名
     * @param key
     * @param value
     */
    public static void putString(Context context, String fileName, String key, String value) {
        getSharedPreferencesObject(context, fileName).edit().putString(key,value).commit();
    }

    /**
     *
     * @param context
     * @param fileName 存储的文件名
     * @param key
     * @param defValue
     */
    public static String getString(Context context, String fileName, String key, String defValue) {
        String value = getSharedPreferencesObject(context, fileName).getString(key, defValue);
        return value;
    }
}

MSG 02-18 自定义组合控件小结
MSG 02-19 自定义属性-attrs文件声明

1、在values目录下创建attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SettingItemView">
        <!--自定义标题-->
        <attr name="nubia_title" format="string" />
        <!--自定义背景-->
        <attr name="nubia_background">
            <enum name="first" value="0" />
            <enum name="middle" value="1" />
            <enum name="last" value="2" />
        </attr>
        <!--开关显示/隐藏-->
        <attr name="nubia_show_toggle" format="boolean"/>
    </declare-styleable>
</resources>

2、自定义命名空间
    xmlns:nubia="http://schemas.android.com/apk/res-auto"
3、布局中配置	

MSG 02-20 在布局文件中配置自定义属性

    <cn.nubia.mobilesecurityguard.view.SettingItemView
        android:id="@+id/auto_update_setting"
        android:layout_marginTop="10dp"
        android:layout_width="match_parent"
        nubia:nubia_title="自动更新设置2"
        nubia:nubia_show_toggle="true"
        nubia:nubia_background="first"
        android:layout_height="wrap_content">

    </cn.nubia.mobilesecurityguard.view.SettingItemView>

MSG 02-21 获取自定义属性并更新控件

    public SettingItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();

        //1 从AttributeSet中取出自定义属性
        //2 根据自定义属性的值更新相关控件
        String nubia_title = attrs.getAttributeValue(NAMESPACE, "nubia_title");
        boolean nubia_show_toggle = attrs.getAttributeBooleanValue(NAMESPACE, "nubia_show_toggle", false);
        int nubia_background = attrs.getAttributeIntValue(NAMESPACE, "nubia_background", 0);
        setTitle(nubia_title);
        switch (nubia_background) {
            case 0:
                setBackgroundResource(R.drawable.first_selector);
                break;
            case 1:
                setBackgroundResource(R.drawable.middle_selector);
                break;
            case 2:
                setBackgroundResource(R.drawable.last_selector);
                break;
        }
    }

MSG 02-22 自定义属性小结
MSG 02-23 总结

**MSG 03-02 自定义弹窗布局 **

自定义dialog布局
dialog_set_pwd.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/gray"
        android:drawableLeft="@drawable/dialog_title_default_icon"
        android:drawablePadding="5dp"
        android:text="设置密码"
        android:textColor="@color/black"
        android:textSize="18sp" />


    <EditText
        android:id="@+id/et_input_pwd"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:hint="请输入密码" />

    <EditText
        android:id="@+id/et_pwd_confirm"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:hint="请再次输入密码" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginRight="5dp"
            android:layout_weight="1"
            android:background="@drawable/blue_selector"
            android:text="确定"
            android:textColor="@color/white" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/white_selector"
            android:text="取消" />
    </LinearLayout>

</LinearLayout>

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void showSetDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setView(R.layout.dialog_set_pwd);
        builder.show();
    }

MSG 03-03 弹窗布局控件获取并设置点击事件

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void showSetDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        View view = View.inflate(this, R.layout.dialog_set_pwd, null);
        builder.setView(view);
        btDetermine = view.findViewById(R.id.bt_determine);
        btCancel = view.findViewById(R.id.bt_cancel);
        etInputPwd = view.findViewById(R.id.et_input_pwd);
        etPwdConfirm = view.findViewById(R.id.et_pwd_confirm);

        //根据build设置的数据,构造一个dialog
        final AlertDialog dialog = builder.create();
        btDetermine.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dialog.dismiss();
            }
        });
        btCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dialog.dismiss();
            }
        });

        builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialogInterface) {

            }
        });
        //builder.show();
        dialog.show();
    }

MSG 03-04 保存密码&输入密码弹窗

点击确认时,保存密码,SharedPreferences存储
                String pwd = etInputPwd.getText().toString().trim();
                String pwdConfirm = etPwdConfirm.getText().toString().trim();
                if (TextUtils.isEmpty(pwd) || TextUtils.isEmpty(pwdConfirm)) {
                    Toast.makeText(HomeActivity.this, "输入内容不能为空!", Toast.LENGTH_SHORT).show();
                } else {
                    if (pwd.equals(pwdConfirm)) {
                        //保存密码
                        PrefUtils.putString(getApplicationContext(), GlobalConstants.SP_FILE, GlobalConstants.PREF_PASSWORD, pwd);
                        //PrefUtils.putString(getApplicationContext(),GlobalConstants.SP_FILE,GlobalConstants.PREF_PASSWORD_CONFIRM,pwdConfirm);

                        dialog.dismiss();
                    } else {
                        Toast.makeText(HomeActivity.this, "输入密码不一致!", Toast.LENGTH_SHORT).show();
                    }
                }

**MSG 03-05 MD5介绍 **

特点:
1、可以将任何数据(字符串或文件)加密成32位长度的16进制字符串(0-f)
2、不可逆,只能加密,无法解密,单向加密算法
3、如果数据一样,那么计算出的md5就一定一样,如果不同,计算出的基本不同---哈希碰撞

MSG 03-06 设置向导1页面布局

方法1:
    <TextView
        style="@style/TitleStyle"
        android:gravity="left|center_vertical"
        android:text="欢迎使用手机防盗" />

方法2:
    <!--标题栏样式-->
    <style name="TitleStyle">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">50dp</item>
        <item name="android:background">@color/global_blue</item>
        <item name="android:gravity">center</item>
        <item name="android:textColor">@color/white</item>
        <item name="android:textSize">22sp</item>
    </style>

    <style name="SubTitleStyle" parent="TitleStyle">
        <item name="android:gravity">center_vertical</item>

设置向导1页面布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity.Setup1Activity">

    <TextView
        style="@style/SubTitleStyle"
        android:text="1. 欢迎使用手机防盗" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="您的手机防盗卫士" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:src="@drawable/step_1" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_alignParentBottom="true"
            android:background="@drawable/blue_selector"
            android:drawableRight="@drawable/next"
            android:text="下一步"
            android:textColor="@color/white" />
    </RelativeLayout>

</LinearLayout>

**MSG 03-07 设置向导2页面布局 **
**MSG 03-08 设置向导3,4,5页面布局 **
**MSG 03-09 手机防盗主页面布局开发 **
**MSG 03-10 设置向导页只进入一次 **

2018-10-07 11:12:35.945 32753-32753/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 32753
    java.lang.RuntimeException: Unable to start activity ComponentInfo{cn.nubia.mobilesecurityguard/cn.nubia.mobilesecurityguard.activity.SetupMainActivity}: android.view.InflateException: Binary XML file line #32: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3194)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3302)
        at android.app.ActivityThread.-wrap12(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1891)
        at android.os.Handler.dispatchMessage(Handler.java:108)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
     Caused by: android.view.InflateException: Binary XML file line #32: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:775)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:741)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:874)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:835)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
        at android.support.v7.app.AppCompatDelegateImplV9.setContentView(AppCompatDelegateImplV9.java:287)
        at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:139)
        at cn.nubia.mobilesecurityguard.activity.SetupMainActivity.onCreate(SetupMainActivity.java:13)
        at android.app.Activity.performCreate(Activity.java:7372)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1218)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3147)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3302)
        at android.app.ActivityThread.-wrap12(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1891)
        at android.os.Handler.dispatchMessage(Handler.java:108)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
2018-10-07 11:12:35.951 1155-5873/? W/ActivityManager:   Force finishing activity cn.nubia.mobilesecurityguard/.activity.SetupMainActivity

布局:大小写问题
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/black" />

**MSG 03-11 重新进入设置向导&颜色状态选择器 **
**MSG 03-12 activity切换动画 **

退出动画
anim_next_exit.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fromXDelta="0"
    android:toXDelte="-100%">
</translate>
进入动画
anim_next_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fromXDelta="100%"
    android:toXDelte="0">
</translate>

在finish()界面后执行
 //activity切换动画,动画的资源文件res/anim
 overridePendingTransition(R.anim.anim_next_enter, R.anim.anim_next_exit);
上一页进入
anim_pre_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fromXDelta="-100%"
    android:toXDelte="0">
</translate>
上一页退出
anim_pre_exit.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fromXDelta="100%"
    android:toXDelte="0">
</translate>

**MSG 03-13 手势识别器的使用 **

    //监听activity的触摸、按下、移动、抬起
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将触摸动作传递给手势识别器,由手势识别器进行分析
        mDetector.onTouchEvent(event);
        
        return super.onTouchEvent(event);
    }
}

手势识别器分析动作
    private GestureDetector mDetector;
            mDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
            //飞,抛动作,快速滑动
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float x, float y) {
                //不能斜着划
                if(Math.abs(e1.getY() -e2.getY()) > 100) {
                    return true;
                }
                //不能划太慢
                if (Math.abs(x) < 150) {

                }
                if (e2.getX() - e1.getX() > 200) {
                    //上一页
                }
                if (e1.getX() - e2.getX() > 200) {
                    //下一页
                }

                return false;
            }
        });

**MSG 03-14 设置向导页基类封装 **

public abstract class BaseSetupActivity extends AppCompatActivity {

    private GestureDetector mDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
            //飞,抛动作,快速滑动
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float x, float y) {
                //不能斜着划
                if (Math.abs(e1.getY() - e2.getY()) > 100) {
                    return true;
                }
                //不能划太慢
                if (Math.abs(x) < 150) {

                }
                if (e2.getX() - e1.getX() > 200) {
                    //上一页
                }
                if (e1.getX() - e2.getX() > 200) {
                    //下一页
                    goToNext();
                }
                return super.onFling(e1, e2, x, y);
            }
        });
    }
抽象方法,必须是在抽象类中,并且子类必须实现父类的抽象方法
    public abstract void goToNext();

    public abstract void goToPre();
}

**MSG 04-02 绑定SIM卡操作 **

    /**
     * sim卡绑定逻辑:
     * 每张卡都有唯一的序列号,将序列号保存在本地,
     * 重启手机时,获取当前的序列号和本地保存的序列号比对
     * 如果不一致则已变更
     */
        //电话管理器
        TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);

    提示需要权限:
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    提示需要检查
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling 考虑打电话
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            这里请求缺少权限,然后覆盖
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            //处理用户授予权限的情况。 请参阅文档
            return;
        }
        String simSerialNumber = tm.getSimSerialNumber();

完整绑定sim卡代码:
    @TargetApi(Build.VERSION_CODES.M)
    private void checkCTA() {
        tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling 考虑打电话
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            这里请求缺少权限,然后覆盖
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            //处理用户授予权限的情况。 请参阅文档
            Log.e("gordon", "没有READ_PHONE_STATE权限!");
            simSerialNumber = tm.getSimSerialNumber();
            requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE}, 0);
        } else {
            simSerialNumber = tm.getSimSerialNumber();
            Log.e("gordon", "有READ_PHONE_STATE权限!");
            bindSimCard();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 0:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //弹出框 点击始终允许
                    Log.e("gordon", "点击始终允许,已允许READ_PHONE_STATE权限!");
                    bindSimCard();
                } else {
                    //弹出框 点击禁止,请打开相机权限
                    Toast.makeText(this, "点击禁止,请允许READ_PHONE_STATE权限!", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }

    /**
     * sim卡绑定逻辑:
     * 每张卡都有唯一的序列号,将序列号保存在本地,
     * 重启手机时,获取当前的序列号和本地保存的序列号比对
     * 如果不一致则已变更
     */
    private void bindSimCard() {

        //如果绑定就解绑,解绑就绑定
        String isBindSim = PrefUtils.getString(this, GlobalConstants.SP_FILE, GlobalConstants.SIM_SERIAL_NUMBER, "");
        if (TextUtils.isEmpty(isBindSim)) {
            Log.e("gordon", "绑定sim卡!");
            Log.e("gordon", "simSerialNumber:" + simSerialNumber);
            PrefUtils.putString(this, GlobalConstants.SP_FILE, GlobalConstants.SIM_SERIAL_NUMBER, simSerialNumber);
            ivBindSim.setImageResource(R.drawable.lock);
        } else {
            Log.e("gordon", "解绑sim卡!");
            //PrefUtils.putString(this, GlobalConstants.SP_FILE, GlobalConstants.SIM_SERIAL_NUMBER, null);
            //直接删除键值对
            PrefUtils.removeSpKey(this,GlobalConstants.SP_FILE,GlobalConstants.SIM_SERIAL_NUMBER);
            ivBindSim.setImageResource(R.drawable.unlock);
        }
    }
注意:下次返回再进入数据回显
    @Override
    public void initData() {
        //下次返回再进入数据回显,界面锁图标
        String isBindSim = PrefUtils.getString(this, GlobalConstants.SP_FILE, GlobalConstants.SIM_SERIAL_NUMBER, "");
        if(TextUtils.isEmpty(isBindSim)) {
            ivBindSim.setImageResource(R.drawable.unlock);
        } else {
            ivBindSim.setImageResource(R.drawable.lock);
        }
    }
点击下一步前判断是否绑定sim卡,如果绑定继续
    @Override
    public void goToNext() {
        String isBindSim = PrefUtils.getString(this, GlobalConstants.SP_FILE, GlobalConstants.SIM_SERIAL_NUMBER, "");
        if(TextUtils.isEmpty(isBindSim)) {
            return;
        } else {
            Intent intent1 = new Intent(Setup2Activity.this, Setup3Activity.class);
            startActivity(intent1);
            finish();
        }
    }

**MSG 04-03 监听开启重启&判断sim卡变化 **

1、在清单文件中注册广播
        <receiver
            android:name=".receiver.BootReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            </intent-filter>
        </receiver>
2、需要权限
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
3、监听开机,实现sim卡状态变化
package cn.nubia.mobilesecurityguard.receiver;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import cn.nubia.mobilesecurityguard.GlobalConstants;
import cn.nubia.mobilesecurityguard.gordon_utils.PrefUtils;

import static android.content.Context.TELEPHONY_SERVICE;

/**
 * 开机重启广播
 */
public class BootReceiver extends BroadcastReceiver {

    private Context mContext;
    private String localSimSerialNumber;

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e("gordon","手机重启了!");
        mContext = context;
        //android.intent.action.BOOT_COMPLETED
        //判断sim卡是否变化
        localSimSerialNumber = PrefUtils.getString(context, GlobalConstants.SP_FILE, GlobalConstants.SIM_SERIAL_NUMBER, "");
        if(TextUtils.isEmpty(localSimSerialNumber)) {
            return;
        } else {
            checkCTA();//检查权限,并且判断当前SIM卡序列号于保存的是否相等
        }
    }
M版本以上需要判断权限,因此无法实现,权限的判断必须依赖activity
    private void checkCTA() {

    }
}
未验证是否可以监听到手机重启

MSG 04-04 跳到系统联系人页面选择联系人

https://blog.csdn.net/lvkaixuan/article/details/61921530

    /**
     * 选择联系人两种方式
     * 1、跳到系统联系人界面
     * 2、直接操作联系人的数据库表
     */
    private void selectContact1() {
        Log.e("gordon","点击选择联系人!");
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setData(ContactsContract.Contacts.CONTENT_URI);
        startActivityForResult(intent,0);
    }

    /**
     *操作联系人数据库:三张表
     * 1、raw_contacts 联系人id
     * 2、data 联系人数据
     * 3、mimetypes 数据类型
     *
     *
     */

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //取出选择的电话号码
        if (resultCode == Activity.RESULT_OK && requestCode == 0) {
            Log.e("gordon","返回选择联系人数据成功!");
            //当前选择的联系人数据的uri
            Uri uri = data.getData();
            //1、查询联系人id
            Cursor cursor = getContentResolver().query(uri, new String[]{ContactsContract.Contacts._ID}, null, null, null);
            cursor.moveToNext();
            String contactId = cursor.getString(0);
            Log.e("gordon","contactId:"+contactId);
        }
    }

报错:   //cursor.moveToNext(); 如果不将游标下移就会报错数组越界
            String contactId = cursor.getString(0);
            
2018-10-13 01:58:25.166 9328-9328/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 9328
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=Intent { dat=content://com.android.contacts/contacts/lookup/134r501-51D151D1/674 flg=0x1 }} to activity {cn.nubia.mobilesecurityguard/cn.nubia.mobilesecurityguard.activity.Setup3Activity}: android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4932)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975)
        at android.app.ActivityThread.-wrap20(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950)
        at android.os.Handler.dispatchMessage(Handler.java:108)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
     Caused by: android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1
        at android.database.AbstractCursor.checkPosition(AbstractCursor.java:460)
        at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:136)
        at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:50)
        at android.database.CursorWrapper.getString(CursorWrapper.java:137)
        at cn.nubia.mobilesecurityguard.activity.Setup3Activity.onActivityResult(Setup3Activity.java:108)
        at android.app.Activity.dispatchActivityResult(Activity.java:7690)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4928)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975) 
        at android.app.ActivityThread.-wrap20(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950) 
        at android.os.Handler.dispatchMessage(Handler.java:108) 
        at android.os.Looper.loop(Looper.java:166) 
        at android.app.ActivityThread.main(ActivityThread.java:7425) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921) 


报错:没加权限
2018-10-13 02:14:47.735 1592-3105/? W/System.err: java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/data/phones from pid=10292, uid=10188 requires android.permission.READ_CONTACTS, or grantUriPermission()
2018-10-13 02:14:47.735 1592-3105/? E/DatabaseUtils: Writing exception to parcel
    java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/data/phones from pid=10292, uid=10188 requires android.permission.READ_CONTACTS, or grantUriPermission()
        at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:633)
        at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:503)
        at android.content.ContentProvider$Transport.query(ContentProvider.java:215)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:102)
        at android.os.Binder.execTransact(Binder.java:675)
2018-10-13 02:14:47.742 10292-10292/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 10292
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=Intent { dat=content://com.android.contacts/contacts/lookup/134r501-51D151D1/674 flg=0x1 }} to activity {cn.nubia.mobilesecurityguard/cn.nubia.mobilesecurityguard.activity.Setup3Activity}: java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/data/phones from pid=10292, uid=10188 requires android.permission.READ_CONTACTS, or grantUriPermission()
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4932)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975)
        at android.app.ActivityThread.-wrap20(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950)
        at android.os.Handler.dispatchMessage(Handler.java:108)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
     Caused by: java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/data/phones from pid=10292, uid=10188 requires android.permission.READ_CONTACTS, or grantUriPermission()
        at android.os.Parcel.readException(Parcel.java:1945)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
        at android.content.ContentProviderProxy.query(ContentProviderNative.java:418)
        at android.content.ContentResolver.query(ContentResolver.java:766)
        at android.content.ContentResolver.query(ContentResolver.java:716)
        at android.content.ContentResolver.query(ContentResolver.java:667)
        at cn.nubia.mobilesecurityguard.activity.Setup3Activity.onActivityResult(Setup3Activity.java:112)
        at android.app.Activity.dispatchActivityResult(Activity.java:7690)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4928)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975) 
        at android.app.ActivityThread.-wrap20(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950) 
        at android.os.Handler.dispatchMessage(Handler.java:108) 
        at android.os.Looper.loop(Looper.java:166) 
        at android.app.ActivityThread.main(ActivityThread.java:7425) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921) 

清单文件:
    <uses-permission android:name="android.permission.READ_CONTACTS"/>

报错:动态权限问题
2018-10-13 02:25:20.148 1592-2651/? W/System.err: java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/data/phones from pid=12662, uid=10189 requires android.permission.READ_CONTACTS, or grantUriPermission()
2018-10-13 02:25:20.148 1592-2651/? E/DatabaseUtils: Writing exception to parcel
    java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/data/phones from pid=12662, uid=10189 requires android.permission.READ_CONTACTS, or grantUriPermission()
        at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:633)
        at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:503)
        at android.content.ContentProvider$Transport.query(ContentProvider.java:215)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:102)
        at android.os.Binder.execTransact(Binder.java:675)
2018-10-13 02:25:20.154 12662-12662/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 12662
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=Intent { dat=content://com.android.contacts/contacts/lookup/134r489-77AC77AC/662 flg=0x1 }} to activity {cn.nubia.mobilesecurityguard/cn.nubia.mobilesecurityguard.activity.Setup3Activity}: java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/data/phones from pid=12662, uid=10189 requires android.permission.READ_CONTACTS, or grantUriPermission()
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4932)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975)
        at android.app.ActivityThread.-wrap20(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950)
        at android.os.Handler.dispatchMessage(Handler.java:108)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
     Caused by: java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/data/phones from pid=12662, uid=10189 requires android.permission.READ_CONTACTS, or grantUriPermission()
        at android.os.Parcel.readException(Parcel.java:1945)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
        at android.content.ContentProviderProxy.query(ContentProviderNative.java:418)
        at android.content.ContentResolver.query(ContentResolver.java:766)
        at android.content.ContentResolver.query(ContentResolver.java:716)
        at android.content.ContentResolver.query(ContentResolver.java:667)
        at cn.nubia.mobilesecurityguard.activity.Setup3Activity.onActivityResult(Setup3Activity.java:112)
        at android.app.Activity.dispatchActivityResult(Activity.java:7690)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4928)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975) 
        at android.app.ActivityThread.-wrap20(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950) 
        at android.os.Handler.dispatchMessage(Handler.java:108) 
        at android.os.Looper.loop(Looper.java:166) 
        at android.app.ActivityThread.main(ActivityThread.java:7425) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921) 

完整跳转系统联系人代码:
    /**
     * 选择联系人两种方式
     * 1、跳到系统联系人界面
     * 2、直接操作联系人的数据库表
     */
    private void selectContact1() {
        Log.e("gordon","点击选择联系人!");
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setData(ContactsContract.Contacts.CONTENT_URI);
        startActivityForResult(intent,0);
    }

    /**
     *操作联系人数据库:三张表
     * 1、raw_contacts 联系人id
     * 2、data 联系人数据
     * 3、mimetypes 数据类型
     *
     *
     */
    private Intent contactData;

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        contactData = data;
        //取出选择的电话号码
        if (resultCode == Activity.RESULT_OK && requestCode == 0) {
            Log.e("gordon","返回选择联系人数据成功!");
            checkCTA();
        }
    }

    @TargetApi(Build.VERSION_CODES.M)
    private void checkCTA() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            这里请求缺少权限,然后覆盖
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            //处理用户授予权限的情况。 请参阅文档
            Log.e("gordon", "没有READ_CONTACTS权限!");
            requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0);
        } else {
            Log.e("gordon", "有READ_CONTACTS权限!");
            String number = retureContacts();
            etSelectNum.setText(number);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 0:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //弹出框 点击始终允许
                    Log.e("gordon", "点击始终允许,已允许READ_PHONE_STATE权限!");
                    String number = retureContacts();
                    etSelectNum.setText(number);
                } else {
                    //弹出框 点击禁止,请打开相机权限
                    Toast.makeText(this, "点击禁止,请允许READ_PHONE_STATE权限!", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }

    private String retureContacts(){
        //当前选择的联系人数据的uri
        Uri uri = contactData.getData();
        //1、查询联系人id
        Cursor cursorContactId = getContentResolver().query(uri, new String[]{ContactsContract.Contacts._ID}, null, null, null);
        cursorContactId.moveToNext();
        String contactId = cursorContactId.getString(0);
        Log.e("gordon","contactId:"+contactId);
        //2、根据id查询电话号码
        Cursor cursorNumber = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER}, ContactsContract.CommonDataKinds.Phone.CONTACT_ID+"=?", new String[]{contactId}, null);
        if(cursorNumber != null && cursorNumber.moveToNext()) {
            String number = cursorNumber.getString(0);
            Log.e("gordon","number:"+number);
            cursorNumber.close();
            return number;
        }
        return null;
    }
    
1、edittext:设置数据
etSelectNum.setText(number);

2、将游标移动到最后面

3、选择电话号为空
        if(cursorNumber != null && cursorNumber.moveToNext()) {
            String number = cursorNumber.getString(0);
            Log.e("gordon","number:"+number);
            return number;
        }
4、过滤-、空格、符号
            number.trim();//去掉两侧空格
            number.replaceAll(" ","");//用空串提掉空格
            number.replaceAll("-","");
            number.replaceAll("\\(","");
            number.replaceAll("\\)","");
            
报错:
2018-10-13 14:42:01.198 2758-2758/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 2758
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=Intent { dat=content://com.android.contacts/contacts/lookup/134r538-C2D0AAB8A8/685 flg=0x1 }} to activity {cn.nubia.mobilesecurityguard/cn.nubia.mobilesecurityguard.activity.Setup3Activity}: android.database.CursorIndexOutOfBoundsException: Index 0 requested, with a size of 0
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4932)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975)
        at android.app.ActivityThread.-wrap20(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950)
        at android.os.Handler.dispatchMessage(Handler.java:108)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
     Caused by: android.database.CursorIndexOutOfBoundsException: Index 0 requested, with a size of 0
        at android.database.AbstractCursor.checkPosition(AbstractCursor.java:460)
        at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:136)
        at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:50)
        at android.database.CursorWrapper.getString(CursorWrapper.java:137)
        at cn.nubia.mobilesecurityguard.activity.Setup3Activity.retureContacts(Setup3Activity.java:170)
        at cn.nubia.mobilesecurityguard.activity.Setup3Activity.checkCTA(Setup3Activity.java:136)
        at cn.nubia.mobilesecurityguard.activity.Setup3Activity.onActivityResult(Setup3Activity.java:116)
        at android.app.Activity.dispatchActivityResult(Activity.java:7690)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4928)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975) 
        at android.app.ActivityThread.-wrap20(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950) 
        at android.os.Handler.dispatchMessage(Handler.java:108) 
        at android.os.Looper.loop(Looper.java:166) 
        at android.app.ActivityThread.main(ActivityThread.java:7425) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921) 

解决:
    private String retureContacts(){
        //当前选择的联系人数据的uri
        Uri uri = contactData.getData();
        //1、查询联系人id
        Cursor cursorContactId = getContentResolver().query(uri, new String[]{ContactsContract.Contacts._ID}, null, null, null);
        cursorContactId.moveToNext();
        String contactId = cursorContactId.getString(0);
        Log.e("gordon","contactId:"+contactId);
        //2、根据id查询电话号码
        Cursor cursorNumber = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER}, ContactsContract.CommonDataKinds.Phone.CONTACT_ID+"=?", new String[]{contactId}, null);
        if(cursorNumber != null && cursorNumber.moveToNext()) {
            String number = cursorNumber.getString(0);
            Log.e("gordon","number:"+number);
            return number;
        }
        return null;
    }

点下一步保存电话号码
    @Override
    public void goToNext() {
        String number = etSelectNum.getText().toString().trim();
        if (!TextUtils.isEmpty(number)) {
            Intent intent1 = new Intent(Setup3Activity.this, Setup4Activity.class);
            startActivity(intent1);
            finish();
        }
    }
    
异常:号码为空
2018-10-13 15:06:17.316 1507-1705/? E/PhoneStatusBar: Setting not found exception : accessibility_display_magnification_navbar_enabled
2018-10-13 15:06:17.337 8226-8226/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 8226
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=Intent { dat=content://com.android.contacts/contacts/lookup/134r538-C2D0AAB8A8/685 flg=0x1 }} to activity {cn.nubia.mobilesecurityguard/cn.nubia.mobilesecurityguard.activity.Setup3Activity}: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.String.trim()' on a null object reference
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4932)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975)
        at android.app.ActivityThread.-wrap20(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950)
        at android.os.Handler.dispatchMessage(Handler.java:108)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.String.trim()' on a null object reference
        at cn.nubia.mobilesecurityguard.activity.Setup3Activity.checkCTA(Setup3Activity.java:147)
        at cn.nubia.mobilesecurityguard.activity.Setup3Activity.onActivityResult(Setup3Activity.java:125)
        at android.app.Activity.dispatchActivityResult(Activity.java:7690)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4928)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4975) 
        at android.app.ActivityThread.-wrap20(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1950) 
        at android.os.Handler.dispatchMessage(Handler.java:108) 
        at android.os.Looper.loop(Looper.java:166) 
        at android.app.ActivityThread.main(ActivityThread.java:7425) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921) 
解决:
            if (number != null) {
                //过滤-、空格、符号
                number.trim();//去掉两侧空格
                number.replaceAll(" ", "");//用空串提掉空格
                number.replaceAll("-", "");
                number.replaceAll("\\(", "");
                number.replaceAll("\\)", "");
                etSelectNum.setText(number);
            } else {
                etSelectNum.setText("");
            }

MSG 04-05 选择联系人细节处理
**MSG 04-06 选择联系人小结 **
**MSG 04-07 手机防盗总开关控制 **

是否选中checkbox
    @Override
    public void goToNext() {
        boolean checked = cbStartProtect.isChecked();
        if (checked) {
            PrefUtils.putBoolean(getApplicationContext(), GlobalConstants.SP_FILE, GlobalConstants.SETUP_SUCCESS, checked);
            Intent intent1 = new Intent(Setup5Activity.this, SetupMainActivity.class);
            startActivity(intent1);
            finish();
        } else {
            Toast.makeText(this, "未选中开启防盗保护!", Toast.LENGTH_SHORT).show();
        }
    }
开关回显:
        boolean protect = PrefUtils.getBoolean(this, GlobalConstants.SP_FILE, GlobalConstants.FINISH_SETUP, false);
        cbStartProtect.setChecked(protect);

**MSG 04-08 手机防盗主页面数据回显 **
**MSG 04-09 发送报警短信 **

1、
        SmsManager sm = SmsManager.getDefault();
        sm.sendTextMessage(number,null,"你的手机被盗了!",null,null);
2、没加权限
报错:
2018-10-13 16:27:16.517 1861-2012/? W/System.err: java.lang.SecurityException: Sending SMS message: uid 10190 does not have android.permission.SEND_SMS.
2018-10-13 16:27:16.526 27752-27752/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 27752
    java.lang.SecurityException: Sending SMS message: uid 10190 does not have android.permission.SEND_SMS.
        at android.os.Parcel.readException(Parcel.java:1945)
        at android.os.Parcel.readException(Parcel.java:1891)
        at com.android.internal.telephony.ISms$Stub$Proxy.sendTextForSubscriber(ISms.java:881)
        at android.telephony.SmsManager.sendTextMessageInternal(SmsManager.java:355)
        at android.telephony.SmsManager.sendTextMessage(SmsManager.java:334)
        at cn.nubia.mobilesecurityguard.activity.SetupMainActivity.sendSms(SetupMainActivity.java:71)
        at cn.nubia.mobilesecurityguard.activity.SetupMainActivity.onClick(SetupMainActivity.java:64)
        at android.view.View.performClick(View.java:6291)
        at android.view.View$PerformClick.run(View.java:24931)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

解决:
    <uses-permission android:name="android.permission.SEND_SMS"/>

加了权限还是报错:
2018-10-13 16:30:16.899 1861-17136/? W/System.err: java.lang.SecurityException: Sending SMS message: uid 10190 does not have android.permission.SEND_SMS.
2018-10-13 16:30:16.903 28703-28703/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 28703
    java.lang.SecurityException: Sending SMS message: uid 10190 does not have android.permission.SEND_SMS.
        at android.os.Parcel.readException(Parcel.java:1945)
        at android.os.Parcel.readException(Parcel.java:1891)
        at com.android.internal.telephony.ISms$Stub$Proxy.sendTextForSubscriber(ISms.java:881)
        at android.telephony.SmsManager.sendTextMessageInternal(SmsManager.java:355)
        at android.telephony.SmsManager.sendTextMessage(SmsManager.java:334)
        at cn.nubia.mobilesecurityguard.activity.SetupMainActivity.sendSms(SetupMainActivity.java:71)
        at cn.nubia.mobilesecurityguard.activity.SetupMainActivity.onClick(SetupMainActivity.java:64)
        at android.view.View.performClick(View.java:6291)
        at android.view.View$PerformClick.run(View.java:24931)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

动态权限问题:
    private void sendSms() {
        SmsManager sm = SmsManager.getDefault();
        sm.sendTextMessage(number, null, "你的手机被盗了!", null, null);
    }

    @TargetApi(Build.VERSION_CODES.M)
    private void checkCTA() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            这里请求缺少权限,然后覆盖
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            //处理用户授予权限的情况。 请参阅文档
            Log.e("gordon", "没有SEND_SMS权限!");
            requestPermissions(new String[]{Manifest.permission.SEND_SMS}, 0);
        } else {
            Log.e("gordon", "有SEND_SMS权限!");
            sendSms();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 0:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //弹出框 点击始终允许
                    Log.e("gordon", "点击始终允许,已允许SEND_SMS权限!");
                    sendSms();
                } else {
                    //弹出框 点击禁止,请打开相机权限
                    Toast.makeText(this, "点击禁止,请允许SEND_SMS权限!", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }

**MSG 04-10 拦截短信功能 **

如果手机被盗,通过发送短信给安全号码报失
通过给被盗手机发送指定短信执行播放音乐等操作

1、创建一个拦截短信的广播

2、清单文件
静态注册的有序广播,有优先级
        <receiver
            android:name=".receiver.SmsReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="2147483647"> Integer最大值
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>
3、

**MSG 04-11 播放报警音乐 **
**MSG 04-12 手机定位原理 **
MSG 04-13 LocationManager获取经纬度
**MSG 04-14 发送经纬度短信及细节处理 **

MSG 05-02 超级管理员的使用

https://developer.android.google.cn/reference/android/app/admin/DeviceAdminReceiver
立即锁屏
        DevicePolicyManager mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
        mDPM.lockNow();

**MSG 05-03 设备策略管理器小结 **
MSG 05-04 设置向导4激活设备管理员

1、清单文件
        <receiver android:name="cn.nubia.mobilesecurityguard.receiver.AdminReceiver"
            android:label="@string/sample_device_admin"
            android:description="@string/sample_device_admin_description"
            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>
2、创建一个广播接受者
public class AdminReceiver extends DeviceAdminReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
    }
}
3、激活/关闭设备管理器
    private void startAdminManager() {
        Log.e("gordon", "点击激活设备管理员!");
        boolean isActive = mDPM.isAdminActive(mComponent);
        if (isActive) {
            mDPM.removeActiveAdmin(mComponent);
            ivAdmin.setImageResource(R.drawable.admin_inactivated);
        } else {
            //隐式意图
            Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
            intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mComponent);
            intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
                    "超级管理员,非常牛逼,赶紧激活!!!");
            startActivityForResult(intent, 0);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        boolean isActive = mDPM.isAdminActive(mComponent);
        if (!isActive) {
            ivAdmin.setImageResource(R.drawable.admin_inactivated);
        } else {
            ivAdmin.setImageResource(R.drawable.admin_activated);
        }
    }
点击锁屏:
报错:
2018-10-14 15:49:04.026 19585-19585/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 19585
    java.lang.SecurityException: No active admin owned by uid 10190 for policy #3
        at android.os.Parcel.readException(Parcel.java:1945)
        at android.os.Parcel.readException(Parcel.java:1891)
        at android.app.admin.IDevicePolicyManager$Stub$Proxy.lockNow(IDevicePolicyManager.java:4508)
        at android.app.admin.DevicePolicyManager.lockNow(DevicePolicyManager.java:3095)
        at android.app.admin.DevicePolicyManager.lockNow(DevicePolicyManager.java:3067)
        at cn.nubia.mobilesecurityguard.activity.Setup4Activity.lockScreen(Setup4Activity.java:91)
        at cn.nubia.mobilesecurityguard.activity.Setup4Activity.onClick(Setup4Activity.java:85)
        at android.view.View.performClick(View.java:6291)
        at android.view.View$PerformClick.run(View.java:24931)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

解决:
    private void lockScreen() {
        boolean isActive = mDPM.isAdminActive(mComponent);
        if (isActive) {
            mDPM.lockNow();
        }
    }

MSG 05-05 归属地查询页面布局

    <EditText
        android:id="@+id/et_search_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入要查询的内容"
        android:inputType="number" />

MSG 05-06 shape形状使用

xml画shape图形
1、在drawable下创建shape_input_select.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/gray"/>
    <!--矩形圆角-->
    <corners android:radius="10dp"></corners>
    <!--渐变-->
    <gradient android:startColor="@color/red"></gradient>
</shape>

MSG 05-07 文本框状态选择器

EditText 有焦点且可用
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/shape_input_select" android:state_focused="true" android:state_enabled="true"/>
<item android:drawable="@drawable/shape_input_normal"/>
</selector>

MSG 05-08 归属地查询原理&拷贝数据库

/**
 * 归属地查询页面
 * <p>
 * 数据库中保存了号码的归属地, 110->报警
 * <p>
 * 1. 联网, 把号码传给服务器, 由服务器在数据库中查询,返回给本地
 * 2. 将数据库放在本地项目中, 直接本地查询
 * <p>
 * 手机号码前7位就可以确定什么运营商, 什么地区
 * select outkey from data1 where id="1351234"
 * select location from data2 where id=930
 * <p>
 * 嵌套查询:  select location from data2 where id=(select outkey from data1 where id="1351234")
 */
1、从本地查询数据库
select outkey from data1 where id="1351234"
select location from data2 where id=930

在这里插入图片描述
在这里插入图片描述

建立本地数据库
1、在main目录下新建assets目录(资产目录)
2、将数据库拷贝到目录下
3、在项目目录下创建db.dao,建立AddressDao.java
4、需要将资产目录下的数据库文件拷贝到本地文件路径下(一般在闪屏页面操作)
    /**
     * 拷贝数据库文件 工具类
     */
    private void copyDb(String dbName) {
        //输出流 data/data/packagename/files
        File filesDir = getFilesDir();
        File desFile = new File(filesDir, dbName);
        //只需拷贝一次,如果存在不需拷贝
        if (desFile.exists()) {
            Log.e("gordon","数据库"+dbName+"已存在!");
            return;
        }
        //资产目录管理器
        AssetManager assetManager = getAssets();
        //获取资产目录文件输入流
        try {
            in = assetManager.open(dbName);
            out = new FileOutputStream(desFile);
            int len = 0;
            byte[] buffer = new byte[1024*8];
            while ((len = in.read(buffer))!= -1) {
                out.write(buffer,0,len);
            }
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Log.e("gordon","拷贝数据库"+dbName+"完成!");
    }
5、在AddressDao中查询数据库
package cn.nubia.mobilesecurityguard.db.dao;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
 * 查询归属地数据库
 */
public class AddressDao {

    public static String getAddress(Context ctx, String number) {
        //读取数据库文件
        String dbFilePath = ctx.getFilesDir().getAbsolutePath();
        //以只读方式打开数据库,参1:数据库文件的本地路径
        SQLiteDatabase database = SQLiteDatabase.openDatabase(dbFilePath + "/address.db", null, SQLiteDatabase.OPEN_READONLY);
        //需要将资产目录下的数据库文件拷贝到本地文件路径下(一般在闪屏页面操作)copyDb()
        //data/data/packagename/files/下
        /**
         * 查询数据库2种方式:
         * 1、      database.query();
         * 2、      直接sql语句
         */
        //database.query();
        String address = "未知号码";
        String sql = "select location from data2 where id=(select outkey from data1 where id=?)";
        //截取前7位
        Cursor cursor = database.rawQuery(sql, new String[]{number.substring(0, 7)});
        if (cursor!= null && cursor.moveToNext()) {
            //有记录
            address = cursor.getString(0);
            cursor.close();
        }
        database.close();
        return address;
    }
}

调用:
    private String searchAddress() {
        String number = etSearchContent.getText().toString().trim();
        if (!TextUtils.isEmpty(number)) {
            String address = AddressDao.getAddress(this, number);
            return address;
        } else {
            Toast.makeText(this, "查询内容不能为空", Toast.LENGTH_SHORT).show();
            return "没有输入任何号码!";
        }
    }

报错:目录路径错误
2018-10-14 20:14:54.077 29795-29795/cn.nubia.mobilesecurityguard E/SQLiteDatabase: Failed to open database '/data/user/0/cn.nubia.mobilesecurityguard/filesaddress.db'.
    android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (Sqlite code 14): Could not open database, (OS error - 2:No such file or directory)
        at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:223)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:207)
        at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:511)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:194)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:183)
        at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:880)
        at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:864)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:767)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:742)
        at cn.nubia.mobilesecurityguard.db.dao.AddressDao.getAddress(AddressDao.java:16)
        at cn.nubia.mobilesecurityguard.activity.AddressActivity.searchAddress(AddressActivity.java:54)
        at cn.nubia.mobilesecurityguard.activity.AddressActivity.onClick(AddressActivity.java:45)
        at android.view.View.performClick(View.java:6291)
        at android.view.View$PerformClick.run(View.java:24931)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
2018-10-14 20:14:54.080 29795-29795/cn.nubia.mobilesecurityguard E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.nubia.mobilesecurityguard, PID: 29795
    android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (Sqlite code 14): Could not open database, (OS error - 2:No such file or directory)
        at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:223)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:207)
        at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:511)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:194)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:183)
        at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:880)
        at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:864)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:767)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:742)
        at cn.nubia.mobilesecurityguard.db.dao.AddressDao.getAddress(AddressDao.java:16)
        at cn.nubia.mobilesecurityguard.activity.AddressActivity.searchAddress(AddressActivity.java:54)
        at cn.nubia.mobilesecurityguard.activity.AddressActivity.onClick(AddressActivity.java:45)
        at android.view.View.performClick(View.java:6291)
        at android.view.View$PerformClick.run(View.java:24931)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:101)
        at android.os.Looper.loop(Looper.java:166)
        at android.app.ActivityThread.main(ActivityThread.java:7425)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

MSG 05-09 raw和assets目录区别
MSG 05-10 归属地查询数据库封装

package cn.nubia.mobilesecurityguard.db.dao;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
 * 查询归属地数据库
 */
public class AddressDao {

    public static String getAddress(Context ctx, String number) {
        //读取数据库文件
        String dbFilePath = ctx.getFilesDir().getAbsolutePath();
        //以只读方式打开数据库,参1:数据库文件的本地路径
        SQLiteDatabase database = SQLiteDatabase.openDatabase(dbFilePath + "/address.db", null, SQLiteDatabase.OPEN_READONLY);
        //需要将资产目录下的数据库文件拷贝到本地文件路径下(一般在闪屏页面操作)copyDb()
        //data/data/packagename/files/下
        /**
         * 查询数据库2种方式:
         * 1、      database.query();
         * 2、      直接sql语句
         */
        //database.query();
        String address = "未知号码";
        //通过正则表达式匹配手机号码
        //1[3-8]+9位0-9的数字
        //^1[3-8]\d{9}$-->"^1[3-8]\\d{9}$"
        if (number.matches("^1[3-8]\\d{9}$")) {
            String sql = "select location from data2 where id=(select outkey from data1 where id=?)";
            //截取前7位
            Cursor cursor = database.rawQuery(sql, new String[]{number.substring(0, 7)});
            if (cursor!= null && cursor.moveToNext()) {
                //有记录
                address = cursor.getString(0);
                cursor.close();
            }
        }
        database.close();
        return address;
    }
}

MSG 05-11 手机号码正则表达式

资料/归属地查询/手机号码
        //通过正则表达式匹配手机号码
        //1[3-8]+9位0-9的数字
        //^1[3-8]\d{9}$-->"^1[3-8]\\d{9}$"
     
手机号码的正则表达式:
"^1[3-8]\\d{9}$"

MSG 05-12 查询其他号码

用区号匹配查询:完整代码
package cn.nubia.mobilesecurityguard.db.dao;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
 * 查询归属地数据库
 */
public class AddressDao {

    public static String getAddress(Context ctx, String number) {
        //读取数据库文件
        String dbFilePath = ctx.getFilesDir().getAbsolutePath();
        //以只读方式打开数据库,参1:数据库文件的本地路径
        SQLiteDatabase database = SQLiteDatabase.openDatabase(dbFilePath + "/address.db", null, SQLiteDatabase.OPEN_READONLY);
        //需要将资产目录下的数据库文件拷贝到本地文件路径下(一般在闪屏页面操作)copyDb()
        //data/data/packagename/files/下
        /**
         * 查询数据库2种方式:
         * 1、      database.query();
         * 2、      直接sql语句
         */
        //database.query();
        String address = "未知号码";
        //通过正则表达式匹配手机号码
        //1[3-8]+9位0-9的数字
        //^1[3-8]\d{9}$-->"^1[3-8]\\d{9}$"
        if (number.matches("^1[3-8]\\d{9}$")) {
            String sql = "select location from data2 where id=(select outkey from data1 where id=?)";
            //截取前7位
            Cursor cursor = database.rawQuery(sql, new String[]{number.substring(0, 7)});
            if (cursor != null && cursor.moveToNext()) {
                //有记录
                address = cursor.getString(0);
                cursor.close();
            }
        } else {
            //其他号码查询
            switch (number.length()) {
                case 3:
                    address = "报警电话";
                    break;
                case 5:
                    address = "银行";
                    break;
                default:
                    if(number.startsWith("0") && number.length() >=10 && number.length() <= 12) {
                        String sql = "select location from data2 where area=?";
                        //截取前4位
                        Cursor cursor = database.rawQuery(sql, new String[]{number.substring(1, 4)});
                        if (cursor != null && cursor.moveToNext()) {
                            //有记录
                            address = cursor.getString(0);
                            cursor.close();
                        }
                        if (address.equals("未知号码")) {
                            Cursor cursor2 = database.rawQuery(sql, new String[]{number.substring(1, 3)});
                            if (cursor2 != null && cursor2.moveToNext()) {
                                //有记录
                                address = cursor2.getString(0);
                                cursor2.close();
                            }
                        }
                    }
                    break;
            }
        }
        database.close();
        return address;
    }
}

MSG 05-13 监听文本框变化&查询归属地

        //监听文本框变化,查询归属地
        etSearchContent.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                Log.e("gordon","文本变化前");
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                //文本变化后查询数据库
                String address = AddressDao.getAddress(AddressActivity.this, s.toString().trim());
                tvSearchResult.setText(address);
            }
        });

MSG 05-14 全速定位项目页面&借鉴代码技巧

ctrl+shift+f 全局搜索

MSG 05-15 抖动动画原理
MSG 05-16 震动器的使用

1、代码
        Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
        vibrator.vibrate(5000);
2、权限
    <uses-permission android:name="android.permission.VIBRATE" />

MSG 06-02 来电监听显示归属地

开启服务
startService(new Intent(getApplicationContext(), AddressShowService.class));


package cn.nubia.mobilesecurityguard.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.widget.Toast;

import cn.nubia.mobilesecurityguard.db.dao.AddressDao;

public class AddressShowService extends Service {

    private TelephonyManager mTm;
    private AddressListener mListener;

    public AddressShowService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //来电监听
        mTm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        mListener = new AddressListener();
        mTm.listen(mListener, PhoneStateListener.LISTEN_CALL_STATE);
        //去电监听
    }

    class AddressListener extends PhoneStateListener {
        @Override
        public void onCallStateChanged(int state, String phoneNumber) {
            super.onCallStateChanged(state, phoneNumber);
            switch (state) {
                case TelephonyManager.CALL_STATE_RINGING:
                    String address = AddressDao.getAddress(getApplicationContext(), phoneNumber);
                    Toast.makeText(AddressShowService.this, address, Toast.LENGTH_SHORT).show();
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK://接通
                    break;
                case TelephonyManager.CALL_STATE_IDLE:
                    break;
            }
        }
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        //停止监听
        mTm.listen(mListener,PhoneStateListener.LISTEN_NONE);
    }
}
注意:在服务停止时,监听并没有取消,需要在onDestory()手动取消监听

MSG 06-03 去电监听&动态和静态广播区别

1、在服务里监听
        //去电监听
        receiver = new AddressReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_NEW_OUTGOING_CALL);
        registerReceiver(receiver, intentFilter);
2、注册权限
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"></uses-permission>
3、动态注册广播需要注销
    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
        receiver=null;
    }
4、
package cn.nubia.mobilesecurityguard.receiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

import cn.nubia.mobilesecurityguard.db.dao.AddressDao;

/**
 * 去电监听的广播
 */
public class AddressReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        //获取去电电话号码
        String number = getResultData();
        String address = AddressDao.getAddress(context, number);
        Log.e("gordon", "address:" + address);
        Toast.makeText(context, address, Toast.LENGTH_SHORT).show();
    }
}

错误:在8.0上还是监听不到
动态注册和静态注册广播的区别:
动态注册:可以取消
静态注册:不运行代码

MSG 06-04 判断服务有没有运行

package cn.nubia.mobilesecurityguard.gordon_utils;

import android.app.ActivityManager;
import android.content.Context;
import java.util.List;

/**
 * 判断服务是否运行
 */
public class ServiceStatusUtils {

    public static boolean isServiceRunning(Context ctx, Class<? extends Service> clazz) {
        ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> runningServices = am.getRunningServices(100);
        for (ActivityManager.RunningServiceInfo info : runningServices) {
            String className = info.service.getClassName();
            if (className.equals(clazz.getName())) {
                return true;
            }
        }
        return false;
    }
}

点击开启或关闭服务
    private void startAddressShowService() {
        boolean serviceRunning = ServiceStatusUtils.isServiceRunning(this, AddressShowService.class);
        if (serviceRunning) {
            stopService(new Intent(getApplicationContext(), AddressShowService.class));
        } else {
            startService(new Intent(getApplicationContext(), AddressShowService.class));
        }
    }

在首次启动或者从后台进入都会走
    @Override
    protected void onStart() {
        super.onStart();
        boolean serviceRunning = ServiceStatusUtils.isServiceRunning(this, AddressShowService.class);
        addressShowSetting.setToggleOn(serviceRunning);
    }

**MSG 06-05 显示窗口布局&WindowManager **
在这里插入图片描述

响铃时添加窗口布局:
    private void showToast(String address) {
        WindowManager mWM = (WindowManager) getSystemService(WINDOW_SERVICE);

        //初始化布局参数
        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.format = PixelFormat.TRANSLUCENT;
        params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.setTitle("Toast");
        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        //初始化布局
        TextView textView = new TextView(this);
        textView.setText(address);
        textView.setTextSize(25);
        mWM.addView(textView, params);
    }
空闲时移除布局:
mWM.removeView(textView);

MSG 06-06 AddressToast的封装

package cn.nubia.mobilesecurityguard.utils;

import android.content.Context;
import android.graphics.PixelFormat;
import android.view.WindowManager;
import android.widget.TextView;

import static android.content.Context.WINDOW_SERVICE;

/**
 * 自定义归属地Toast
 */
public class AddressToast {

    private final WindowManager mWM;
    private final TextView textView;
    private final WindowManager.LayoutParams params;

    public AddressToast(Context ctx) {
        mWM = (WindowManager) ctx.getSystemService(WINDOW_SERVICE);
        //初始化布局参数
        params = new WindowManager.LayoutParams();
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.format = PixelFormat.TRANSLUCENT;
        params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.setTitle("Toast");
        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        //初始化布局
        textView = new TextView(ctx);

    }
    public void showToast(String text) {
        textView.setText(text);
        mWM.addView(textView, params);
    }

    public void hideToast() {
        //ctrl+alt+t 快捷键 自动生成代码块
        //view 没有添加给wm,移除会crash
        if(mWM != null && textView != null) {
            try {
                mWM.removeView(textView);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            }
        }
    }
}

MSG 06-07 修改归属地窗口布局
inflate
**MSG 06-08 窗口布局支持触摸 **

    /**
     * 可触摸:
     * 1、
     * //| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
     * 2、窗口级别:
     * params.type = WindowManager.LayoutParams.TYPE_PHONE;
     * 3、权限 没有会crash
     * <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"></uses-permission>
     */
             //初始化布局
        textView = new TextView(ctx);
        textView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        break;
                    case MotionEvent.ACTION_MOVE:
                        break;
                    case MotionEvent.ACTION_UP:
                        break;
                }
                return true;
            }
        });

**MSG 06-09 getX和getRawX的区别 **
在这里插入图片描述

                        //相对于触摸的控件位置
                        int startX = (int) event.getX();
                        int startY = (int) event.getY();

                        //-相对于屏幕位置
                        int startX = event.getRawX();
                        int startY = event.getRawY();

**MSG 06-10 触摸移动窗口布局 **

完整代码:
package cn.nubia.mobilesecurityguard.utils;

import android.content.Context;
import android.graphics.PixelFormat;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

import static android.content.Context.WINDOW_SERVICE;

/**
 * 自定义归属地Toast
 */
public class AddressToast {

    private final WindowManager mWM;
    private final TextView textView;
    private final WindowManager.LayoutParams params;
    private int startX;
    private int startY;
    private int endX;
    private int endY;

    public AddressToast(Context ctx) {
        mWM = (WindowManager) ctx.getSystemService(WINDOW_SERVICE);
        //初始化布局参数
        params = new WindowManager.LayoutParams();
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.format = PixelFormat.TRANSLUCENT;
        //params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.type = WindowManager.LayoutParams.TYPE_PHONE;
        params.setTitle("Toast");
        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
                //| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        //初始化布局
        textView = new TextView(ctx);
        textView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        //1 记录启动坐标-相对于屏幕位置
                        startX = (int) event.getRawX();
                        startY = (int) event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //2 记录终点坐标-相对于屏幕位置
                        endX = (int) event.getRawX();
                        endY = (int) event.getRawY();
                        //3 偏移量
                        int dx = endX - startX;
                        int dy = endY -startY;
                        //4 根据偏移量更新布局位置
                        params.x += dx;
                        params.y += dy;
                        mWM.updateViewLayout(textView,params);

                        //5 更新启动坐标
                        startX = endX;
                        startY = endY;
                        break;
                    case MotionEvent.ACTION_UP:
                        break;
                }
                return true;
            }
        });
    }

    public void showToast(String text) {
        textView.setText(text);
        mWM.addView(textView, params);
    }

    public void hideToast() {
        //ctrl+alt+t 快捷键 自动生成代码块
        //view 没有添加给wm,移除会crash
        if (mWM != null && textView != null) {
            try {
                mWM.removeView(textView);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            }
        }
    }

    /**
     * 可触摸:
     * 1、
     * //| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
     * 2、窗口级别:
     * params.type = WindowManager.LayoutParams.TYPE_PHONE;
     * 3、权限 没有会crash
     * <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"></uses-permission>
     */
}

**MSG 06-11 自定义dialog&设置布局&去掉标题和背景 **

1、继承dialog
package cn.nubia.mobilesecurityguard.view;

import android.app.Dialog;
import android.content.Context;
import cn.nubia.mobilesecurityguard.R;

/**
 * 自定义dialog
 * 1、给dialog设置布局
 * 2、需要清除标题栏
 */
public class AddressDialog extends Dialog {
    public AddressDialog(Context context) {
    	//指定样式
        super(context,R.style.AddressDialogStyle);
        setContentView(R.layout.address_dialog);
    }
}

2、设置自定义布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_title"
        android:text="选择归属地样式"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <ListView
        android:id="@+id/lv_address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </ListView>

</LinearLayout>
3、在value/style.xml
    <!--归属地弹窗样式-->
    <style name="AddressDialogStyle" parent="Theme.AppCompat.Dialog">
        <item name="windowNoTitle">true</item>
        <item name="android:windowBackground">@null</item>
    </style>

**MSG 06-12 dialog显示在屏幕下方 **

        //修改dialog所在窗口window的位置
        Window window = getWindow();
        WindowManager.LayoutParams params = window.getAttributes();
        params.gravity = Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL;
        window.setAttributes(params);

**MSG 06-13 给弹窗ListView设置数据 **

1、
package cn.nubia.mobilesecurityguard.view;

import android.app.Dialog;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.ListView;

import cn.nubia.mobilesecurityguard.R;

/**
 * 自定义dialog
 * 1、给dialog设置布局
 * 2、需要清除标题栏
 * 3、
 */
public class AddressDialog extends Dialog {

    private final ListView lvAddress;

    public AddressDialog(Context context) {
        super(context,R.style.AddressDialogStyle);
        setContentView(R.layout.address_dialog);

        //修改dialog所在窗口window的位置
        Window window = getWindow();
        WindowManager.LayoutParams params = window.getAttributes();
        params.gravity = Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL;
        window.setAttributes(params);

        lvAddress = findViewById(R.id.lv_address);
    }

    public void setAdapter(BaseAdapter adapter) {
        lvAddress.setAdapter(adapter);
    }

    public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {
        lvAddress.setOnItemClickListener(listener);
    }
}

2、
    private void showAddressStyleDialog() {
        //展示弹窗
        AddressDialog dialog = new AddressDialog(SettingActivity.this);
        //给dialog设置布局
        dialog.setAdapter(new AddressStyleAdapter());
        dialog.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                //1、记录当前被选中的条目的位置
                PrefUtils.putInt(getApplicationContext(),GlobalConstants.SP_FILE, GlobalConstants.PREF_ADDRESS_STYLE, position);
                //2、取消弹窗
                dialog.dismiss();
            }
        });
        dialog.show();
    }

    private int[] mIcons = new int[]{
            R.drawable.shape_address_normal,
            R.drawable.shape_address_normal,
            R.drawable.shape_address_normal,
            R.drawable.shape_address_normal,
            R.drawable.shape_address_normal,
            R.drawable.shape_address_normal
    };

    private String[] titles = new String[]{
            "半透明1",
            "半透明2",
            "半透明3",
            "半透明4",
            "半透明5",
            "半透明6"
    };

    class AddressStyleAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return titles.length;
        }

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = View.inflate(SettingActivity.this, R.layout.item_address, null);
            ImageView ivIcon = view.findViewById(R.id.iv_icon);
            ImageView ivSelect = view.findViewById(R.id.iv_select);
            TextView tvTitle = view.findViewById(R.id.tv_title);
            ivIcon.setImageResource(mIcons[position]);
            tvTitle.setText(titles[position]);
            int pos = PrefUtils.getInt(getApplicationContext(),GlobalConstants.SP_FILE, GlobalConstants.PREF_ADDRESS_STYLE, 0);
            if (position == pos) {
                ivSelect.setVisibility(View.VISIBLE);
            } else {
                ivSelect.setVisibility(View.GONE);
            }
            return view;
        }
    }

**MSG 06-14 点击条目、记录选中位置 **

代码如上

**MSG 06-15 根据记录样式修改窗口布局背景 **

    public void showToast(String text) {
        textView.setText(text);
        int pos = PrefUtils.getInt(mContext, GlobalConstants.SP_FILE, GlobalConstants.PREF_ADDRESS_STYLE, 0);
        textView.setBackgroundResource(mIcons[pos]);
        mWM.addView(textView, params);
    }

**MSG 06-16 设置弹窗进入退出动画 **

1、在style.xml中设置样式
    <!--归属地弹窗样式-->
    <style name="AddressDialogStyle" parent="Theme.AppCompat.Dialog">
        <item name="windowNoTitle">true</item>
        <item name="android:windowBackground">@null</item>
        <item name="android:windowAnimationStyle">@style/AddressAnimStyle</item>
    </style>

    <!--归属地动画-->
    <style name="AddressAnimStyle" parent="android:Animation">
        <item name="android:windowEnterAnimation">@anim/address_dialog_enter</item>
        <item name="android:windowExitAnimation">@anim/address_dialog_exit</item>
    </style>
    
2、设置动画address_dialog_enter.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<alpha
    android:duration="150"
    android:fromAlpha="0.0"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:toAlpha="1.0" />

<translate
    android:duration="150"
    android:fromYDelta="70%"
    android:toYDelta="0" />
</set>

3、exit反之

4、自定义dialog
package cn.nubia.mobilesecurityguard.view;

import android.app.Dialog;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;

import cn.nubia.mobilesecurityguard.R;

/**
 * 自定义dialog
 * 1、给dialog设置布局
 * 2、需要清除标题栏
 * 3、
 */
public class AddressDialog extends Dialog {

    private final ListView lvAddress;

    public AddressDialog(Context context) {
        super(context,R.style.AddressDialogStyle);
        setContentView(R.layout.address_dialog);

        //修改dialog所在窗口window的位置
        Window window = getWindow();
        WindowManager.LayoutParams params = window.getAttributes();
        params.gravity = Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL;
        window.setAttributes(params);

        lvAddress = findViewById(R.id.lv_address);
    }

    public void setAdapter(BaseAdapter adapter) {
        lvAddress.setAdapter(adapter);
    }

    public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {
        lvAddress.setOnItemClickListener(listener);
    }
}

**MSG 07-02 黑名单数据库封装 **

1、创建数据库帮助类
package cn.nubia.mobilesecurityguard.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class BlackNumberOpenHelper extends SQLiteOpenHelper {

    public BlackNumberOpenHelper(Context context) {
        super(context, "blacknumber.db",null,1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建表
        String sql = "create table blacknumber(_id integer primary key autoincrement," +
                " number varchar(30),mode integer)";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}
2、创建增删改查dao
package cn.nubia.mobilesecurityguard.db.dao;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.bean.BlackNumberInfo;
import cn.nubia.mobilesecurityguard.db.BlackNumberOpenHelper;

/**
 * 黑名单
 */
public class BlackNumberDao {

    private final BlackNumberOpenHelper helper;

    public BlackNumberDao(Context context) {
        helper = new BlackNumberOpenHelper(context);
    }

    public boolean insert(String number, int mode) {
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("number", number);
        values.put("mode", mode);
        //返回插入记录的id,-1表示失败
        long insert = db.insert("blacknumber", null, values);
        db.close();
        return insert != -1;
    }

    public boolean delete(String number) {
        SQLiteDatabase db = helper.getWritableDatabase();
        //影响的行数,0表示未删除
        int delete = db.delete("blacknumber", "number=?", new String[]{number});
        db.close();
        return delete > 0;
    }

    public boolean update(String number, int mode) {
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        //values.put("number", number);
        values.put("mode", mode);
        //影响的行数
        int update = db.update("blacknumber", values, "number=?", new String[]{number});
        db.close();
        return update > 0;
    }

    //查询是否在数据库
    public boolean query(String number) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("blacknumber", new String[]{"number", "mode"}, "number=?", new String[]{number}, null, null, null);
        boolean isExit = false;
        if (cursor != null) {
            if (cursor.moveToNext()) {
                isExit = true;
            }
            cursor.close();
        }
        db.close();
        return isExit;
    }

    //查询某个号码的拦截模式
    public int queryMode(String number) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("blacknumber", new String[]{"mode"}, "number=?", new String[]{number}, null, null, null);
        int mode = -1;
        if (cursor != null) {
            if (cursor.moveToNext()) {
                mode = cursor.getInt(0);
            }
            cursor.close();
        }
        db.close();
        return mode;
    }

    //查询所有号码的集合
    public ArrayList<BlackNumberInfo> queryAllNumberAndMode() {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("blacknumber", new String[]{"number","mode"}, null, null, null, null, null);
        ArrayList<BlackNumberInfo> list =  new ArrayList<>();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                BlackNumberInfo info = new BlackNumberInfo();
                String number = cursor.getString(0);
                int mode = cursor.getInt(1);
                info.number = number;
                info.mode = mode;
                list.add(info);
            }
            cursor.close();
        }
        db.close();
        return list;
    }
}
3、封装数据对象
package cn.nubia.mobilesecurityguard.bean;

public class BlackNumberInfo {
    public String number;
    public int mode;
}

**MSG 07-03 单例设计模式 **

将数据库dao改造成单例模式
package cn.nubia.mobilesecurityguard.db.dao;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.bean.BlackNumberInfo;
import cn.nubia.mobilesecurityguard.db.BlackNumberOpenHelper;

/**
 * 黑名单-单例设计模式
 *
 * 两种方式进行初始化:
 * 1、懒汉式:线程安全问题 1.给方法加同步锁 synchronized 2.创建对象的代码块加同步锁
 *    //2、公开方法,返回单例对象
 *     public static BlackNumberDao getInstance(Context context) {
 *         if (mInstance == null) {
 *             synchronized (BlackNumberDao.class) {
 *                 if (mInstance == null) {
 *                     mInstance =  new BlackNumberDao(context);
 *                 }
 *             }
 *         }
 *         return mInstance;
 *     }
 * 2、饿汉式:
 * private static BlackNumberDao mInstance = new BlackNumberDao();
 *
 */
public class BlackNumberDao {

    private final BlackNumberOpenHelper helper;

    //3、声明一个静态对象
    private static BlackNumberDao mInstance;

    //1、构造方法私有
    private BlackNumberDao(Context context) {
        helper = new BlackNumberOpenHelper(context);
    }

    //2、公开方法,返回单例对象
    public static BlackNumberDao getInstance(Context context) {
        if (mInstance == null) {
            synchronized (BlackNumberDao.class) {
                if (mInstance == null) {
                    mInstance =  new BlackNumberDao(context);
                }
            }
        }
        return mInstance;
    }

    public boolean insert(String number, int mode) {
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("number", number);
        values.put("mode", mode);
        //返回插入记录的id,-1表示失败
        long insert = db.insert("blacknumber", null, values);
        db.close();
        return insert != -1;
    }

    public boolean delete(String number) {
        SQLiteDatabase db = helper.getWritableDatabase();
        //影响的行数,0表示未删除
        int delete = db.delete("blacknumber", "number=?", new String[]{number});
        db.close();
        return delete > 0;
    }

    public boolean update(String number, int mode) {
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        //values.put("number", number);
        values.put("mode", mode);
        //影响的行数
        int update = db.update("blacknumber", values, "number=?", new String[]{number});
        db.close();
        return update > 0;
    }

    //查询是否在数据库
    public boolean query(String number) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("blacknumber", new String[]{"number", "mode"}, "number=?", new String[]{number}, null, null, null);
        boolean isExit = false;
        if (cursor != null) {
            if (cursor.moveToNext()) {
                isExit = true;
            }
            cursor.close();
        }
        db.close();
        return isExit;
    }

    //查询某个号码的拦截模式
    public int queryMode(String number) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("blacknumber", new String[]{"mode"}, "number=?", new String[]{number}, null, null, null);
        int mode = -1;
        if (cursor != null) {
            if (cursor.moveToNext()) {
                mode = cursor.getInt(0);
            }
            cursor.close();
        }
        db.close();
        return mode;
    }

    //查询所有号码的集合
    public ArrayList<BlackNumberInfo> queryAllNumberAndMode() {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("blacknumber", new String[]{"number","mode"}, null, null, null, null, null);
        ArrayList<BlackNumberInfo> list =  new ArrayList<>();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                BlackNumberInfo info = new BlackNumberInfo();
                String number = cursor.getString(0);
                int mode = cursor.getInt(1);
                info.number = number;
                info.mode = mode;
                list.add(info);
            }
            cursor.close();
        }
        db.close();
        return list;
    }
}

MSG 07-04 单元测试

package cn.nubia.mobilesecurityguard;

import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import cn.nubia.mobilesecurityguard.db.dao.BlackNumberDao;

import static android.support.test.InstrumentationRegistry.getContext;

/**
 * 黑名单测试:
 *
 */
@RunWith(AndroidJUnit4.class)
public class BlackNumerTest{

    @Test
    public void testAdd() {
        BlackNumberDao dao = BlackNumberDao.getInstance(getContext());
        dao.insert("110",0);
    }
}

MSG 07-05 给黑名单列表填充数据

package cn.nubia.mobilesecurityguard.activity;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;

import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.adapter.BlackNumberAdapter;
import cn.nubia.mobilesecurityguard.bean.BlackNumberInfo;
import cn.nubia.mobilesecurityguard.db.dao.BlackNumberDao;

public class BlackNumberActivity extends AppCompatActivity {

    private ListView lvAddNumber;
    private BlackNumberDao dao;
    private ArrayList<BlackNumberInfo> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_black_number);

        initView();
        initData();

    }

    /**
     * 数据库查询放在子线程
     */
    private void initData() {
        dao = BlackNumberDao.getInstance(this);
        new Thread() {
            @Override
            public void run() {
                list = dao.queryAllNumberAndMode();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        BlackNumberAdapter blackNumberAdapter = new BlackNumberAdapter(BlackNumberActivity.this, list);
                        lvAddNumber.setAdapter(blackNumberAdapter);
                    }
                });
            }
        }.start();
    }

    private void initView() {
        lvAddNumber = findViewById(R.id.lv_add_number);
    }
}

MSG 07-06 Gradle刷新问题解决
MSG 07-07 ListView优化

package cn.nubia.mobilesecurityguard.adapter;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.bean.BlackNumberInfo;

public class BlackNumberAdapter extends BaseAdapter {

    private ArrayList list;
    private Context context;


    public BlackNumberAdapter(Context context, ArrayList list) {
        this.list = list;
        this.context = context;
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public BlackNumberInfo getItem(int position) {
        return (BlackNumberInfo) list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = View.inflate(context, R.layout.item_black_number, null);
            holder = new ViewHolder();
            holder.tvNumber = convertView.findViewById(R.id.tv_number);
            holder.tvMode = convertView.findViewById(R.id.tv_mode);
            holder.ivDelete = convertView.findViewById(R.id.iv_delete);
            //将holder保存和当前布局绑定
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        BlackNumberInfo info = getItem(position);
        holder.tvNumber.setText(info.number);
        switch (info.mode) {
            case 0:
                holder.tvMode.setText("拦截电话");
                break;
            case 1:
                holder.tvMode.setText("拦截短信");
                break;
            case 2:
                holder.tvMode.setText("拦截全部");
                break;
        }
        return convertView;
    }

    public static class ViewHolder {
        public TextView tvNumber;
        public TextView tvMode;
        public ImageView ivDelete;
    }
}

MSG 07-08 加载中布局展示

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@+id/lv_add_number"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"></ListView>

        <LinearLayout
            android:id="@+id/ll_loading"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:orientation="vertical">

            <ProgressBar
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:text="正在加载中..." />
        </LinearLayout>
    </FrameLayout>

使用:
        llLoading.setVisibility(View.VISIBLE);
        isFristUseDb = true;
        dao = BlackNumberDao.getInstance(this);
        new Thread() {
            @Override
            public void run() {
                if (isFristUseDb) {
                    for (int i = 0; i < 1000; i++) {
                        dao.insert("1" + i, 1);
                    }
                    isFristUseDb = false;
                }
                list = dao.queryAllNumberAndMode();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        BlackNumberAdapter blackNumberAdapter = new BlackNumberAdapter(BlackNumberActivity.this, list);
                        lvAddNumber.setAdapter(blackNumberAdapter);
                        llLoading.setVisibility(View.GONE);
                    }
                });
            }
        }.start();
耗时:
        SystemClock.sleep(2000);

MSG 07-09 自定义旋转进度条

            <ProgressBar
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:indeterminateDrawable="@drawable/custom_loading" />

custom_loading.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/loading"
    android:fromDegrees="0"
    android:toDegrees="360">
</rotate>

MSG 07-10 include标签的使用

        <include layout="@layout/pb_loading" />
        单独抽取
        <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/ll_loading"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:orientation="vertical"
    tools:showIn="@layout/activity_black_number">

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminateDrawable="@drawable/custom_loading" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:text="正在加载中..." />
</LinearLayout>

MSG 07-11 数据库分页查询sql语句

    //分页查询
    public ArrayList<BlackNumberInfo> queryPartNumberAndMode(int index) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select number, mode from blacknumber limit ?,20", new String[]{index + ""});
        ArrayList<BlackNumberInfo> list = new ArrayList<>();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                BlackNumberInfo info = new BlackNumberInfo();
                String number = cursor.getString(0);
                int mode = cursor.getInt(1);
                info.number = number;
                info.mode = mode;
                list.add(info);
            }
            cursor.close();
        }
        db.close();
        return list;
    

MSG 07-12 判读ListView是否滑动到底

        lvAddNumber.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                //滑动状态发生变化
                if (scrollState == SCROLL_STATE_IDLE) {
                    //当前显示最后一个条目的位置
                    int position = lvAddNumber.getLastVisiblePosition();
                    //当前显示的最后一条的位置等于集合总数的-1,就是滑动到底了
                    if (position == list.size() - 1) {
                        Log.e("sunyang","滑动到底了,加载下一页数据");
                        initData();
                    }
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });

MSG 07-13 加载下一页数据

    private void initData() {
        llLoading.setVisibility(View.VISIBLE);
        isFristUseDb = true;
        SystemClock.sleep(2000);
        dao = BlackNumberDao.getInstance(this);
        new Thread() {
            @Override
            public void run() {
                if (isFristUseDb) {
                    for (int i = 0; i < 100; i++) {
                        dao.insert("1" + i, 1);
                    }
                    isFristUseDb = false;
                }
                注意:在第一次加载时集合为空,会出现空指针
                list = dao.queryPartNumberAndMode(list.size());
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        BlackNumberAdapter blackNumberAdapter = new BlackNumberAdapter(BlackNumberActivity.this, list);
                        lvAddNumber.setAdapter(blackNumberAdapter);
                        llLoading.setVisibility(View.GONE);
                    }
                });
            }
        }.start();
    }
备注:在第一次加载时集合为空,会出现空指针,在开始时
    private ArrayList<BlackNumberInfo> list = new ArrayList<>();
集合追加:
                moreList = dao.queryPartNumberAndMode(list.size());
                list.addAll(moreList);
不跳页面,加载数据
    private void initData() {
        llLoading.setVisibility(View.VISIBLE);
        isFristUseDb = true;
        SystemClock.sleep(2000);
        dao = BlackNumberDao.getInstance(this);
        new Thread() {
            @Override
            public void run() {
                if (isFristUseDb) {
                    for (int i = 0; i < 100; i++) {
                        dao.insert("1" + i, 1);
                    }
                    isFristUseDb = false;
                }
                moreList = dao.queryPartNumberAndMode(list.size());
                list.addAll(moreList);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if(blackNumberAdapter == null) {
                            blackNumberAdapter = new BlackNumberAdapter(BlackNumberActivity.this, list);
                            lvAddNumber.setAdapter(blackNumberAdapter);
                        } else {
                            //如果是从第二页开始,就进行数据刷新,不会跳页面
                            blackNumberAdapter.notifyDataSetChanged();
                        }
                        llLoading.setVisibility(View.GONE);
                    }
                });
            }
        }.start();
    }

MSG 07-14 判读是否到达最后 一页

    private void initView() {
        lvAddNumber = findViewById(R.id.lv_add_number);
        llLoading = findViewById(R.id.ll_loading);
        lvAddNumber.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                //滑动状态发生变化
                if (scrollState == SCROLL_STATE_IDLE) {
                    //当前显示最后一个条目的位置
                    int position = lvAddNumber.getLastVisiblePosition();
                    //当前显示的最后一条的位置等于集合总数的-1,就是滑动到底了
                    if (position == list.size() - 1) {
                        Log.e("sunyang","滑动到底了,加载下一页数据");
                        //判断是否还有数据需要加载
                        if (list.size() < dao.getCount()) {
                            initData();
                        }
                    } else {
                        Toast.makeText(BlackNumberActivity.this, "没有数据了", Toast.LENGTH_SHORT).show();
                    }
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });
    }

MSG 07-15 判断加载完成再加载下页数据
变量控制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值