学习笔记

mail:bjsunwei@tedu.cn

项目:
友录 :对手机联系人,短信,电话的操作
       ContentProvider,ListView/GridView+适配器,Service

1)数据的持久化
   偏好设置文件,文件(内部/外部) 数据库Sqlite 网络

2)Sqlite存取
   step1:DBHelper继承自SqliteOpenHelper
         onCreate 第一次创建数据库
           建表的语句
         onUpdate 数据库的版本号发生变化
   step2:DBUtil 要用属性持有DBHelper
         DBUtil会写CRUD方法,这些方法中实际执行操作的都是SqliteDatabase对象
         如何获得SqliteDatabase对象?就利用DBHelper的getWriteableDatabase
         
         CRUD方法的参数都要包含一个数据表的名字
     
   数据库是存储在内部:data/data/项目名称/database/xxx.db      

3) ContentProvider组件,它可以提供跨进程的数据访问
   step1 写一个MyContentProvider继承在ContentProvider 重写6个方法
         CRUD 四个方法
         onCreate
         getType
   step2 在AndroidManifest文件中进行注册。
         name
         Uri
         
   getContentResolver----->CRUD Uri---->ContentProvider CRUD
   
4) 友录项目操作联系人,通话记录,短信 
   人家提供的ContentProvider
   找到人家注册的Uri 
===================================================================
包名
com.tarena.youlu
 -adapter   适配器
 -fragment  Fragment
 -util      工具类
 -biz/model 业务类
 -ui        Activity
 -view      自定义的View
 -bean/entity/pojo 实体类  
 -test      单元测试      

启动界面 SplashActivity

   修改AndroidManifest文件,将<intent-filter>剪切复制
   修改label属性
   修改Theme属性android:theme="@android:style/Theme.Light.NoTitleBar"

动画分类
 补间动画:alpha动画,缩放动画,移动动画,旋转动画,组合
 帧动画
 布局动画
 属性动画

includ 标签
在一个布局文件中引用一个已进写好的布局

如果用DDMS拖拽contact2.db报null错误,用命令行方式
找到adb.exe 所在的文件夹
打以下命令
adb pull 
data/data/com.android.providers.contacts/databases/contacts2.db
d:/

cotacts2.db中,与联系人相关的表

contacts表 _id列 获取联系人的id,photo_id 获取联系人的photo_id

raw_contacts表 在单机账户环境下,没有用

data表 与联系人相关的所有详细信息,都被记录在这种数据表中
       每一条信息,在表中用一行数据来显示
       信息的具体内容存放在DATA1列
       只有头像的具体内容会以二进制数组的方式存储在DATA15列

mimetypes表 提供不同类型的联系人信息的说明


单元测试
对整个项目中,部分代码单独运行,测试结果
主要是对工具类来进行测试
使用单元测试要进行如下必要的设置:
添加 <uses-library android:name="android.test.runner"/>
导入Android JUnit类库
添加
<instrumentation 
    android:name="android.test.InstrumentationTestRunner"
    android:targetPackage="com.tarena.youlu"
    android:label="test"
    ></instrumentation>
对测试类库进行相关的配置

建立test包,Test类继承AndroidTestCast,写test方法

在启动模拟器的情况下,右键单击test方法,选择Run As...Android Junit

如果运行成功,JUnit面板会显示绿色。
测试失败,JUnit面板会显示红色。

自定义View
继承自View 重写:构造器,onDraw
onDraw方法的作用,就是绘图。在绘图的时候,需要有画笔,画布

分为两大类:
一类是从头创建:继承自View类
应该重写构造器,以及重写三个方法:onMeasure,onLayout,onDraw

一类是继承已有的View/ViewGroup
对已有的View/ViewGroup做一些局部功能的扩展

咱们下午写显示圆形图的控件 CircleImageView 继承自ImageView

Canvas画布+Paint画笔
圆形

创建一个Bitmap对象的方法:
Bitmap.createBitmap(200, 200, Config.ARGB_8888);

