android_16_布局笔记

本文介绍了Android中的常用布局、日志系统、文件读写操作、SQLite数据库管理等内容,并讲解了ListView、Fragment等UI组件的使用方法及动画效果的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#常见布局
###线性布局
* 有一个布局方向,水平或者竖直
* 在竖直布局下,左对齐、右对齐,水平居中生效
* 在水平布局下,顶部对齐、底部对齐、竖直居中生效
* 权重:按比例分配屏幕的剩余宽度或者高度

###相对布局
* 组件默认位置都是左上角,组件之间可以重叠
* 可以相对于父元素上下左右对齐,相对于父元素,水平居中、竖直居中、水平竖直同时居中
* 可以相对于其他组件上下左右对齐
* 可以布局于其他组件的上方、下方、左边、右边

###帧布局
* 组件默认位置都是左上角,组件之间可以重叠
* 可以设置上下左右对齐,水平竖直居中,设置方式与线性布局一样

###表格布局
* 每有一个TableRow子节点表示一行,该子节点的每一个子节点都表示一列
* TableLayout的一级子节点默认宽都是匹配父元素
* TableRow的子节点默认宽高都是包裹内容

---
#Logcat
###等级
* verbose:冗余,最低等级
* debug:调试
* info:正常等级的信息
* warn:警告
* error:错误

---
#Android的存储
###内部存储空间
* RAM内存:运行内存,相当于电脑的内存
* ROM内存:存储内存,相当于电脑的硬盘
###外部存储空间
* SD卡:相当于电脑的移动硬盘
	* 2.2之前,sd卡路径:sdcard
	* 4.3之前,sd卡路径:mnt/sdcard
	* 4.3开始,sd卡路径:storage/sdcard

* 所有存储设备,都会被划分成若干个区块,每个区块有固定的大小
* 存储设备的总大小 = 区块大小 * 区块数量

---
#文件访问权限
* 指的是谁能访问这个文件
* 在Android中,每一个应用,都是一个独立的用户
* 使用10个字母表示
* drwxrwxrwx
* 第一个字母:
	* d:表示文件夹
	* -:表示文件
* 第一组rwx:表示的是文件拥有者(owner)对文件的权限
	* r:read,读
	* w:write
	* x:execute
* 第二组rwx:表示的是跟文件拥有者属于同一用户组的用户(grouper)对文件的权限
* 第三组rwx:表示的其他用户(other)对文件的权限

---
#SharedPreference
* 非常适合用来保存零散的简单的数据

 

 

 

 

 

 

 

#常见布局
###相对布局
#####RelativeLayout
* 组件默认左对齐、顶部对齐
* 设置组件在指定组件的右边

		 android:layout_toRightOf="@id/tv1"
* 设置在指定组件的下边

		android:layout_below="@id/tv1"
* 设置右对齐父元素

		android:layout_alignParentRight="true"
* 设置与指定组件右对齐

		 android:layout_alignRight="@id/tv1"

###线性布局
#####LinearLayout
* 指定各个节点的排列方向

		android:orientation="horizontal"
* 设置右对齐

		android:layout_gravity="right"
* 当竖直布局时,只能左右对齐和水平居中,顶部底部对齐竖直居中无效
* 当水平布局时,只能顶部底部对齐和竖直居中
* 使用match_parent时注意不要把其他组件顶出去
* 线性布局非常重要的一个属性:权重

		android:layout_weight="1"
* 权重设置的是按比例分配剩余的空间

###帧布局
#####FrameLayout
* 默认组件都是左对齐和顶部对齐,每个组件相当于一个div
* 可以更改对齐方式

		android:layout_gravity="bottom"
* 不能相对于其他组件布局

###表格布局
#####TableLayout
* 每个<TableRow/>节点是一行,它的每个子节点是一列
* 表格布局中的节点可以不设置宽高,因为设置了也无效
	* 根节点<TableLayout/>的子节点宽为匹配父元素,高为包裹内容
	* <TableRow/>节点的子节点宽为包裹内容,高为包裹内容
	* 以上默认属性无法修改

* 根节点中可以设置以下属性,表示让第1列拉伸填满屏幕宽度的剩余空间

		android:stretchColumns="1"

###绝对布局
#####AbsoluteLayout
* 直接指定组件的x、y坐标

		android:layout_x="144dp"
        android:layout_y="154dp"

---
#logcat
* 日志信息总共分为5个等级
	* verbose
	* debug
	* info
	* warn
	* error
* 定义过滤器方便查看
* System.out.print输出的日志级别是info,tag是System.out
* Android提供的日志输出api
	
		Log.v(TAG, "加油吧,童鞋们");
		Log.d(TAG, "加油吧,童鞋们");
		Log.i(TAG, "加油吧,童鞋们");
		Log.w(TAG, "加油吧,童鞋们");
		Log.e(TAG, "加油吧,童鞋们");

----
#文件读写操作
* Ram内存:运行内存,相当于电脑的内存
* Rom内存:内部存储空间,相当于电脑的硬盘
* sd卡:外部存储空间,相当于电脑的移动硬盘
###在内部存储空间中读写文件
>小案例:用户输入账号密码,勾选“记住账号密码”,点击登录按钮,登录的同时持久化保存账号和密码
#####1. 定义布局

#####2. 完成按钮的点击事件
* 弹土司提示用户登录成功

		Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();

#####3. 拿到用户输入的数据
* 判断用户是否勾选保存账号密码

		CheckBox cb = (CheckBox) findViewById(R.id.cb);
		if(cb.isChecked()){
			
		}

#####4. 开启io流把文件写入内部存储
* 直接开启文件输出流写数据

		//持久化保存数据
			File file = new File("data/data/com.sg31.rwinrom/info.txt");
			FileOutputStream fos = new FileOutputStream(file);
			fos.write((name + "##" + pass).getBytes());
			fos.close();
* 读取数据前先检测文件是否存在

		if(file.exists())
* 读取保存的数据,也是直接开文件输入流读取

		File file = new File("data/data/com.sg31.rwinrom/info.txt");
		FileInputStream fis = new FileInputStream(file);
		//把字节流转换成字符流
		BufferedReader br = new BufferedReader(new InputStreamReader(fis));
		String text = br.readLine();
		String[] s = text.split("##");
* 读取到数据之后,回显至输入框

		et_name.setText(s[0]);
		et_pass.setText(s[1]);
* 应用只能在自己的包名目录下创建文件,不能到别人家去创建

###直接复制项目
* 需要改动的地方:
	* 项目名字
	* 应用包名
	* R文件重新导包

###使用路径api读写文件
* getFilesDir()得到的file对象的路径是data/data/com.sg31.rwinrom2/files
	* 存放在这个路径下的文件,只要你不删,它就一直在
* getCacheDir()得到的file对象的路径是data/data/com.sg31.rwinrom2/cache
	* 存放在这个路径下的文件,当内存不足时,有可能被删除

* 系统管理应用界面的清除缓存,会清除cache文件夹下的东西,清除数据,会清除整个包名目录下的东西

-----
#在外部存储读写数据
###sd卡的路径
* sdcard:2.3之前的sd卡路径
* mnt/sdcard:4.3之前的sd卡路径
* storage/sdcard:4.3之后的sd卡路径

* 最简单的打开sd卡的方式
		
		File file = new File("sdcard/info.txt");

* 写sd卡需要权限

		<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
* 读sd卡,在4.0之前不需要权限,4.0之后可以设置为需要

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

* 使用api获得sd卡的真实路径,部分手机品牌会更改sd卡的路径

		Environment.getExternalStorageDirectory()
* 判断sd卡是否准备就绪

		if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))

-----
#查看源代码查找获取sd卡剩余容量的代码
* 导入Settings项目
* 查找“可用空间”得到

		 <string name="memory_available" msgid="418542433817289474">"可用空间"</string>

* 查找"memory_available",得到

		<Preference android:key="memory_sd_avail" 
            style="?android:attr/preferenceInformationStyle" 
            android:title="@string/memory_available"
            android:summary="00"/>

* 查找"memory_sd_avail",得到

		//这个字符串就是sd卡剩余容量
		formatSize(availableBlocks * blockSize) + readOnly
		//这两个参数相乘,得到sd卡以字节为单位的剩余容量
		availableBlocks * blockSize

* 存储设备会被分为若干个区块,每个区块有固定的大小
* 区块大小 * 区块数量 等于 存储设备的总大小

-------
#Linux文件的访问权限
* 在Android中,每一个应用是一个独立的用户
* drwxrwxrwx
* 第1位:d表示文件夹,-表示文件
* 第2-4位:rwx,表示这个文件的拥有者用户(owner)对该文件的权限
	* r:读
	* w:写
	* x:执行
* 第5-7位:rwx,表示跟文件拥有者用户同组的用户(grouper)对该文件的权限
* 第8-10位:rwx,表示其他用户组的用户(other)对该文件的权限

----
#openFileOutput的四种模式
* MODE_PRIVATE:-rw-rw----
* MODE_APPEND:-rw-rw----
* MODE_WORLD_WRITEABLE:-rw-rw--w-
* MODE_WORLD_READABLE:-rw-rw-r--

----
#SharedPreference
>用SharedPreference存储账号密码

* 往SharedPreference里写数据

		//拿到一个SharedPreference对象
		SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
		//拿到编辑器
		Editor ed = sp.edit();
		//写数据
		ed.putBoolean("name", name);
		ed.commit();

* 从SharedPreference里取数据

		SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
		//从SharedPreference里取数据
		String name = sp.getBoolean("name", "");

---
#生成XML文件备份短信

* 创建几个虚拟的短信对象,存在list中
* 备份数据通常都是备份至sd卡
###使用StringBuffer拼接字符串
* 把整个xml文件所有节点append到sb对象里

		sb.append("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>");
		//添加smss的开始节点
		sb.append("<smss>");
		.......
* 把sb写到输出流中

		fos.write(sb.toString().getBytes());

###使用XMl序列化器生成xml文件
* 得到xml序列化器对象

		XmlSerializer xs = Xml.newSerializer();
* 给序列化器设置输出流

		File file = new File(Environment.getExternalStorageDirectory(), "backupsms.xml");
		FileOutputStream fos = new FileOutputStream(file);
		//给序列化器指定好输出流
		xs.setOutput(fos, "utf-8");
