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. 将内容提交到主线程中显示
学习笔记
最新推荐文章于 2024-09-11 21:45:27 发布