调整一个Bitmap图形的大小的方法:
Bitmap.createScaledBitmap(bitmap, 200, 200, true);

着色器:
着色器有线性颜色渐变着色器,放射颜色渐变着色器和图片着色器
线性渐变着色器:
LinearGradient(0, 0, 200, 200, 
    			Color.GREEN, Color.BLUE, 
    			TileMode.CLAMP);
放射颜色渐变着色器
RadialGradient(100, 100, 100, 
    			Color.RED, Color.YELLOW, 
    			TileMode.CLAMP);
图片着色器
BitmapShader(bitmap ,TileMode.CLAMP, TileMode.CLAMP);

利用着色器绘图,只要将创建好的着色器放置到画笔上即可:
paint.setShader(shader);

利用图形混合模式
图形混合模式是用来处理两幅有相交区域图形的一些列的算法
采用不同的混合模式可以产生不同的效果
安卓中一共有18种混合模式
比较常用的有SRC_IN DST_IN等

    			  			
dp和px的换算关系

dp是一个绝对值单位 160dp=1英寸=25.4毫米
屏幕密度 1英寸可以显示多少个像素点
ldpi  120px/英寸
mdpi  160px/英寸
hdpi  240px/英寸
xhdpi 320px/英寸 

dp    px    屏幕密度
 1    .75    ldpi
 1    1      mdpi    
 1    1.5    hdpi
 1    2     xhdpi

自定义View设置自定义属性
自定义属性写在 res/value/attrs.xml文件中
<resources>
    <declare-styleable name="CircleImageView">
        <attr name="border_color" format="color"></attr>
        <attr name="border_width" format="dimension"></attr>
    </declare-styleable>
</resources>
声明了两个自定义属性:
border_color属性,该属性接收的属性值类型为颜色
border_width属性,该属性接收的属性值类型为尺寸
注意:使用IDE写这个文件的时候,没有自动提示功能,必须注意拼写,不要写错了

在布局文件中使用自定义属性,使用前,一定要声明命名空间:
 xmlns:sunwei="http://schemas.android.com/apk/res/应用包名"
 命名空间的名字为sunwei,地址为http://schemas.android.com/apk/res/应用包名
 使用自定义属性的时候:
 sunwei:border_color = "#ff00ffff"  边框的颜色值
 sunwei:border_width = "2dp"        边框的粗度
 
 在自定义View中读取在布局文件中添加的自定义属性的值
 一般在构造方法中添加一个init(context,attrs)方法
 private void init(Context context, AttributeSet attrs) {
		TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView);
		borderColor = t.getColor(
				R.styleable.CircleImageView_border_color, 
				Color.WHITE);
		borderWidth = t.getDimensionPixelSize(
				R.styleable.CircleImageView_border_width, 
				2);
		t.recycle();
	}
使用TypedArray对象后,一定要记住回收资源,调用它的recyle方法。


发送隐式意图添加联系人

隐式意图一定要包含3个内容:
action:ContactContracts.Intents.SHOW_OR_CREATE_CONTACT
必须要包含电话号码,电话号码放到intent的data部分:
data: Uri.parse("tel:"+number);
必须要包含名字,名字要放到inent的Extra部分,key有常量:
extra: key是 ContactContracts.Intent.Insert.Name
       value是 name

可选信息插入:电子邮件,地址,这些内容都是作为extra部分添加
电子邮件:key:ContactContracts.Intent.Insert.Email
         value:邮箱
地址:key:ContactContracts.Intent.Insert.Postal
      value:地址

代码示例:
          Intent intent = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT);
					Uri data = Uri.parse("tel:13123456789");
					intent.setData(data );
					intent.putExtra(ContactsContract.Intents.Insert.NAME, "Zhangsan");
					intent.putExtra(ContactsContract.Intents.Insert.EMAIL, "zhangsan@abc.com");
					getActivity().startActivity(intent);