* 开始生成xml文件

		xs.startDocument("utf-8", true);
		xs.startTag(null, "smss");
		......

---
#pull解析xml文件
* 先自己写一个xml文件,存一些天气信息
###拿到xml文件
		
		InputStream is = getClassLoader().getResourceAsStream("weather.xml");
###拿到pull解析器

		XmlPullParser xp = Xml.newPullParser();

###开始解析
* 拿到指针所在当前节点的事件类型

		int type = xp.getEventType();
* 事件类型主要有五种
	* START_DOCUMENT:xml头的事件类型
	* END_DOCUMENT:xml尾的事件类型
	* START_TAG:开始节点的事件类型
	* END_TAG:结束节点的事件类型
	* TEXT:文本节点的事件类型

* 如果获取到的事件类型不是END_DOCUMENT,就说明解析还没有完成,如果是,解析完成,while循环结束

		while(type != XmlPullParser.END_DOCUMENT)
* 当我们解析到不同节点时,需要进行不同的操作,所以判断一下当前节点的name
	* 当解析到weather的开始节点时,new出list
	* 当解析到city的开始节点时,创建city对象,创建对象是为了更方便的保存即将解析到的文本
	* 当解析到name开始节点时,获取下一个节点的文本内容,temp、pm也是一样

			case XmlPullParser.START_TAG:
			//获取当前节点的名字
				if("weather".equals(xp.getName())){
					citys = new ArrayList<City>();
				}
				else if("city".equals(xp.getName())){
					city = new City();
				}
				else if("name".equals(xp.getName())){
					//获取当前节点的下一个节点的文本
					String name = xp.nextText();
					city.setName(name);
				}
				else if("temp".equals(xp.getName())){
					String temp = xp.nextText();
					city.setTemp(temp);
				}
				else if("pm".equals(xp.getName())){
					String pm = xp.nextText();
					city.setPm(pm);
				}
				break;

* 当解析到city的结束节点时,说明city的三个子节点已经全部解析完了,把city对象添加至list

		case XmlPullParser.END_TAG:
			if("city".equals(xp.getName())){
					citys.add(city);
			}

 

 

 

 

#测试
* 黑盒测试
	* 测试逻辑业务
* 白盒测试
	* 测试逻辑方法

* 根据测试粒度
	* 方法测试:function test
	* 单元测试:unit test
	* 集成测试:integration test
	* 系统测试:system test

* 根据测试暴力程度
	* 冒烟测试:smoke test
	* 压力测试:pressure test

------
#单元测试junit
* 定义一个类继承AndroidTestCase,在类中定义方法,即可测试该方法


* 在指定指令集时,targetPackage指定你要测试的应用的包名

		<instrumentation 
	    android:name="android.test.InstrumentationTestRunner"
	    android:targetPackage="com.sg31.junit"
	    ></instrumentation>

* 定义使用的类库

		<uses-library android:name="android.test.runner"></uses-library>

* 断言的作用,检测运行结果和预期是否一致
* 如果应用出现异常,会抛给测试框架

-----
#SQLite数据库
* 轻量级关系型数据库
* 创建数据库需要使用的api:SQLiteOpenHelper
	* 必须定义一个构造方法:

			//arg1:数据库文件的名字
			//arg2:游标工厂
			//arg3:数据库版本
			public MyOpenHelper(Context context, String name, CursorFactory factory, int version){}
	* 数据库被创建时会调用:onCreate方法
	* 数据库升级时会调用:onUpgrade方法

###创建数据库

	//创建OpenHelper对象
	MyOpenHelper oh = new MyOpenHelper(getContext(), "person.db", null, 1);
	//获得数据库对象,如果数据库不存在,先创建数据库,后获得,如果存在,则直接获得
	SQLiteDatabase db = oh.getWritableDatabase();

* getWritableDatabase():打开可读写的数据库
* getReadableDatabase():在磁盘空间不足时打开只读数据库,否则打开可读写数据库
* 在创建数据库时创建表

		public void onCreate(SQLiteDatabase db) {
			// TODO Auto-generated method stub
			db.execSQL("create table person (_id integer primary key autoincrement, name char(10), phone char(20), money integer(20))");
		}

---
#数据库的增删改查
###SQL语句
* insert into person (name, phone, money) values ('张三', '159874611', 2000);
* delete from person where name = '李四' and _id = 4;
* update person set money = 6000 where name = '李四';
* select name, phone from person where name = '张三';

###执行SQL语句实现增删改查

		//插入
		db.execSQL("insert into person (name, phone, money) values (?, ?, ?);", new Object[]{"张三", 15987461, 75000});
		//查找
		Cursor cs = db.rawQuery("select _id, name, money from person where name = ?;", new String[]{"张三"});
* 测试方法执行前会调用此方法

		protected void setUp() throws Exception {
			super.setUp();
			//					获取虚拟上下文对象
			oh = new MyOpenHelper(getContext(), "people.db", null, 1);
		}
###使用api实现增删改查
* 插入

		//以键值对的形式保存要存入数据库的数据
		ContentValues cv = new ContentValues();
		cv.put("name", "刘能");
		cv.put("phone", 1651646);
		cv.put("money", 3500);
		//返回值是改行的主键,如果出错返回-1
		long i = db.insert("person", null, cv);
* 删除

		//返回值是删除的行数
		int i = db.delete("person", "_id = ? and name = ?", new String[]{"1", "张三"});
* 修改
	
		ContentValues cv = new ContentValues();
		cv.put("money", 25000);
		int i = db.update("person", cv, "name = ?", new String[]{"赵四"});
* 查询

		//arg1:要查询的字段
		//arg2:查询条件
		//arg3:填充查询条件的占位符
		Cursor cs = db.query("person", new String[]{"name", "money"}, "name = ?", new String[]{"张三"}, null, null, null);
		while(cs.moveToNext()){
			//							获取指定列的索引值
			String name = cs.getString(cs.getColumnIndex("name"));
			String money = cs.getString(cs.getColumnIndex("money"));
			System.out.println(name + ";" + money);
		}

###事务
* 保证多条SQL语句要么同时成功,要么同时失败
* 最常见案例:银行转账
* 事务api

		try {
			//开启事务
			db.beginTransaction();
			...........
			//设置事务执行成功
			db.setTransactionSuccessful();
		} finally{
			//关闭事务
			//如果此时已经设置事务执行成功,则sql语句生效,否则不生效
			db.endTransaction();
		}

---
#把数据库的数据显示至屏幕
1. 任意插入一些数据
*  定义业务bean:Person.java
*  读取数据库的所有数据

		Cursor cs = db.query("person", null, null, null, null, null, null);
        while(cs.moveToNext()){
        	String name = cs.getString(cs.getColumnIndex("name"));
        	String phone = cs.getString(cs.getColumnIndex("phone"));
        	String money = cs.getString(cs.getColumnIndex("money"));
        	//把读到的数据封装至Person对象
        	Person p = new Person(name, phone, money);
        	//把person对象保存至集合中
        	people.add(p);
        }

* 把集合中的数据显示至屏幕

         LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
         for(Person p : people){
			 //创建TextView,每条数据用一个文本框显示
        	 TextView tv = new TextView(this);
        	 tv.setText(p.toString());
			 //把文本框设置为ll的子节点
        	 ll.addView(tv);
         }
* 分页查询

		Cursor cs = db.query("person", null, null, null, null, null, null, "0, 10");

---
#ListView
* 就是用来显示一行一行的条目的
* MVC结构
	* M:model模型层,要显示的数据           ————people集合
	* V:view视图层,用户看到的界面          ————ListView
	* c:control控制层,操作数据如何显示     ————adapter对象
* 每一个条目都是一个View对象
###BaseAdapter
* 必须实现的两个方法

	* 第一个

			//系统调用此方法,用来获知模型层有多少条数据
			@Override
			public int getCount() {
				return people.size();
			}

	* 第二个

			//系统调用此方法,获取要显示至ListView的View对象
			//position:是return的View对象所对应的数据在集合中的位置
			@Override
			public View getView(int position, View convertView, ViewGroup parent) {
				System.out.println("getView方法调用" + position);
				TextView tv = new TextView(MainActivity.this);
				//拿到集合中的元素
				Person p = people.get(position);
				tv.setText(p.toString());
				
				//把TextView的对象返回出去,它会变成ListView的条目
				return tv;
			}
* 屏幕上能显示多少个条目,getView方法就会被调用多少次,屏幕向下滑动时,getView会继续被调用,创建更多的View对象显示至屏幕
###条目的缓存
* 当条目划出屏幕时,系统会把该条目缓存至内存,当该条目再次进入屏幕,系统在重新调用getView时会把缓存的条目作为convertView参数传入,但是传入的条目不一定是之前被缓存的该条目,即系统有可能在调用getView方法获取第一个条目时,传入任意一个条目的缓存

---
#对话框
###确定取消对话框
* 创建对话框构建器对象,类似工厂模式
* 
		AlertDialog.Builder builder = new Builder(this);
* 设置标题和正文
* 
    	builder.setTitle("警告");
    	builder.setMessage("若练此功,必先自宫");
* 设置确定和取消按钮

    	builder.setPositiveButton("现在自宫", new OnClickListener() {
			
			@Override
			public void onClick(DialogInterface dialog, int which) {
				// TODO Auto-generated method stub
				Toast.makeText(MainActivity.this, "恭喜你自宫成功,现在程序退出", 0).show();
			}
		});
    	
    	builder.setNegativeButton("下次再说", new OnClickListener() {
			
			@Override
			public void onClick(DialogInterface dialog, int which) {
				// TODO Auto-generated method stub
				Toast.makeText(MainActivity.this, "若不自宫,一定不成功", 0).show();
			}
		});
* 使用构建器创建出对话框对象

    	AlertDialog ad = builder.create();
    	ad.show();

###单选对话框

		AlertDialog.Builder builder = new Builder(this);
    	builder.setTitle("选择你的性别");