去数据库中取数据然后呈现数据
1)数据库中数据表关联的Uri是什么?
2)准备好实体类,用来封装从数据表中取出的数据
3)准备Biz类/Util类
有了以上三步,就可以拿到数据并且封装好
就可以放到ListView/GridView中展示数据
4) 适配器
5)条目布局(Item Layout)
有了4,5两步,数据就已经展示出来了
6)添加监听器、菜单实现更多的相关业务逻辑

calls表的Uri

常量:CallLog.Calls.CONTENT_URI;

calls表结构

type        1:呼入 2:呼出 3:未接
_id         id值
number      电话号码
name        姓名 (联系人列表中没有的,name值为null)
photo_id    头像
date        时间

这里特别需要注意的是photo_id这个列。
该列并没有提供常量与之对应,实际测试中会发现,calls表中photo_id列
的值与联系人实际的photo_id值并不相符。所以,如果要设置头像时,不能
直接取用calls表中photo_id列的值。
解决的方法是,利用电话号码(number列的值)来反查联系人的photo_id
反查的时候,可以使用系统提供的一个特别的Uri
ContactContract.PhoneLookup.CONTENT_FILTER_URI
另外,在使用ContentResolver进行查询的时候,可以使用两种形式的Uri
一种是针对某一张表,一种是针对某一张表中某一条具体的数据。
这里使用了Uri类提供的withAppendedPath方法
构建了一个指向某一条具体数据的Uri:
Uri uri = Uri.withAppendedPath(
		ContactsContract.PhoneLookup.CONTENT_FILTER_URI, 
		number);

完整的利用电话号码反查photo_id的代码是:
	public static int getPhotoIdByNumber(Context context,String number){
		int photo_id = 0;
		ContentResolver cr = context.getContentResolver();
		//content://contacts/phonelookup/13513513500
		Uri uri = Uri.withAppendedPath(
		ContactsContract.PhoneLookup.CONTENT_FILTER_URI, 
		number);
		String[] projection = new String[]{PhoneLookup.PHOTO_ID};
		Cursor c = cr.query(uri,projection,null,null,null);
		if(c.moveToNext()){
			photo_id = c.getInt(0);
		}
		c.close();
		return photo_id;
	}

可以看一下PhoneLookup中其它的常量。
利用PhoneLookup,可以查到更多的信息。

如何查看一个Uri对应的表中所有列的名字和值
相关的方法:
c.getColumnCount() 获得Cursor中,包含的列的数量
c.getColumnName(i) 获得Cursor中,第i列的名字(第一列的i值是0)
c.getString(i)     获得Cursor中,某一行数据的第i列中的内容是什么
所以,使用一个遍历的方式,就可以打印出Cursor中,某一行数据的所有列的列名和值
    Cursor c = cr.query(uri,null,null,null,null);
		if(c.moveToNext()){
			for(int i=0;i<c.getColumnCount();i++){
				System.out.println(
					"列名是:"+c.getColumnName(i)+
					" ,该列的值是:"+
					c.getString(i));
			}
		}
		c.close();

从calls表中删除一条记录
删除一条记录的方式与查询非常类似,只是调用的ContentResolver方法不同
cr.delete(uri,where,args);
ContentResolver的delete方法会调用uri对应的ContentProvider的delete方法
从相应的数据表中删除数据。
代码示例:
  public static void removeCalllog(Context context, MyCallLog mcl) {
		ContentResolver cr = context.getContentResolver();
		String where = Calls._ID + " = ?";
		String[] selectionArgs = new String[]{String.valueOf(mcl.get_id())};
		//利用ContentProvider删除数据表中的数据
		cr.delete(CallLog.Calls.CONTENT_URI,
				where, 
				selectionArgs);
	}
ContentResolver的delete方法也有返回值,它的返回值意思是从数据表中
删除了多少条数据。
   

会话实体类:
需要的属性:
_id ?会话id
photo_id 头像
name 姓名
body 会话列表中最后一条短信的正文
date 会话列表中最后一条短信正文的时间
threadDate 将date中时间戳格式转为美工设计格式
read 读/未读
phone 电话号码

threads数据表相关
Uri
数据表中可用的数据列:
_id 会话的id
date 
snippet 一个会话中最后一条短信的正文
read    已读/未读

ContentProvider给我们提供了什么?
列名是:body ,该列的值是:88
列名是:person ,该列的值是:null
列名是:date ,该列的值是:1454224060276
列名是:read ,该列的值是:1
列名是:thread_id ,该列的值是:1
列名是:address ,该列的值是:1 330-133-1330

MyBaseAdapter适配器的基类

HuihuaAdapter extends MyBaseAdapter

去数据库中取数据然后呈现数据

1)数据库中数据表关联的Uri是什么?
2)准备好实体类,用来封装从数据表中取出的数据
3)准备Biz类/Util类
有了以上三步,就可以拿到数据并且封装好
就可以放到ListView/GridView中展示数据
4) 适配器
5)条目布局(Item Layout)
有了4,5两步,数据就已经展示出来了
6)添加监听器、菜单实现更多的相关业务逻辑


短信表
Uri content://sms 短信表的Uri

另外两个相关的Uri:
content://sms/sent 所有发出短信(短信的发件箱)
content://sms/inbox 所有接收短信(短信的收件箱)

短信表的ContentProvider返回的列:
列名是:_id ,该列的值是:7 短信在短信表中的id
列名是:thread_id ,该列的值是:3 短信所属的会话的id
列名是:address ,该列的值是:13201321320
列名是:person ,该列的值是:3 如果是我发送出去的0,如果是联系人列表中不存在的也是0
列名是:date ,该列的值是:1454224131597
列名是:read ,该列的值是:0   1是已读 0是未读
列名是:type ,该列的值是:1   1是我收到的,2是我发出去的
列名是:body ,该列的值是:qing ni chi fan!

项目中,实际需要哪些属性:
int _id         短信id
int photo_id    头像id
String body     短信正文
int type        是收到的还是发出的
long date       时间
String smsDate  将date属性的值转为美工要求的格式
*int person      如果是自己或者陌生人,值0
                如果是联系人列表中有的,值就是他在联系人表中的id
*String number   电话号码     

加星号的属性是可选属性

ListView中显示多种条目布局
要重写适配器的两个关联方法:
public int getViewTypeCount() 
该方法返回条目视图种类的数量,咱们的例子里面返回2,也就是2种条目视图类型
public int getItemViewType(int position) 
返回position位置的数据应该使用哪一种条目视图
该方法必须与getViewTypeCount配合使用。
对于咱们的例子,如果position位置的短信数据的类型为收到的短信,则返回0
如果position位置的短信数据的类型为发出的短信,则返回1
注意:如果getViewTypeCount方法返回值为2,则getItemViewType方法的返回值只能是0或1
如果getViewTypeCount方法返回值为4,则getItemViewType方法的返回值只能是0或1或2或3
不能随便写。
重写好了这两个方法后,在重写getView方法的时候,在"膨胀"条目视图的时候
需要根据getItemViewType的返回值,来为convertView选择不同的布局文件
代码示例:
int type = getItemViewType(position);
if(convertView == null){
			switch (type) {
			case LEFT:
				convertView = inflater.inflate(R.layout.chat_item_left, parent,false);
				break;

			case RIGHT:
				convertView = inflater.inflate(R.layout.chat_item_right, parent,false);
				break;
			}
	......
}

ListView的一些其它相关方法
设置ListView的分割线不可见:listView.setDividerHeight(0);
设置ListView显示条目时显示最后一条:listView.setSelection(adapter.getCount()-1);
设置ListView条目点击的时候不改变颜色:在布局文件中为<ListView />添加属性:android:listSelector="#00000000"
设置ListView滚动的时候不改变颜色:在布局文件中为<ListView />添加属性:android:cacheColorHint="#00000000"