* 定义单选选项
* 
    	final String[] items = new String[]{
    			"男", "女", "其他"
    	};
		//-1表示没有默认选择
		//点击侦听的导包要注意别导错
    	builder.setSingleChoiceItems(items, -1, new OnClickListener() {
			
			//which表示点击的是哪一个选项
			@Override
			public void onClick(DialogInterface dialog, int which) {
				Toast.makeText(MainActivity.this, "您选择了" + items[which], 0).show();
				//对话框消失
				dialog.dismiss();
			}
    	});
    	
    	builder.show();

###多选对话框

		AlertDialog.Builder builder = new Builder(this);
    	builder.setTitle("请选择你认为最帅的人");
* 定义多选的选项,因为可以多选,所以需要一个boolean数组来记录哪些选项被选了
* 
    	final String[] items = new String[]{
    			"赵帅哥",
    			"赵师哥",
    			"赵老师",
    			"侃哥"
    	};
		//true表示对应位置的选项被选了
    	final boolean[] checkedItems = new boolean[]{
    			true,
    			false,
    			false,
    			false,
    	};
    	builder.setMultiChoiceItems(items, checkedItems, new OnMultiChoiceClickListener() {

			//点击某个选项,如果该选项之前没被选择,那么此时isChecked的值为true
			@Override
			public void onClick(DialogInterface dialog, int which, boolean isChecked) {
				checkedItems[which] = isChecked;
			}
		});
    	
    	builder.setPositiveButton("确定", new OnClickListener() {
			
			@Override
			public void onClick(DialogInterface dialog, int which) {
				StringBuffer sb = new StringBuffer();
				for(int i = 0;i < items.length; i++){
					sb.append(checkedItems[i] ? items[i] + " " : "");
				}
				Toast.makeText(MainActivity.this, sb.toString(), 0).show();
			}
		});
    	builder.show();

 

 

 

 

 

 

 

#测试
###按岗位划分
* 黑盒测试:测试逻辑业务
* 白盒测试:测试逻辑方法
###按测试粒度分
* 方法测试:function test
* 单元测试:unit test
* 集成测试:integration test
* 系统测试:system test
###按测试的暴力程度分
* 冒烟测试:smoke test
* 压力测试:pressure test

---
#单元测试
* junit
* 在清单文件中指定指令集

		<instrumentation 
        android:name="android.test.InstrumentationTestRunner"
		//指定该测试框架要测试哪一个项目
        android:targetPackage="com.sg31.junit"
        ></instrumentation>
* 定义使用的类库

		<uses-library android:name="android.test.runner"/>

---
#SQLite
###事务
> 保证所有sql语句要么一起成功,要么一起失败

---
#ListView
###MVC架构
* M:模型层 ---- javaBean ---- personList
* V:视图层 ---- jsp      ---- ListView
* C:控制层 ---- servlet  ---- Adapter
###Adapter
* ListView的每个条目都是一个View对象

 

 

 

 

 

 

#网络请求
###主线程阻塞
* UI停止刷新,应用无法响应用户操作
* 耗时操作不应该在主线程进行
* ANR
	* application not responding
	* 应用无响应异常
	* 主线程阻塞时间过长,就会抛出ANR

* 主线程又称UI线程,因为只有在主线程中,才能刷新UI

###消息队列机制
* 主线程创建时,系统会同时创建消息队列对象(MessageQueue)和消息轮询器对象(Looper)
* 轮询器的作用,就是不停的检测消息队列中是否有消息(Message)
* 消息队列一旦有消息,轮询器会把消息对象传给消息处理器(Handler),处理器会调用handleMessage方法来处理这条消息,handleMessage方法运行在主线程中,所以可以刷新ui
* 总结:只要消息队列有消息,handleMessage方法就会调用
* 子线程如果需要刷新ui,只需要往消息队列中发一条消息,触发handleMessage方法即可
* 子线程使用处理器对象的sendMessage方法发送消息

 

 

 

 

 

 

 

#网络图片查看器
* 确定图片的网址
* 发送http请求
		
    	URL url = new URL(address);
    	//获取连接对象,并没有建立连接
    	HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    	//设置连接和读取超时
    	conn.setConnectTimeout(5000);
    	conn.setReadTimeout(5000);
    	//设置请求方法,注意必须大写
    	conn.setRequestMethod("GET");
    	//建立连接,发送get请求
		//conn.connect();
    	//建立连接,然后获取响应吗,200说明请求成功
    	conn.getResponseCode();
* 服务器的图片是以流的形式返回给浏览器的 

    	//拿到服务器返回的输入流
    	InputStream is = conn.getInputStream();
    	//把流里的数据读取出来,并构造成图片
    	Bitmap bm = BitmapFactory.decodeStream(is);
* 把图片设置为ImageView的显示内容

		ImageView iv = (ImageView) findViewById(R.id.iv);
   		iv.setImageBitmap(bm);
* 添加权限

###主线程不能被阻塞
* 在Android中,主线程被阻塞会导致应用不能刷新ui界面,不能响应用户操作,用户体验将非常差
* 主线程阻塞时间过长,系统会抛出ANR异常
* ANR:Application Not Response;应用无响应
* 任何耗时操作都不可以写在主线程
* 因为网络交互属于耗时操作,如果网速很慢,代码会阻塞,所以网络交互的代码不能运行在主线程
###只有主线程能刷新ui
* 刷新ui的代码只能运行在主线程,运行在子线程是没有任何效果的
* 如果需要在子线程中刷新ui,使用消息队列机制
#####消息队列
* Looper一旦发现Message Queue中有消息,就会把消息取出,然后把消息扔给Handler对象,Handler会调用自己的handleMessage方法来处理这条消息
* handleMessage方法运行在主线程
* 主线程创建时,消息队列和轮询器对象就会被创建,但是消息处理器对象,需要使用时,自行创建

		//消息队列
		Handler handler = new Handler(){
			//主线程中有一个消息轮询器looper,不断检测消息队列中是否有新消息,如果发现有新消息,自动调用此方法,注意此方法是在主线程中运行的
			public void handleMessage(android.os.Message msg) {
		
			}
		};
* 在子线程中往消息队列里发消息

		//创建消息对象
		Message msg = new Message();
    	//消息的obj属性可以赋值任何对象,通过这个属性可以携带数据
		msg.obj = bm;
    	//what属性相当于一个标签,用于区分出不同的消息,从而运行不能的代码
		msg.what = 1;
    	//发送消息
    	handler.sendMessage(msg);
* 通过switch语句区分不同的消息

		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			//如果是1,说明属于请求成功的消息
			case 1:
				ImageView iv = (ImageView) findViewById(R.id.iv);
				Bitmap bm = (Bitmap) msg.obj;
				iv.setImageBitmap(bm);
				break;
			case 2:
				Toast.makeText(MainActivity.this, "请求失败", 0).show();
				break;
			}		
		}
###加入缓存图片的功能
* 把服务器返回的流里的数据读取出来,然后通过文件输入流写至本地文件

		//1.拿到服务器返回的输入流
	    InputStream is = conn.getInputStream();
	    //2.把流里的数据读取出来,并构造成图片
	    					
	    FileOutputStream fos = new FileOutputStream(file);
	    byte[] b = new byte[1024];
	    int len = 0;
	    while((len = is.read(b)) != -1){
	    	fos.write(b, 0, len);
	    }
* 创建bitmap对象的代码改成

		Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath());
* 每次发送请求前检测一下在缓存中是否存在同名图片,如果存在,则读取缓存

---
#获取开源代码的网站
* code.google.com
* github.com
* 在github搜索smart-image-view
* 下载开源项目smart-image-view
* 使用自定义组件时,标签名字要写包名

		<com.loopj.android.image.SmartImageView/>
* SmartImageView的使用

		SmartImageView siv = (SmartImageView) findViewById(R.id.siv);
		siv.setImageUrl("http://192.168.1.102:8080/dd.jpg");

---
#Html源文件查看器
* 发送GET请求

		URL url = new URL(path);
		//获取连接对象
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		//设置连接属性
		conn.setRequestMethod("GET");
		conn.setConnectTimeout(5000);
		conn.setReadTimeout(5000);
		//建立连接,获取响应吗
		if(conn.getResponseCode() == 200){
				
		}
* 获取服务器返回的流,从流中把html源码读取出来

		byte[] b = new byte[1024];
		int len = 0;
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		while((len = is.read(b)) != -1){
			//把读到的字节先写入字节数组输出流中存起来
			bos.write(b, 0, len);
		}
		//把字节数组输出流中的内容转换成字符串
		//默认使用utf-8
		text = new String(bos.toByteArray());
###乱码的处理
* 乱码的出现是因为服务器和客户端码表不一致导致
		
		//手动指定码表
		text = new String(bos.toByteArray(), "gb2312");

---
#提交数据
###GET方式提交数据
* get方式提交的数据是直接拼接在url的末尾

		final String path = "http://192.168.1.104/Web/servlet/CheckLogin?name=" + name + "&pass=" + pass;

* 发送get请求,代码和之前一样

		URL url = new URL(path);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestMethod("GET");
		conn.setReadTimeout(5000);
		conn.setConnectTimeout(5000);
		if(conn.getResponseCode() == 200){

		}
* 浏览器在发送请求携带数据时会对数据进行URL编码,我们写代码时也需要为中文进行URL编码

		String path = "http://192.168.1.104/Web/servlet/CheckLogin?name=" + URLEncoder.encode(name) + "&pass=" + pass;
###POST方式提交数据
* post提交数据是用流写给服务器的
* 协议头中多了两个属性
	* Content-Type: application/x-www-form-urlencoded,描述提交的数据的mimetype
	* Content-Length: 32,描述提交的数据的长度

			//给请求头添加post多出来的两个属性
			String data = "name=" + URLEncoder.encode(name) + "&pass=" + pass;
			conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			conn.setRequestProperty("Content-Length", data.length() + "");

* 设置允许打开post请求的流

		conn.setDoOutput(true);

* 获取连接对象的输出流,往流里写要提交给服务器的数据

		OutputStream os = conn.getOutputStream();
		os.write(data.getBytes());


 

 

 

 

 

#HttpClient
###发送get请求
* 创建一个客户端对象

	HttpClient client = new DefaultHttpClient();

* 创建一个get请求对象

	HttpGet hg = new HttpGet(path);

* 发送get请求,建立连接,返回响应头对象

	HttpResponse hr = hc.execute(hg);