通过隐式意图,调用系统的发送短信程序,发送短信
		
		Intent intent = new Intent(Intent.ACTION_SENDTO);
		Uri data = Uri.parse("smsto:"+etNumber.getText().toString());
		intent.setData(data );
		intent.putExtra("sms_body", etContent.getText().toString());
		startActivity(intent);

利用代码发送短信
通过SmsManager可以发送短消息。
manager.sendTextMessage(接收方电话号码, 服务商电话号码, 
					短信正文, 
					发送后的延迟Intent, 送抵后的延迟Intent);
注意:第二个参数一般都传null。
      第三个参数,短信正文部分是有长度限制的。对于过长的短信可能不能发送
      短信正文的长度以及短信正文过长时如何处理取决于电信运营商。
      也可以使用SmsManager的divideMessage方法显式的将短信分割为一个短信集合
      第五个参数指定的PendingIntent是否被回调,取决于电信运营商

构建PendingIntent时第四个参数的作用:
PendingIntent pi = PendingIntent.getBroadcast(上下文,请求码,Intent,0);
如果第四个参数传递0,那么第三个参数中Intent的Extra部分可能不会更新
如果第四个参数传递PendingIntent.FLAG_UPDATE_CURRENT,每次都会去更新Intent中的Extra内容



利用代码接收短信息
当系统接收到短信息的时候,会发送一个有序广播,广播的action为:android.provider.Telephony.SMS_RECEIVED
只要我们的程序中写一个广播接收器,也接收这个广播就可以获得短信的相关内容。
    IntentFilter filter = new IntentFilter();
		filter.addAction("android.provider.Telephony.SMS_RECEIVED");
		filter.setPriority(999);
		registerReceiver(receiver, filter);
因为该广播为一个有序广播,系统的短信程序收到广播后
会阻止广播继续传播,隐藏我们需要设置一下广播接收器的优先级,
将优先级设定的高一些(1000左右),抢在系统的短信程序之前先接受到系统广播
当收到了系统广播后,我们就可以对广播Intent对一下处理,从Intent的bundle中获得
短信的正文,时间和发送短信的电话号码:
				Bundle bundle = intent. getExtras();
				Object[] pdus = (Object[]) bundle.get("pdus");
				//用来拼接后获得短信正文
				StringBuilder sb = new StringBuilder();
				//用来获得电话号码
				String number = "";
			
				for (int i = 0; i < pdus.length; i++) {
					SmsMessage sm = SmsMessage.createFromPdu((byte[]) pdus[i]);
					sb.append(sm.getMessageBody());
					if(i==pdus.length-1)
						number = sm.getOriginatingAddress();
					//还可以拿短信时间
					//sm.getTimestampMillis();
				}
			
				Log.d("TAG","发送方的电话号码是:"+number);
				Log.d("TAG","短信的正文内容是:"+sb.toString());

短信广播是有序广播,所以我们的广播接收器在收到广播处理完毕后,也可以
阻止广播的继续发送,只需要调用abortBroadcast()方法。

友录中聊天界面短信发送业务逻辑
1)点击“发送”按钮,利用SmsManager发送编辑框中输入的内容
2)使用SmsManager发送的短信是不会写入短信数据表的,因此必须在短信发送后
   手动将刚才发送的内容写入到短信数据表
3)写入短信数据表的时候,要使用短信发信箱content://sms/sent对应的ContentProvider来进行
   插入数据的时候,要利用ContentValues来构建数据,ContentValues共需要7个键值对
   values.put("thread_id", thread_id);//短信所属的会话ID
		values.put("address", address);//对方的电话号码
		values.put("person", 0);//联系人(因为是我发送,所以是0)
		values.put("date", System.currentTimeMillis());//时间
		values.put("read", 1);//已读
		values.put("type", 2);//发送,所以是2
		values.put("body",body);//短信正文
4)当数据写入数据表完毕后,调用refresh方法,刷新ListView