* 获取状态行对象,获取状态码,如果为200则说明请求成功

		if(hr.getStatusLine().getStatusCode() == 200){
			//拿到服务器返回的输入流
			InputStream is = hr.getEntity().getContent();
			String text = Utils.getTextFromStream(is);
		}

###发送post请求

		//创建一个客户端对象
		HttpClient client = new DefaultHttpClient();
		//创建一个post请求对象
		HttpPost hp = new HttpPost(path);

* 往post对象里放入要提交给服务器的数据
	
		//要提交的数据以键值对的形式存在BasicNameValuePair对象中
		List<NameValuePair> parameters = new ArrayList<NameValuePair>();
		BasicNameValuePair bnvp = new BasicNameValuePair("name", name);
		BasicNameValuePair bnvp2 = new BasicNameValuePair("pass", pass);
		parameters.add(bnvp);
		parameters.add(bnvp2);
		//创建实体对象,指定进行URL编码的码表
		UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "utf-8");
		//为post请求设置实体
		hp.setEntity(entity);

---
#异步HttpClient框架
###发送get请求
	
		//创建异步的httpclient对象
		AsyncHttpClient ahc = new AsyncHttpClient();
		//发送get请求
		ahc.get(path, new MyHandler());
* 注意AsyncHttpResponseHandler两个方法的调用时机

		class MyHandler extends AsyncHttpResponseHandler{

			//http请求成功,返回码为200,系统回调此方法
			@Override
			public void onSuccess(int statusCode, Header[] headers,
					//responseBody的内容就是服务器返回的数据
					byte[] responseBody) {
				Toast.makeText(MainActivity.this, new String(responseBody), 0).show();
				
			}
	
			//http请求失败,返回码不为200,系统回调此方法
			@Override
			public void onFailure(int statusCode, Header[] headers,
					byte[] responseBody, Throwable error) {
				Toast.makeText(MainActivity.this, "返回码不为200", 0).show();
				
			}
		
		}
###发送post请求

* 使用RequestParams对象封装要携带的数据
 
		//创建异步httpclient对象
		AsyncHttpClient ahc = new AsyncHttpClient();
		//创建RequestParams封装要携带的数据
		RequestParams rp = new RequestParams();
		rp.add("name", name);
		rp.add("pass", pass);
		//发送post请求
		ahc.post(path, rp, new MyHandler());

---
#多线程下载
>原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源

###确定每条线程下载多少数据
* 发送http请求至下载地址

		String path = "http://192.168.1.102:8080/editplus.exe";		
		URL url = new URL(path);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setReadTimeout(5000);
		conn.setConnectTimeout(5000);
		conn.setRequestMethod("GET");					