友录中聊天界面短信接收业务逻辑
1) 聊天界面中使用广播接收器来接收系统发送的短信广播
2)当有新的短信到来时,根据收到的广播Intent内容来构建一个写入短信收件箱的“短信”
   与写入发件箱类似,这里写入的时候需要使用content://sms/inbox对应的ContentProvider
   写入时使用的ContentValues对象也需要七个属性:
   values.put("thread_id", thread_id);//短信所属的会话ID
		values.put("address", address);//对方的电话号码,从短信广播intent的bundle中获得
		values.put("person", id);//联系人在手机联系人表中的id,这个id需要使用电话号码反查
		values.put("date", date);//时间,从短信广播intent的bundle中获得
		values.put("read", 1);//已读
		values.put("type", 1);//接收的短信
		values.put("body",body);//短信正文,从短信广播intent的bundle中获得
3)当写入短信数据表完毕后,要阻止广播继续传播,防止系统的短信接收程序接收到该短信后再次写入数据表
4)调用refresh方法,刷新ListView界面
注意:在短信界面截获系统的短信广播后,必须先对广播中包含的电话号码
与当前聊天人的电话号码进行比对!如果两个号码是一样的,则可以进行上述
操作,如果不一样,说明该短信与当前的聊天人无关,不应该进行截获。

设置字体
在SmsUtil中写一个方法来设置字体:
public static void setFont(Context context,TextView tv){
		if(TF==null){
			AssetManager mgr = context.getAssets();
			String path = "fonts/LHANDW.TTF";
			TF = Typeface.createFromAsset(mgr, path);
		}
		tv.setTypeface(TF);
	}
字体文件比较大,所以一般应该将其设置为静态属性,使用时,只需要加载一遍即可。

利用代码在RelativeLayout中添加TextView
1)创建要添加的TextView对象
2)设置TextView的基本属性(包括文本内容,字体大小,字体颜色,居中等)
3)设置TextView的尺寸。设置TextView的尺寸时必须使用LayoutParams来进行设置
   因为子控件的尺寸并不是由子控件自己决定的,而是由它的父控件来决定的。
4)设置每一个TextView的位置。位置参数也是添加在LayoutParams参数中
5)将设定好的TextView添加到父控件RelativeLayout中
示例代码:
通过代码在dialPad中添加12个TextView

private void initDialPad(RelativeLayout dialPad) {
		//获得dialPad的宽度。它的宽度是屏幕的宽度
		int width = getActivity().
				getResources().
				getDisplayMetrics().
				widthPixels;
		//获得dialPad的高度,300dp,但是要将dp转为像素
		int height = (int) TypedValue.
				applyDimension(
						TypedValue.COMPLEX_UNIT_DIP, 
						300, 
						getActivity().getResources().getDisplayMetrics());
		//获得一个拨号键(TextView)的宽度和高度
		int tvWidth = width/3;
		int tvHeight = height/5;
		//在dialPad上面画12个TextView
		for(int i=0;i<12;i++){
			final TextView tv = new TextView(getActivity());
			//设置tv属性的代码
			tv.setText(i+1+"");
			if(i==9){
				tv.setText("*");
			}
			if(i==10){
				tv.setText("0");
			}
			if(i==11){
				tv.setText("#");
			}
			tv.setTextSize(TypedValue.COMPLEX_UNIT_SP,30);
			tv.setTextColor(Color.BLACK);
			//内容在TextView中居中
			tv.setGravity(Gravity.CENTER);
			//父控件的布局参数
			RelativeLayout.LayoutParams params = 
					new RelativeLayout.LayoutParams(
							tvWidth, tvHeight);
			//设置每一个TextView的位置
			tv.setId(i+1);//设置id值,但是id不能为0
			//添加谁在谁右侧的规则
			if(i%3!=0){
				params.addRule(RelativeLayout.RIGHT_OF, i);
			}
			//添加谁在谁下方的规则
			if(i>=3){
				params.addRule(RelativeLayout.BELOW, i-2);
			}
			tv.setLayoutParams(params);
			dialPad.addView(tv);
		}

	}