* 获取文件总长度,然后创建长度一致的临时文件

		if(conn.getResponseCode() == 200){
			//获得服务器流中数据的长度
			int length = conn.getContentLength();
			//创建一个临时文件存储下载的数据
			RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
			//设置临时文件的大小
			raf.setLength(length);
			raf.close();
* 确定线程下载多少数据

			//计算每个线程下载多少数据
			int blockSize = length / THREAD_COUNT;
###计算每条线程下载数据的开始位置和结束位置

		for(int id = 1; id <= 3; id++){
			//计算每个线程下载数据的开始位置和结束位置
			int startIndex = (id - 1) * blockSize;
			int endIndex = id * blockSize - 1;
			if(id == THREAD_COUNT){
				endIndex = length;
			}
							
			//开启线程,按照计算出来的开始结束位置开始下载数据
			new DownLoadThread(startIndex, endIndex, id).start();
		}
###再次发送请求至下载地址,请求开始位置至结束位置的数据

		String path = "http://192.168.1.102:8080/editplus.exe";
	
		URL url = new URL(path);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setReadTimeout(5000);
		conn.setConnectTimeout(5000);
		conn.setRequestMethod("GET");
		
		//向服务器请求部分数据
		conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
		conn.connect();
* 下载请求到的数据,存放至临时文件中

		if(conn.getResponseCode() == 206){
			InputStream is = conn.getInputStream();
			RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
			//指定从哪个位置开始存放数据
			raf.seek(startIndex);
			byte[] b = new byte[1024];
			int len;
			while((len = is.read(b)) != -1){
				raf.write(b, 0, len);
			}
			raf.close();
		}

---
#带断点续传的多线程下载
* 定义一个int变量记录每条线程下载的数据总长度,然后加上该线程的下载开始位置,得到的结果就是下次下载时,该线程的开始位置,把得到的结果存入缓存文件

		//用来记录当前线程总的下载长度
		int total = 0;
		while((len = is.read(b)) != -1){
			raf.write(b, 0, len);
			total += len;
			//每次下载都把新的下载位置写入缓存文本文件
			RandomAccessFile raf2 = new RandomAccessFile(threadId + ".txt", "rwd");
			raf2.write((startIndex + total + "").getBytes());
			raf2.close();
		}
* 下次下载开始时,先读取缓存文件中的值,得到的值就是该线程新的开始位置

		FileInputStream fis = new FileInputStream(file);
		BufferedReader br = new BufferedReader(new InputStreamReader(fis));
		String text = br.readLine();
		int newStartIndex = Integer.parseInt(text);
		//把读到的值作为新的开始位置
		startIndex = newStartIndex;
		fis.close();
* 三条线程都下载完毕之后,删除缓存文件

		RUNNING_THREAD--;
		if(RUNNING_THREAD == 0){
			for(int i = 0; i <= 3; i++){
				File f = new File(i + ".txt");
				f.delete();
			}
		}

---
#手机版的断点续传多线程下载器
* 把刚才的代码直接粘贴过来就能用,记得在访问文件时的路径要改成Android的目录,添加访问网络和外部存储的路径
###用进度条显示下载进度
* 拿到下载文件总长度时,设置进度条的最大值

		//设置进度条的最大值
		pb.setMax(length);
* 进度条需要显示三条线程的整体下载进度,所以三条线程每下载一次,就要把新下载的长度加入进度条
	* 定义一个int全局变量,记录三条线程的总下载长度

			int progress;
	* 刷新进度条
	 
			while((len = is.read(b)) != -1){
				raf.write(b, 0, len);

						
				//把当前线程本次下载的长度加到进度条里
				progress += len;
				pb.setProgress(progress);
* 每次断点下载时,从新的开始位置开始下载,进度条也要从新的位置开始显示,在读取缓存文件获取新的下载开始位置时,也要处理进度条进度

		FileInputStream fis = new FileInputStream(file);
		BufferedReader br = new BufferedReader(new InputStreamReader(fis));
		String text = br.readLine();
		int newStartIndex = Integer.parseInt(text);
					
		//新开始位置减去原本的开始位置,得到已经下载的数据长度
		int alreadyDownload = newStartIndex - startIndex;
		//把已经下载的长度设置入进度条
		progress += alreadyDownload;
###添加文本框显示百分比进度
	
		tv.setText(progress * 100 / pb.getMax() + "%");

---
#HttpUtils的使用
>HttpUtils本身就支持多线程断点续传,使用起来非常的方便

* 创建HttpUtils对象

		HttpUtils http = new HttpUtils();
* 下载文件
		
		http.download(url, //下载请求的网址
				target, //下载的数据保存路径和文件名
				true, //是否开启断点续传
				true, //如果服务器响应头中包含了文件名,那么下载完毕后自动重命名
				new RequestCallBack<File>() {//侦听下载状态
			
			//下载成功此方法调用
			@Override
			public void onSuccess(ResponseInfo<File> arg0) {
				tv.setText("下载成功" + arg0.result.getPath());
			}
			
			//下载失败此方法调用,比如文件已经下载、没有网络权限、文件访问不到,方法传入一个字符串参数告知失败原因
			@Override
			public void onFailure(HttpException arg0, String arg1) {
				tv.setText("下载失败" + arg1);
			}
			
			//在下载过程中不断的调用,用于刷新进度条
			@Override
			public void onLoading(long total, long current, boolean isUploading) {
				super.onLoading(total, current, isUploading);
				//设置进度条总长度
				pb.setMax((int) total);
				//设置进度条当前进度
				pb.setProgress((int) current);
				tv_progress.setText(current * 100 / total + "%");
			}
		});

 

 

 

 

#Android四大组件
* Activity
* BroadCastReceiver
* Service
* ContentProvider
#创建第二个activity
* 新创建的activity,必须在清单文件中做配置,否则系统找不到,在显示时会直接报错

		<activity android:name="com.itheima.createactivity.SecondActivity"></activity>
* 只要有以下代码,那么就是入口activity,就会生成快捷图标

		<intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

* 如果Activity所在的包跟应用包名同名,那么可以省略不写

1. 创建class类继承Activity
2. 创建布局文件,作为Activity的显示内容
3. 在清单文件中注册Activity
#Activity的跳转
###隐式跳转
* 一个Activity如果需要隐式跳转,那么在清单文件中必须添加以下子节点

		<intent-filter >
            <action android:name="com.itheima.sa"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
* action节点的name是自己定义的,定义好之后,这个name的值就会成为这个activity动作,在隐式启动Activity时,意图中设置的action必须跟"com.itheima.sa"是完全匹配的
###应用场景
* 显示意图:启动同一个应用中的Activity
* 隐式意图:启动不同应用中的Activity
* 再启动效率上,隐式远远低于显式
* 如果系统中有多个Activity与意图设置的Action匹配,那么在启动Activity时,会弹出一个对话框,里面包含所有匹配的Activity

----
#Activity生命周期
* oncreate:Activity对象创建完毕,但此时不可见
* onstart:Activity在屏幕可见,但是此时没有焦点
* onResume:Activity在屏幕可见,并且获得焦点
* onPause:Activity此时在屏幕依然可见,但是已经没有焦点
* onStop:Activity已经不可见了,但此时Activity的对象还在内存中
* onDestroy:Activity对象被销毁

###内存不足
* 内存不足时,系统会优先杀死后台Activity所在的进程,都杀光了,如果内存还是不足,那么就会杀死暂停状态的Activity所在的进程,如果还是不够,有可能杀死前台进程
* 如果有多个后台进程,在选择杀死的目标时,采用最近最少使用算法(LRU)

---
###Activity任务栈
* 应用运行过程中,内存中可能会打开多个Activity,那么所有打开的Activity都会被保存在Activity任务栈
* 栈:后进先出,最先进栈,就会最后出栈

###Activity的启动模式
* 标准模式:默认就是这个模式
* singleTop:如果目标Activity不在栈顶,那么就会启动一个新的Activity,如果已经在栈顶了,那么就不会再启动了
* singleTask:如果栈中没有该Activity,那么启动时就会创建一个该Activity,如果栈中已经有该Activity的实例存在了,那么在启动时,就会杀死在栈中处于该Activity上方的所有Activity全部杀死,那么此时该Activity就成为了栈顶Activity。
	* singleTask的作用:保证整个栈中只有一个该Activity的实例
* singleInstance:设为此模式的Activity会有一个自己独立的任务栈,该Activity的实例只会创建一个,保存在独立的任务栈中
	* singleInstance的作用:保证整个系统的内存中只有一个该Activity的实例

---
#横竖屏的切换
* Activity在横竖屏切换时会销毁重建,目的就是为了读取新的布局文件
* 写死方向,不允许切换

		android:screenOrientation="portrait"
 		android:screenOrientation="landscape"
* 配置Activity时添加以下属性,横竖屏切换时就不会销毁重建

		android:configChanges="orientation|keyboardHidden|screenSize"

---
#Activity返回时传递数据
* 请求码:用来区分数据来自于哪一个Activity
* 结果码:用来区分,返回的数据时属于什么类型

 

 

 

 

#创建第二个Activity
* 需要在清单文件中为其配置一个activity标签
* 标签中如果带有这个子节点,则会在系统中多创建一个快捷图标

		 <intent-filter>
             <action android:name="android.intent.action.MAIN" />
             <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
* 一个应用程序可以在桌面创建多个快捷图标。
* activity的名称、图标可以和应用程序的名称、图标不相同

		android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"

---
#Activity的跳转
>Activity的跳转需要创建Intent对象,通过设置intent对象的参数指定要跳转Activity
>
>通过设置Activity的包名和类名实现跳转,称为显式意图
>
>通过指定动作实现跳转,称为隐式意图
###显式意图
* 跳转至同一项目下的另一个Activity,直接指定该Activity的字节码即可

		Intent intent = new Intent();
		intent.setClass(this, SecondActivity.class);
    	startActivity(intent);
* 跳转至其他应用中的Activity,需要指定该应用的包名和该Activity的类名

		Intent intent = new Intent();
		//启动系统自带的拨号器应用
		intent.setClassName("com.android.dialer", "com.android.dialer.DialtactsActivity");
		startActivity(intent);

###隐式意图
* 隐式意图跳转至指定Activity

		Intent intent = new Intent();
		//启动系统自带的拨号器应用
    	intent.setAction(Intent.ACTION_DIAL);
    	startActivity(intent);
* 要让一个Activity可以被隐式启动,需要在清单文件的activity节点中设置intent-filter子节点

		<intent-filter >
            <action android:name="com.itheima.second"/>
            <data android:scheme="asd" android:mimeType="aa/bb"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
	* action 指定动作(可以自定义,可以使用系统自带的)
	* data   指定数据(操作什么内容)
	* category 类别 (默认类别,机顶盒,车载电脑)
* 隐式意图启动Activity,需要为intent设置以上三个属性,且值必须与该Activity在清单文件中对三个属性的定义匹配
* intent-filter节点及其子节点都可以同时定义多个,隐式启动时只需与任意一个匹配即可
#####获取通过setData传递的数据

		//获取启动此Activity的intent对象
		Intent intent = getIntent();
		Uri uri = intent.getData();
###显式意图和隐式意图的应用场景
* 显式意图用于启动同一应用中的Activity
* 隐式意图用于启动不同应用中的Activity
	* 如果系统中存在多个Activity的intent-filter同时与你的intent匹配,那么系统会显示一个对话框,列出所有匹配的Activity,由用户选择启动哪一个
	
---
#Activity跳转时的数据传递
* Activity通过Intent启动时,可以通过Intent对象携带数据到目标Activity

		Intent intent = new Intent(this, SecondActivity.class);
    	intent.putExtra("maleName", maleName);
    	intent.putExtra("femaleName", femaleName);
    	startActivity(intent);
* 在目标Activity中取出数据

		Intent intent = getIntent();
		String maleName = intent.getStringExtra("maleName");
		String femaleName = intent.getStringExtra("femaleName");

---
#Activity生命周期
###void onCreate()
* Activity已经被创建完毕
###void onStart()
* Activity已经显示在屏幕,但没有得到焦点
###void onResume()
* Activity得到焦点,可以与用户交互
###void onPause()
* Activity失去焦点,无法再与用户交互,但依然可见
###void onStop()
* Activity不可见,进入后台
###void onDestroy()
* Activity被销毁
###void onRestart()
* Activity从不可见变成可见时会执行此方法
###使用场景
* Activity创建时需要初始化资源,销毁时需要释放资源;或者播放器应用,在界面进入后台时需要自动暂停

###完整生命周期(entire lifetime)
onCreate-->onStart-->onResume-->onPause-->onStop-->onDestory

###可视生命周期(visible lifetime)
onStart-->onResume-->onPause-->onStop

###前台生命周期(foreground lifetime)
onResume-->onPause

---
#Activity的四种启动模式
>每个应用会有一个Activity任务栈,存放已启动的Activity
>
>Activity的启动模式,修改任务栈的排列情况

* standard 标准启动模式
* singleTop 单一顶部模式 
	* 如果任务栈的栈顶存在这个要开启的activity,不会重新的创建activity,而是复用已经存在的activity。保证栈顶如果存在,不会重复创建。
	* 应用场景:浏览器的书签
* singeTask 单一任务栈,在当前任务栈里面只能有一个实例存在
	* 当开启activity的时候,就去检查在任务栈里面是否有实例已经存在,如果有实例存在就复用这个已经存在的activity,并且把这个activity上面的所有的别的activity都清空,复用这个已经存在的activity。保证整个任务栈里面只有一个实例存在
	* 应用场景:浏览器的activity
	* 如果一个activity的创建需要占用大量的系统资源(cpu,内存)一般配置这个activity为singletask的启动模式。webkit内核 c代码

* singleInstance启动模式非常特殊, activity会运行在自己的任务栈里面,并且这个任务栈里面只有一个实例存在
	* 如果你要保证一个activity在整个手机操作系统里面只有一个实例存在,使用singleInstance
	* 应用场景: 电话拨打界面

---
##横竖屏切换的生命周期
>默认情况下 ,横竖屏切换, 销毁当前的activity,重新创建一个新的activity
>
> 快捷键ctrl+F11

在一些特殊的应用程序常见下,比如游戏,不希望横竖屏切换activity被销毁重新创建
需求:禁用掉横竖屏切换的生命周期
1. 横竖屏写死 
		android:screenOrientation="landscape"
		android:screenOrientation="portrait"

2. 让系统的环境 不再去敏感横竖屏的切换。
	
		 android:configChanges="orientation|screenSize|keyboardHidden"

---
#掌握开启activity获取返回值
###从A界面打开B界面, B界面关闭的时候,返回一个数据给A界面
步骤:
1. 开启activity并且获取返回值

		startActivityForResult(intent, 0);
2. 在新开启的界面里面实现设置数据的逻辑
	
		Intent data = new Intent();
		data.putExtra("phone", phone);
		//设置一个结果数据,数据会返回给调用者
		setResult(0, data);
		finish();//关闭掉当前的activity,才会返回数据

3. 在开启者activity里面实现方法
		onActivityResult(int requestCode, int resultCode, Intent data) 
		通过data获取返回的数据
4. 根据请求码和结果码确定业务逻辑

 

 

 

 

 

 

 

 

 

#广播
* 广播的概念
	* 现实:电台通过发送广播发布消息,买个收音机,就能收听
	* Android:系统在产生某个事件时发送广播,应用程序使用广播接收者接收这个广播,就知道系统产生了什么事件。
Android系统在运行的过程中,会产生很多事件,比如开机、电量改变、收发短信、拨打电话、屏幕解锁

---
#IP拨号器
> 原理:接收拨打电话的广播,修改广播内携带的电话号码
* 定义广播接收者接收打电话广播

	public class CallReceiver extends BroadcastReceiver {

		//当广播接收者接收到广播时,此方法会调用
		@Override
		public void onReceive(Context context, Intent intent) {
			//拿到用户拨打的号码
			String number = getResultData();
			//修改广播内的号码
			setResultData("17951" + number);
		}
	}
* 在清单文件中定义该广播接收者接收的广播类型

		<receiver android:name="com.itheima.ipdialer.CallReceiver">
            <intent-filter >
                <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
            </intent-filter>
        </receiver>
* 接收打电话广播需要权限

		<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
* 即使广播接收者的进程没有启动,当系统发送的广播可以被该接收者接收时,系统会自动启动该接收者所在的进程

---
#短信拦截器
>系统收到短信时会产生一条广播,广播中包含了短信的号码和内容

* 定义广播接收者接收短信广播

		public void onReceive(Context context, Intent intent) {
		//拿到广播里携带的短信内容
		Bundle bundle = intent.getExtras();
		Object[] objects = (Object[]) bundle.get("pdus");
		for(Object ob : objects ){
			//通过object对象创建一个短信对象
			SmsMessage sms = SmsMessage.createFromPdu((byte[])ob);
			System.out.println(sms.getMessageBody());
			System.out.println(sms.getOriginatingAddress());
		}
	}
* 系统创建广播时,把短信存放到一个数组,然后把数据以pdus为key存入bundle,再把bundle存入intent
* 清单文件中配置广播接收者接收的广播类型,注意要设置优先级属性,要保证优先级高于短信应用,才可以实现拦截

		<receiver android:name="com.itheima.smslistener.SmsReceiver">
            <intent-filter android:priority="1000">
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>
* 添加权限

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

* 4.0以后广播接收者安装以后必须手动启动一次,否则不生效
* 4.0以后广播接收者如果被手动关闭,就不会再启动了

---
#监听SD卡状态
* 清单文件中定义广播接收者接收的类型,监听SD卡常见的三种状态,所以广播接收者需要接收三种广播

		 <receiver android:name="com.itheima.sdcradlistener.SDCardReceiver">
            <intent-filter >
                <action android:name="android.intent.action.MEDIA_MOUNTED"/>
                <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
                <action android:name="android.intent.action.MEDIA_REMOVED"/>
                <data android:scheme="file"/>
            </intent-filter>
        </receiver>
* 广播接收者的定义

		public class SDCardReceiver extends BroadcastReceiver {
			@Override
			public void onReceive(Context context, Intent intent) {
				// 区分接收到的是哪个广播
				String action = intent.getAction();
					
				if(action.equals("android.intent.action.MEDIA_MOUNTED")){
					System.out.println("sd卡就绪");
				}
				else if(action.equals("android.intent.action.MEDIA_UNMOUNTED")){
					System.out.println("sd卡被移除");
				}
				else if(action.equals("android.intent.action.MEDIA_REMOVED")){
					System.out.println("sd卡被拔出");
				}
			}
		}

---
#勒索软件
* 接收开机广播,在广播接收者中启动勒索的Activity
* 清单文件中配置接收开机广播

		<receiver android:name="com.itheima.lesuo.BootReceiver">
            <intent-filter >
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
* 权限

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

* 定义广播接收者

		@Override
		public void onReceive(Context context, Intent intent) {
			//开机的时候就启动勒索软件
			Intent it = new Intent(context, MainActivity.class);		
			context.startActivity(it);
		}
* 以上代码还不能启动MainActivity,因为广播接收者的启动,并不会创建任务栈,那么没有任务栈,就无法启动activity
* 手动设置创建新任务栈的flag

		it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

---
#监听应用的安装、卸载、更新
> 原理:应用在安装卸载更新时,系统会发送广播,广播里会携带应用的包名
* 清单文件定义广播接收者接收的类型,因为要监听应用的三个动作,所以需要接收三种广播

		<receiver android:name="com.itheima.app.AppReceiver">
            <intent-filter >
                <action android:name="android.intent.action.PACKAGE_ADDED"/>
                <action android:name="android.intent.action.PACKAGE_REPLACED"/>
                <action android:name="android.intent.action.PACKAGE_REMOVED"/>
                <data android:scheme="package"/>
            </intent-filter>
        </receiver>
* 广播接收者的定义

		public void onReceive(Context context, Intent intent) {
			//区分接收到的是哪种广播
			String action = intent.getAction();
			//获取广播中包含的应用包名
			Uri uri = intent.getData();
			if(action.equals("android.intent.action.PACKAGE_ADDED")){
				System.out.println(uri + "被安装了");
			}
			else if(action.equals("android.intent.action.PACKAGE_REPLACED")){
				System.out.println(uri + "被更新了");
			}
			else if(action.equals("android.intent.action.PACKAGE_REMOVED")){
				System.out.println(uri + "被卸载了");
			}
		}

---
#广播的两种类型
* 无序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,并且是没有先后顺序(同时收到)
* 有序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,但是会按照广播接收者的优先级来决定接收的先后顺序
	* 优先级的定义:-1000~1000
	* 最终接收者:所有广播接收者都接收到广播之后,它才接收,并且一定会接收
	* abortBroadCast:阻止其他接收者接收这条广播,类似拦截,只有有序广播可以被拦截

---
#Service
* 就是默默运行在后台的组件,可以理解为是没有前台的activity,适合用来运行不需要前台界面的代码
* 服务可以被手动关闭,不会重启,但是如果被自动关闭,内存充足就会重启
* startService启动服务的生命周期
	* onCreate-onStartCommand-onDestroy
* 重复的调用startService会导致onStartCommand被重复调用

---
# 进程优先级
1. 前台进程:拥有前台activity(onResume方法被调用)
2. 可见进程:拥有可见activity(onPause方法被调用)
3. 服务进程:不到万不得已不会被回收,而且即便被回收,内存充足时也会被重启
4. 后台进程:拥有后台activity(activity的onStop方法被调用了),很容易被回收
5. 空进程:没有运行任何activity,很容易被回收

---
#电话窃听器
* 电话状态:空闲、响铃、接听
* 获取电话管理器,设置侦听

		TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
		tm.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE);
* 侦听对象的实现

		class MyPhoneStateListener extends PhoneStateListener{

			//当电话状态改变时,此方法调用
			@Override
			public void onCallStateChanged(int state, String incomingNumber) {
				// TODO Auto-generated method stub
				super.onCallStateChanged(state, incomingNumber);
				switch (state) {
				case TelephonyManager.CALL_STATE_IDLE://空闲
					if(recorder != null){
						recorder.stop();
						recorder.release();
					}
					break;
				case TelephonyManager.CALL_STATE_OFFHOOK://摘机
					if(recorder != null){
						recorder.start();
					}
					break;
				case TelephonyManager.CALL_STATE_RINGING://响铃
					recorder = new MediaRecorder();
					//设置声音来源
					recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
					//设置音频文件格式
					recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
					recorder.setOutputFile("sdcard/haha.3gp");
					//设置音频文件编码
					recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
					try {
						recorder.prepare();
					} catch (IllegalStateException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					break;
				}
			}
		}

 

 

 

 

 

 

 

 

#广播接收者
* 现实中:电台要发布消息,通过广播把消息广播出去,使用收音机,就可以收听广播,得知这条消息
* Android中:系统在运行过程中,会产生会多事件,那么某些事件产生时,比如:电量改变、收发短信、拨打电话、屏幕解锁、开机,系统会发送广播,只要应用程序接收到这条广播,就知道系统发生了相应的事件,从而执行相应的代码。使用广播接收者,就可以收听广播

###创建广播接收者
1. 定义java类继承BroadcastReceiver
2. 在清单文件中定义receiver节点,定义name属性,指定广播接收者java类的全类名
3. 在intent-filter的节点中,指定action子节点,action的值必须跟要接受的广播中的action匹配,比如,如果要接受打电话广播,
那么action的值必须指定为

		<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>

* 因为打电话广播中所包含的action,就是"android.intent.action.NEW_OUTGOING_CALL",所以我们定义广播接收者时,
  action必须与其匹配,才能收到这条广播
* 即便广播接收者所在进程已经被关闭,当系统发出的广播中的action跟该广播接收者的action匹配时,系统会启动该广播接收者所在的进程,
  并把广播发给该广播接收者

###短信防火墙
* 系统发送短信广播时,是怎么把短信内容存入广播的,我们就只能怎么取出来
* 如果短信过长,那么发送时会拆分成多条短信发送,那么短信广播中就会包含多条短信
* 4.0之后,广播接收者所在进程如果从来没启动过,那么广播接收者不会生效
* 4.0之后,如果系统自动关闭广播接收者所在进程,在广播中的action跟该广播接收者的action匹配时,系统会启动该广播接收者所在的进程,但是如果是用户手动关闭该进程,
那么该进程会进入冻结状态,再也不会启动了,直到用户下一次手动启动该进程

###广播的分类
#####无序广播
* 所有与广播中的action匹配的广播接收者都可以收到这条广播,并且是没有先后顺序,视为同时收到
#####有序广播
* 所有与广播中的action匹配的广播接收者都可以收到这条广播,但是是有先后顺序的,按照广播接收者的优先级排序

---
#服务
* Service
* 运行于后台的一个组件,用来运行适合运行在后台的代码,服务是没有前台界面,可以视为没有界面的activity
###进程优先级
1. 前台进程:拥有一个正在与用户交互的Activity(onResume方法被调用)的进程
2. 可见进程:拥有一个可见但是没有焦点的Activity(onPause方法被调用)
3. 服务进程:拥有一个通过startService方法启动的服务
4. 后台进程:拥有一个不可见的Activity(onStop方法被调用)的进程
5. 空进程:没有拥有任何活动的应用组件的进程

###电话录音机
#####电话的状态
* 空闲状态
* 响铃状态
* 摘机状态

#####录音机
* 音频文件的编码和格式不是一一对应的

 

 

 

 

 

 

 

#服务两种启动方式
* startService:服务被启动之后,跟启动它的组件没有一毛钱关系
* bindService:跟启动它的组件同生共死
* 绑定服务和解绑服务的生命周期方法:onCreate->onBind->onUnbind->onDestroy

---
#找领导办证
* 把服务看成一个领导,服务中有一个banZheng方法,如何才能访问?
* 绑定服务时,会触发服务的onBind方法,此方法会返回一个Ibinder的对象给MainActivity,通过这个对象访问服务中的方法
* 绑定服务

		Intent intent = new Intent(this, BanZhengService.class);
    	bindService(intent, conn, BIND_AUTO_CREATE);
* 绑定服务时要求传入一个ServiceConnection实现类的对象
* 定义这个实现类

		class MyServiceconn implements ServiceConnection{
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			zjr = (PublicBusiness) service;
		}
		@Override
		public void onServiceDisconnected(ComponentName name) {	
		}
    	
    }
* 创建实现类对象

		 conn = new MyServiceconn();
* 在服务中定义一个类实现Ibinder接口,以在onBind方法中返回

		class ZhongJianRen extends Binder implements PublicBusiness{
		public void QianXian(){
			//访问服务中的banZheng方法
			BanZheng();
		}	
		public void daMaJiang(){
			
		}
	}
* 把QianXian方法抽取到接口PublicBusiness中定义

---
#两种启动方法混合使用
* 用服务实现音乐播放时,因为音乐播放必须运行在服务进程中,可是音乐服务中的方法,需要被前台Activity所调用,所以需要混合启动音乐服务
* 先start,再bind,销毁时先unbind,在stop

---
##使用服务注册广播接收者
* Android四大组件都要在清单文件中注册
* 广播接收者比较特殊,既可以在清单文件中注册,也可以直接使用代码注册
* 有的广播接收者,必须代码注册
	* 电量改变
	* 屏幕锁屏和解锁

* 注册广播接收者

		//创建广播接收者对象
		receiver = new ScreenOnOffReceiver();
		//通过IntentFilter对象指定广播接收者接收什么类型的广播
		IntentFilter filter = new IntentFilter();
		filter.addAction(Intent.ACTION_SCREEN_OFF);
		filter.addAction(Intent.ACTION_SCREEN_ON);
		
		//注册广播接收者
		registerReceiver(receiver, filter);
* 解除注册广播接收者

		unregisterReceiver(receiver);
* 解除注册之后,广播接收者将失去作用

##本地服务:服务和启动它的组件在同一个进程
##远程服务:服务和启动它的组件不在同一个进程
* 远程服务只能隐式启动,类似隐式启动Activity,在清单文件中配置Service标签时,必须配置intent-filter子节点,并指定action子节点

#AIDL
* Android interface definition language
* 安卓接口定义语言
* 作用:跨进程通信
* 应用场景:远程服务中的中间人对象,其他应用是拿不到的,那么在通过绑定服务获取中间人对象时,就无法强制转换,使用aidl,就可以在其他应用中拿到中间人类所实现的接口

##支付宝远程服务
1. 定义支付宝的服务,在服务中定义pay方法
2. 定义中间人对象,把pay方法抽取成接口
3. 把抽取出来的接口后缀名改成aidl
4. 中间人对象直接继承Stub对象
5. 注册这个支付宝服务,定义它的intent-Filter

##需要支付的应用
1. 把刚才定义好的aidl文件拷贝过来,注意aidl文件所在的包名必须跟原包名一致
2. 远程绑定支付宝的服务,通过onServiceConnected方法我们可以拿到中间人对象
3. 把中间人对象通过Stub.asInterface方法强转成定义了pay方法的接口
4. 调用中间人的pay方法

##五种前台进程
1. activity执行了onresume方法,获得焦点
2. 拥有一个跟正在与用户交互的activity绑定的服务
3. 拥有一个服务执行了startForeground()方法
4. 拥有一个正在执行onCreate()、onStart()或者onDestroy()方法中的任意一个的服务
5. 拥有一个正在执行onReceive方法的广播接收者

##两种可见进程
1. activity执行了onPause方法,失去焦点,但是可见
2. 拥有一个跟可见或前台activity绑定的服务

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

#服务
###开启方式
* startService
	* 该方法启动的服务所在的进程属于服务进程
	* Activity一旦启动服务,服务就跟Activity一毛钱关系也没有了
* bindService
	* 该方法启动的服务所在进程不属于服务进程
	* Activity与服务建立连接,Activity一旦死亡,服务也会死亡

* 服务的混合调用
	* 先开始、再绑定,先解绑、再停止

#使用代码配置广播接收者
* 可以使用清单文件注册
	* 广播一旦发出,系统就会去所有清单文件中寻找,哪个广播接收者的action和广播的action是匹配的,如果找到了,就把该广播接收者的进程启动起来
* 可以使用代码注册
	* 需要使用广播接收者时,执行注册的代码,不需要时,执行解除注册的代码

###特殊的广播接收者
* 安卓中有一些广播接收者,必须使用代码注册,清单文件注册是无效的
1. 屏幕锁屏和解锁
2. 电量改变

###服务的分类
* 本地服务:指的是服务和启动服务的activity在同一个进程中
* 远程服务:指的是服务和启动服务的activity不在同一个进程中

###AIDL
* Android interface definition language
* 进程间通信
1. 把远程服务的方法抽取成一个单独的接口java文件
2. 把接口java文件的后缀名改成aidl
3. 在自动生成的PublicBusiness.java文件中,有一个静态抽象类Stub,它已经继承了binder类,实现了publicBusiness接口,这个类就是新的中间人
4. 把aidl文件复制粘贴到06项目,粘贴的时候注意,aidl文件所在的包名必须跟05项目中aidl所在的包名一致
5. 在06项目中,强转中间人对象时,直接使用Stub.asInterface()

###进程优先级
* 前台进程
	* 拥有一个正在与用户交互的activity(onResume调用)的进程
	* 拥有一个与正在和用户交互的activity绑定的服务的进程
	* 拥有一个正在“运行于前台”的服务——服务的startForeground方法调用
	* 拥有一个正在执行以下三个生命周期方法中任意一个的服务(onCreate(), onStart(), or onDestroy())
	* 拥有一个正在执行onReceive方法的广播接收者的进程
* 可见进程
	* 拥有一个不在前台,但是对用户依然可见的activity(onPause方法调用)的进程
	* 拥有一个与可见(或前台)activity绑定的服务的进程

 

 

 

 

 

 

 

 

 

 

 

 

 

 

#内容提供者
* 应用的数据库是不允许其他应用访问的
* 内容提供者的作用就是让别的应用访问到你的数据库
* 自定义内容提供者,继承ContentProvider类,重写增删改查方法,在方法中写增删改查数据库的代码,举例增方法

		@Override
		public Uri insert(Uri uri, ContentValues values) {
			db.insert("person", null, values);
			return uri;
		}
* 在清单文件中定义内容提供者的标签,注意必须要有authorities属性,这是内容提供者的主机名,功能类似地址

		<provider android:name="com.itheima.contentprovider.PersonProvider"
            android:authorities="com.itheima.person"
            android:exported="true"
         ></provider>

* 创建一个其他应用,访问自定义的内容提供者,实现对数据库的插入操作

		public void click(View v){
			//得到内容分解器对象
			ContentResolver cr = getContentResolver();
			ContentValues cv = new ContentValues();
			cv.put("name", "小方");
			cv.put("phone", 138856);
			cv.put("money", 3000);
			//url:内容提供者的主机名
			cr.insert(Uri.parse("content://com.itheima.person"), cv);
		}

###UriMatcher
* 用于判断一条uri跟指定的多条uri中的哪条匹配
* 添加匹配规则

		//指定多条uri
		um.addURI("com.itheima.person", "person", PERSON_CODE);
		um.addURI("com.itheima.person", "company", COMPANY_CODE);
		//#号可以代表任意数字
		um.addURI("com.itheima.person", "person/#", QUERY_ONE_PERSON_CODE);
* 通过Uri匹配器可以实现操作不同的表

		@Override
		public Uri insert(Uri uri, ContentValues values) {
			if(um.match(uri) == PERSON_CODE){
				db.insert("person", null, values);
			}
			else if(um.match(uri) == COMPANY_CODE){
				db.insert("company", null, values);
			}
			else{
				throw new IllegalArgumentException();
			}
			return uri;
		}
* 如果路径中带有数字,把数字提取出来的api

		int id = (int) ContentUris.parseId(uri);

---
#短信数据库
* 只需要关注sms表
* 只需要关注4个字段
	* body:短信内容
	* address:短信的发件人或收件人号码(跟你聊天那哥们的号码)
	* date:短信时间
	* type:1为收到,2为发送
* 读取系统短信,首先查询源码获得短信数据库内容提供者的主机名和路径,然后

		ContentResolver cr = getContentResolver();
		Cursor c = cr.query(Uri.parse("content://sms"), new String[]{"body", "date", "address", "type"}, null, null, null);
		while(c.moveToNext()){
			String body = c.getString(0);
			String date = c.getString(1);
			String address = c.getString(2);
			String type = c.getString(3);
			System.out.println(body+";" + date + ";" + address + ";" + type);
		}

* 插入系统短信

		ContentResolver cr = getContentResolver();
		ContentValues cv = new ContentValues();
		cv.put("body", "您尾号为XXXX的招行储蓄卡收到转账1,000,000人民币");
		cv.put("address", 95555);
		cv.put("type", 1);
		cv.put("date", System.currentTimeMillis());
		cr.insert(Uri.parse("content://sms"), cv);
* 插入查询系统短信需要注册权限

---
#联系人数据库

* raw\_contacts表:
	* contact_id:联系人id
* data表:联系人的具体信息,一个信息占一行
	* data1:信息的具体内容
	* raw\_contact_id:联系人id,描述信息属于哪个联系人
	* mimetype_id:描述信息是属于什么类型
* mimetypes表:通过mimetype_id到该表查看具体类型

###读取联系人
* 先查询raw\_contacts表拿到联系人id

		Cursor cursor = cr.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"contact_id"}, null, null, null);
* 然后拿着联系人id去data表查询属于该联系人的信息

		Cursor c = cr.query(Uri.parse("content://com.android.contacts/data"), new String[]{"data1", "mimetype"}, "raw_contact_id = ?", new String[]{contactId}, null);
* 得到data1字段的值,就是联系人的信息,通过mimetype判断是什么类型的信息

		while(c.moveToNext()){
			String data1 = c.getString(0);
			String mimetype = c.getString(1);
			if("vnd.android.cursor.item/email_v2".equals(mimetype)){
				contact.setEmail(data1);
			}
			else if("vnd.android.cursor.item/name".equals(mimetype)){
				contact.setName(data1);
			}
			else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){
				contact.setPhone(data1);
			}
		}
###插入联系人
* 先查询raw\_contacts表,确定新的联系人的id应该是多少
* 把确定的联系人id插入raw\_contacts表

		cv.put("contact_id", _id);
		cr.insert(Uri.parse("content://com.android.contacts/raw_contacts"), cv);
* 在data表插入数据
	* 插3个字段:data1、mimetype、raw\_contact_id
		
			cv = new ContentValues();
			cv.put("data1", "赵六");
			cv.put("mimetype", "vnd.android.cursor.item/name");
			cv.put("raw_contact_id", _id);
			cr.insert(Uri.parse("content://com.android.contacts/data"), cv);
			
			cv = new ContentValues();
			cv.put("data1", "1596874");
			cv.put("mimetype", "vnd.android.cursor.item/phone_v2");
			cv.put("raw_contact_id", _id);
			cr.insert(Uri.parse("content://com.android.contacts/data"), cv);