使用SoundPool来播放音效文件
1)创建SoundPool对象
   new SoundPool(4, AudioManager.STREAM_MUSIC, 0);
   构造器中3个参数的意思是:
   第一个参数意为可以同时播放几个音效
   第二个参数意为要播放的音效的数据类型
   第三个参数暂时无意义
2)为SoundPool对象添加加载音效完毕的监听器。当要播放的音效文件加载完毕后
   会回调监听器中的onLoadComplete方法。
   将播放音效的代码写在该回调方法中。
  
   pool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
			
			@Override
			public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
				soundPool.play(sampleId, 1.0f, 1.0f, 0, 0, 1.0f);
			}
		});
		play方法是用来播放音效的具体方法,它的5个参数意思为:
		1.左声道音量。2.右声道音量。3.该参数暂时没有用。4.循环播放的次数。5.播放速率
		一般这5个参数都是指定为1.0f,1.0f,0,0,1.0f
3)写一个播放音效的方法,在该方法中去加载要播放的音效文件
  public void playSound(int resId){
		pool.load(getActivity(), resId, 1);
	}
	load方法是SoundPool用来加载音效文件的方法。该方法的三个参数是:
	1.上下文。2.资源文件的名称(音效文件一般都放在res/raw文件夹下,使用时的资源id为R.raw.XXX)
	3.这个参数暂时没有用,但是要求使用1。

内容变化监听器TextWather
里面提供了3个方法:

public void onTextChanged(CharSequence s, int start, int before, int count) {
				//只能看不能摸
			}
			
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count,
					int after) {
				//只能看不能摸
			}
			
			@Override
			public void afterTextChanged(Editable s) {
				
			}
三个方法中的onTextChanged和beforeTextChanged方法不能对tvTitle中
的内容做任何的修改,所以是“只能看不能摸”的方法
在afterTextChanged方法中,可以对tvTitle的内容进行修改
比如,在友录的逻辑中,如果tvTitle中的长度超过13时(11位手机号码和2个-)
将tvTitle中超出长度的内容都要删除掉
代码如下所示:
			public void afterTextChanged(Editable s) {
				String content = s.toString();
				//当监听器发现tvTitle中内容的长度大于13的时候
				//就把大于部分的内容删除掉
				if(content.length()>13){
					s.delete(13, content.length());
				}
			}

利用系统提供的电话功能拨打电话
				Intent intent = new Intent(Intent.ACTION_CALL);
				intent.setData(Uri.parse("tel:"+number));
				startActivity(intent);
action为Intent.ACTION_CALL
data为Uri.parse("tel:"+电话号码)
注意拨打电话属于敏感操作,要在AndroidManifest文件中添加权限:
<uses-permission android:name="android.permission.CALL_PHONE" />
				

ViewPager的缓存机制
ViewPager为了让Fragment的滑动更加流畅,默认时ViewPager会对上一个和下一个
Fragment提取创建出来进行缓存。这样会导致一个结果,Fragment的一些
生命周期方法可能被提前调用。
通过使用ViewPager的setOffscreenPageLimit(数量)方法可以设定ViewPager的
缓存Fragment数量。
在Fragment中有一个方法setUserVisibleHint(boolean isVisibleToUser)
会在Fragment不可见/可见时均会被调用。所以可以把一些业务逻辑放到该方法中。
但是需要注意的是,该方法有可能在onCreateView方法调用之前被调用

Activity和Service之间通信
Activity启动Service:
startService 、 bindService
通信:
通过广播、Binder(ServiceConnection)

AIDL是什么
AIDL:安卓接口描述语言
.aidl文件,是用来让Activity和Service进行跨进程的绑定

提供服务的app
1)写一个.aidl文件。aidl文件的内容和Java的interface非常像,但是没有public
关键字。一个.aidl文件的示例:
package com.example.servicedemo;

interface MyBinder{
	String getTime();
}
MyBinder就是一个利用aidl定义的接口,它提供了一个方法叫做getTime。
当MyBinder.aidl写完之后,如果没有错误,会在程序的gen文件夹下,
由系统自动生成一个MyBinder.java源文件。

2)在Service中写一个内部类,继承MyBinder.Stub类,并实现在MyBinder.aidl中
定义的抽象方法:
  public class MeBinder extends MyBinder.Stub{

		@Override
		public String getTime() throws RemoteException {
			return new SimpleDateFormat("HH:mm:ss").format(new Date());
		}
		
	}
这里使用一个名为MeBinder的类继承了MyBinder.Stub并实现了getTime方法

3)将MeBinder类的对象作为Service的onBind方法的返回值。
	public IBinder onBind(Intent intent) {
		return new MeBinder();
	}
到时,谁利用bindService的方式来与该Service进行绑定,都会将MeBinder对象
返回给绑定者。

4)在AndroidManifest文件中注册该Service,并且要提供一个隐式意图
        <service 
            android:name="com.example.servicedemo.MyService"
            android:enabled="true"
            android:exported="true"
            >
            <intent-filter >
                <action android:name="com.tarena.TIME_SERVICE"/>
            </intent-filter>
        </service>
   使用者会通过该隐式意图与该Service进行绑定。

使用服务的app
1)必须获得提供服务app中的aidl文件。而且要求包结构必须完全一致。
   如果包名结构完全一致,当复制结束后,会在使用服务的app的gen文件夹下生成
   一个.java文件。比如,我们的例子中,使用服务的app要将提供服务app的MyBinder.aidl文件
   连同完整的包结构一并复制过来。当复制结束后,会在使用服务app的gen文件夹下生成一个MyBinder.java文件
2)在使用服务app的Activity中至少要声明两个属性:
   ServiceConnection conn;
	 MyBinder binder;//特别注意这个MyBinder类,是系统根据复制过来的MyBinder.aidl文件自动生成的

3)初始化ServiceConnection类型对象conn
   conn = new ServiceConnection() {

			@Override
			public void onServiceDisconnected(ComponentName name) {
			}

			@Override
			public void onServiceConnected(ComponentName name, IBinder service) {
				binder = MyBinder.Stub.asInterface(service);
			}
		};
		在onServiceConnected方法中,会得到被绑定的Service中onBind方法返回的IBinder类型的对象
4)通过隐式意图绑定方式启动Servie
   Intent intent = new Intent("com.tarena.TIME_SERVICE");
	 bindService(intent, conn, Context.BIND_AUTO_CREATE);
	 注意,bindService要确保在conn对象已经完成了初始化之后再进行
5)一旦获得了binder,就可以在使用服务的app中调用它里面的getTime方法了
   public void getTime(View v){
		TextView tv = (TextView) findViewById(R.id.tv_time);
		try {
			if(binder!=null){
				String time = binder.getTime();
				tv.setText(time);
			}
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	注意,使用MyBinder中的方法前,要先确保MyBinder已经被正常的初始化了。

PHONE STATE
安卓的电话管理是依靠TelephonyManager类
电话一共分为三种状态:
IDLE:   闲置
RINGING: 振铃
OFFHOOK: 摘机
------------------------------------------------------------------
HTTP基础
安卓中提供了两个类可以进行HTTP的访问

HttpURLConnection / HttpClient

在安卓中进行网络访问的注意事项:
1)必须要申请权限 <android.premission.INTERNET>
2)所有网络访问相关代码必须写在工作线程中。
3)所有网络访问获得的结果,必须通过跨线程通信的方式提交到主线程再做显示

安卓中写网络访问程序的基本步骤
step0. 申请网络访问权限
step1. 起线程
step2. 创建能够进行网络访问的对象(HttpClient对象/HttpURLConnection对象)
step3. 设定网络访问的方式(GET/POST)
step4. 设定参数
step5. 发起真正的请求,获得响应
step6. 解析响应内容
step7. 将内容提交到主线程中显示
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值