-----
#内容观察者
* 当数据库数据改变时,内容提供者会发出通知,在内容提供者的uri上注册一个内容观察者,就可以收到数据改变的通知

		cr.registerContentObserver(Uri.parse("content://sms"), true, new MyObserver(new Handler()));
		
		class MyObserver extends ContentObserver{

			public MyObserver(Handler handler) {
				super(handler);
				// TODO Auto-generated constructor stub
			}
	
			//内容观察者收到数据库发生改变的通知时,会调用此方法
			@Override
			public void onChange(boolean selfChange) {

			}
		
		}
* 在内容提供者中发通知的代码

		ContentResolver cr = getContext().getContentResolver();
		//发出通知,所有注册在这个uri上的内容观察者都可以收到通知
		cr.notifyChange(uri, null);

 

 

 

 

 

#ContentProvider
* 四大组件之一
* 内容提供者的作用:把私有数据暴露给其他应用,通常,是把私有数据库的数据暴露给其他应用

###短信数据库
* sms表
	* body:短信内容
	* date:短信时间
	* address:对方号码
	* type:发送还是接收

###联系人数据库
* raw_contacts表
	* contact_id:联系人id
* data表:存放联系人的详细的信息,每行数据是单独的一条联系人信息
	* data1:联系人的具体的信息
	* raw_contact_id:该行信息属于哪个联系人
	* mimetype_id:该行信息属于什么类型
* mimetypes表:mimetype_id对应的类型的字符串

 

 

 

 

 

 

##帧动画FrameAnimation
* 多张图片快速切换,形成动画效果
* 帧动画使用xml定义

##补间动画
* 组件由原始状态向终极状态转变时,为了让过渡更自然,而自动生成的动画
###位移动画

		TranslateAnimation ta = new TranslateAnimation(10, 100, 20, 200);
* 10:表示的x坐标起始位置
	* iv的真实x + 10

* 100:表示x坐标的结束位置
	* iv的真实x + 100

* 20:表示y坐标的起始位置
	* iv的真实y + 20

* 200:表示y坐标的结束位置
	* iv的真实y + 200

			TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 3, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2);
* Animation.RELATIVE_TO_SELF, 1:x坐标的初始位置
	* iv的真实x + 1 * iv宽

* Animation.RELATIVE_TO_SELF, 0.5f:y坐标的起始位置
	* iv的真实y + 0.5 * iv高

###缩放动画

		ScaleAnimation sa = new ScaleAnimation(0.5f, 2, 0.1f, 3, iv.getWidth() / 2, iv.getHeight() / 2);
* 0.5f:表示x坐标缩放的初始位置
	* 0.5 * iv宽
* 2:表示x坐标缩放的结束位置
	* 2 * iv宽
* iv.getWidth() / 2:表示缩放点的x坐标
	* iv的真实x + iv.getWidth() / 2

			ScaleAnimation sa = new ScaleAnimation(0.5f, 2, 0.1f, 3, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
* Animation.RELATIVE_TO_SELF, 0.5f:表示缩放点的x坐标
	* iv的真实x + 0.5 * iv宽

###透明动画

		AlphaAnimation aa = new AlphaAnimation(0, 0.5f);
* 0表示动画的起始透明度
* 0.5f表示动画的结束透明度

 

 

 

 

#Fragment
* 用途:在一个Activity里切换界面,切换界面时只切换Fragment里面的内容
* 生命周期方法跟Activity一致,可以理解把其为就是一个Activity
* 定义布局文件作为Fragment的显示内容

		//此方法返回的View就会被显示在Fragment上
		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
			// TODO Auto-generated method stub
			//用布局文件填充成一个View对象,返回出去,那么就显示在Fragment上了
			View v = inflater.inflate(R.layout.fragment01, null);
			return v;     
		}
* 把Fragment显示至指定ViewGroup中

		//把fragment显示至界面
    	//new出fragment对象
    	Fragment01 fg = new Fragment01();
    	FragmentManager fm = getFragmentManager();
    	//开启事务
    	FragmentTransaction ft = fm.beginTransaction();
    	//把fragment对象显示到指定资源id的组件里面
    	ft.replace(R.id.fl, fg);
    	ft.commit();
###生命周期
* fragment切换时旧fragment对象会销毁,新的fragment对象会被创建
###低版本兼容
* 在support-v4.jar包中有相关api,也就是说fragment可以在低版本模拟器运行

---
#动画
###帧动画
> 一张张图片不断的切换,形成动画效果

* 在drawable目录下定义xml文件,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长
		
		<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
		    <item android:drawable="@drawable/g1" android:duration="200" />
		    <item android:drawable="@drawable/g2" android:duration="200" />
		    <item android:drawable="@drawable/g3" android:duration="200" />
		</animation-list>
* 在屏幕上播放帧动画

		ImageView iv = (ImageView) findViewById(R.id.iv);
		//把动画文件设置为imageView的背景
        iv.setBackgroundResource(R.drawable.animations);
        AnimationDrawable ad = (AnimationDrawable) iv.getBackground();
		//播放动画        
		ad.start();

###补间动画
* 原形态变成新形态时为了过渡变形过程,生成的动画就叫补间动画
* 位移、旋转、缩放、透明
#####位移:
* 参数10指的是X的起点坐标,但不是指屏幕x坐标为10的位置,而是imageview的 真实X + 10
* 参数150指的是X的终点坐标,它的值是imageview的 真实X + 150
	
		//创建为位移动画对象,设置动画的初始位置和结束位置
		TranslateAnimation ta = new TranslateAnimation(10, 150, 20, 140);
* x坐标的起点位置,如果相对于自己,传0.5f,那么起点坐标就是 真实X + 0.5 * iv宽度
* x坐标的终点位置,如果传入2,那么终点坐标就是 真实X + 2 * iv的宽度
* y坐标的起点位置,如果传入0.5f,那么起点坐标就是 真实Y + 0.5 * iv高度
* y坐标的终点位置,如果传入2,那么终点坐标就是 真实Y + 2 * iv高度
	
		TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2);

* 动画播放相关的设置

		//设置动画持续时间
    	ta.setDuration(2000);
		//动画重复播放的次数
    	ta.setRepeatCount(1);
		//动画重复播放的模式
    	ta.setRepeatMode(Animation.REVERSE);
		//动画播放完毕后,组件停留在动画结束的位置上
    	ta.setFillAfter(true);
		//播放动画
    	iv.startAnimation(ta);
#####缩放:
* 参数0.1f表示动画的起始宽度是真实宽度的0.1倍
* 参数4表示动画的结束宽度是真实宽度的4倍
* 缩放的中心点在iv左上角

		ScaleAnimation sa = new ScaleAnimation(0.1f, 4, 0.1f, 4);
* 参数0.1f和4意义与上面相同
* 改变缩放的中心点:传入的两个0.5f,类型都是相对于自己,这两个参数改变了缩放的中心点
* 中心点x坐标 = 真实X + 0.5 * iv宽度
* 中心点Y坐标 = 真实Y + 0.5 * iv高度
	
	ScaleAnimation sa = new ScaleAnimation(0.1f, 4, 0.1f, 4, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
#####透明:
* 0为完全透明,1为完全不透明

		AlphaAnimation aa = new AlphaAnimation(0, 0.5f);

#####旋转:
* 20表示动画开始时的iv的角度
* 360表示动画结束时iv的角度
* 默认旋转的圆心在iv左上角

		RotateAnimation ra = new RotateAnimation(20, 360);
* 20,360的意义和上面一样
* 指定圆心坐标,相对于自己,值传入0.5,那么圆心的x坐标:真实X + iv宽度 * 0.5
* 圆心的Y坐标:真实Y + iv高度 * 0.5

		RotateAnimation ra = new RotateAnimation(20, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
#####所有动画一起飞
	
		//创建动画集合
		AnimationSet set = new AnimationSet(false);
		//往集合中添加动画
		set.addAnimation(aa);
		set.addAnimation(sa);
		set.addAnimation(ra);
		iv.startAnimation(set);

---
#属性动画
* 补间动画,只是一个动画效果,组件其实还在原来的位置上,xy没有改变
###位移:
* 第一个参数target指定要显示动画的组件
* 第二个参数propertyName指定要改变组件的哪个属性
* 第三个参数values是可变参数,就是赋予属性的新的值
* 传入0,代表x起始坐标:当前x + 0
* 传入100,代表x终点坐标:当前x + 100

		//具有get、set方法的成员变量就称为属性
		ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "translationX", 0, 100) ;

###缩放:
* 第三个参数指定缩放的比例
* 0.1是从原本高度的十分之一开始
* 2是到原本高度的2倍结束

		ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "scaleY", 0.1f, 2);
###透明:
* 透明度,0是完全透明,1是完全不透明
		
		ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "alpha", 0.1f, 1);
###旋转
* rotation指定是顺时针旋转
* 20是起始角度
* 270是结束角度

		ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "rotation", 20, 270);
* 属性指定为rotationX是竖直翻转
* 属性指定为rotationY是水平翻转

		ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "rotationY", 20, 180);

###可变参数
* 第三个参数可变参数可以传入多个参数,可以实现往回位移(旋转、缩放、透明)

		ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "translationX", 0, 70, 30, 100) ;

###所有动画一起飞

		//创建动画师集合
		AnimatorSet set = new AnimatorSet();
		//设置要播放动画的组件
		set.setTarget(bt);
		//所有动画有先后顺序的播放
		//set.playSequentially(oa, oa2, oa3, oa4);
		//所有动画一起播放
		set.playTogether(oa, oa2, oa3, oa4);
		set.start();


 

 

 

 

 

 

下拉刷新-------
	1.addHeaderView必须在setAdapter之前调用
	2.将paddingTop设置一个headerView高度的负值去隐藏它
	
	getHeight()和getMeasuredHeight()的区别:
	getMeasuredHeight():获取测量完的高度,只要在onMeasure方法执行完,就可以用
	                    它获取到宽高,在自定义控件内部多使用这个
						使用view.measure(0,0)方法可以主动通知系统去测量,然后就
						可以直接使用它获取宽高
	getHeight():必须在onLayout方法执行完后,才能获得宽高
				view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
					@Override
					public void onGlobalLayout() {
					headerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
					int headerViewHeight = headerView.getHeight();
					//直接可以获取宽高
			}
		});
	3.setSelection(position);将对应位置的item放置到屏幕顶端
	
侧滑菜单---
github-SlidingMenu
	1.在ViewGroup中,让自己内容移动有以下几个方法:
	layout(l,t,r,b);
	offsetTopAndBottom(offset)和offsetLeftAndRight(offset);
	scrollTo和scrollBy方法;
	注意:滚动的并不是viewgroup内容本身,而是它的矩形边框
		  它是瞬间移动,
	
	2.在自定义ViewGroup中一般不需要去实现onMeasure,
	  我们去实现系统已有的ViewGroup,比如FrameLayout,
	  它会帮我们区实现onMeasure方法


	3.让view在一段时间内移动到某个位置
	 a.使用自定义动画(让view在一段时间内做某件事)
	   
	 b.使用Scroller(模拟一个执行流程,)

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值