
什么是 Retrofit ? Retrofit 是一套 RESTful 架构的 Android(Java) 客户端实现,基于注解,提供 JSON to POJO(Plain Ordinary Java Object ,简单 Java 对象),POJO to JSON,网络请求(POST,GET, PUT,DELETE 等)封装。 配置环境 在build.gradle中添加 ..... //编译RxJava compile 'io.reactivex:rxjava:1.1.6' //编译RxAndroid compile 'io.reactivex:rxandroid:1.2.1' //编译Retrofit及其相关库,包括Gson compile 'com.squareup.okhttp3:okhttp:3.3.1' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' compile 'com.squareup.okhttp3:logging-interceptor:3.3.1' 说明: Retrofit默认依赖于okhttp,所以需要集成okhttp。API返回的数据为JSON格式,在此我使用的是Gson对返回数据解析.请使用最新版的Gson 。 接口 这里我们调试借助百度名人名言API 该接口的API主机地址为:http://apistore.baidu.com; 需要访问的接口:avatardata/mingrenmingyan/lookup; 需要一个key等于apikey的Header和一个keyword等于名人名言的查询关键字,而且该请求为GET请求. 接口返回json格式: { "total": 10, "result": [ { "famous_name": "佚名", "famous_saying": "婚姻是一家私人专门银行,存储真爱和默契,提取幸福和快乐。夫妻双方互为账户,且存折是活期的,可以随存随取,而家庭则是这家银行里的柜台,通过它,夫妻双方可以把自己的喜怒哀乐尽情地存进对方的银行里,并可随时提取微笑、鼓励、安慰、体贴、温柔等利息。" }, { "famous_name": "英国", "famous_saying": "真爱无坦途" }, { "famous_name": "狄太人", "famous_saying": "一个人真爱的时候,甚至会想不到自己是爱着对方。" }, { "famous_name": "佚名", "famous_saying": "所有的阻碍,全是对真爱的淬炼。" }, { "famous_name": "罗兰", "famous_saying": "当你真爱一个人的时候,你是会忘记自己的苦乐得失,而只是关心对方的苦乐得失的。" }, { "famous_name": "罗兰", "famous_saying": "当两人之间有真爱情的时候,是不会考虑到年龄的问题,经济的条件,相貌的美丑,个子的高矮,等等外在的无关紧要的因素的。假如你们之间存在着这种问题,那你要先问问自己,是否真正在爱才好。" }, { "famous_name": "佚名", "famous_saying": "真正的勇气是来自内心的真爱。" }, { "famous_name": "佚名", "famous_saying": "天国般的幸福,存在于对真爱的希望。" }, { "famous_name": "狄太人", "famous_saying": "一个人真爱的时候,甚至会想不到自己是爱着对方" }, { "famous_name": "Shakespeare", "famous_saying": "通向真爱的路从无坦途。" } ], "error_code": 0, "reason": "Succes" } 定义实体类 我们根据上面API返回的json数据来创建一个Famous数据对象,我们可以利用AndroidStudio插件 GsonFormat 快速方便的将json数据转为Java 对象。 Famous.java public class Famous { //下面变量的定义要与接口中的字段名字保持一致 public int total; public int error_code; public String reason; public List<FamousInfo> result; public static class FamousInfo { public String famous_name; public String famous_saying; } } 注意:如果你的字段有跟json不一样的,要在字段上面加注解@SerializedName,@SerializedName是指定Json格式中的Key名。 如上面的错误码字段,你就像定义为code,而服务器返回的是error_code,这个时候就应该这么写: @SerializedName("error_code") public int code; 使用 首先定义 public abstract class BaseApi { public static final String API_SERVER = "服务器地址" private static final OkHttpClient mOkHttpClient = new OkHttpClient(); private static Retrofit mRetrofit; protected static Retrofit getRetrofit() { if (Retrofit == null) { Context context = Application.getInstance().getApplicationContext(); //设定30秒超时 mOkHttpClient.setConnectTimeout(30, TimeUnit.SECONDS); //设置拦截器,以用于自定义Cookies的设置 mOkHttpClient.networkInterceptors() .add(new CookiesInterceptor(context)); //设置缓存目录 File cacheDirectory = new File(context.getCacheDir() .getAbsolutePath(), "HttpCache"); Cache cache = new Cache(cacheDirectory, 20 * 1024 * 1024); mOkHttpClient.setCache(cache); //构建Retrofit mRetrofit = new Retrofit.Builder() //配置服务器路径 .baseUrl(API_SERVER + "/") //设置日期解析格式,这样可以直接解析Date类型 .setDateFormat("yyyy-MM-dd HH:mm:ss") //配置转化库,默认是Gson .addConverterFactory(GsonConverterFactory.create()) //配置回调库,采用RxJava .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //设置OKHttpClient为网络客户端 .client(mOkHttpClient) .build(); } return mRetrofit; } } 定义FamousApi public class FamousApi extends BaseApi{ //定义接口 private interface FamousService { @GET("/avatardata/mingrenmingyan/lookup") Observable<Famous> getFamousList(@Header("apiKey") String apiKey, @Query("keyword") String keyword, @Query("page") int page, @Query("rows") int rows); } protected static final FamousService service = getRetrofit().create(FamousService.class); public static Observable<UserProfileResp> getFamousList(int userId){ return service.getFamousList(userId); } } 最终使用: public void getFamousList(){ FamousApi.getFamousList("apiKey","人才",1,20) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Famous>(){ @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Famous famous) { List<FamousInfo> list = famous.result; //填充UI } }); } 如下效果: 参考demo:https://github.com/jdsjlzx/RetrofitDemo
很想给大家分享这个开源项目,但是由于工作的关系,没有抽出空,但还是趁着工作间隙写下了这篇博客。 简介 LRecyclerView是支持addHeaderView、 addFooterView、下拉刷新、分页加载数据的RecyclerView。 它对 RecyclerView 控件进行了拓展,给RecyclerView增加HeaderView、FooterView,并且不需要对你的Adapter做任何修改。 主要功能 下拉刷新、滑动到底部自动加载下页数据; 可以方便添加Header和Footer; 头部下拉样式可以自定义; 具备item点击和长按事件。 网络错误加载失败点击Footer重新请求数据; 可以动态为FooterView赋予不同状态(加载中、加载失败、滑到最底等)。 项目地址:https://github.com/jdsjlzx/LRecyclerView 感谢 如果我比别人看得远些,那是因为我站在巨人们的肩上。 (牛顿) 本开源控件是基于 HeaderAndFooterRecyclerView 开源项目而来,在原基础上进行了扩充。在此感谢cundong作者(github地址:https://github.com/cundong)。 效果图 使用 添加HeaderView、FooterView mDataAdapter = new DataAdapter(this); mDataAdapter.setData(dataList); mHeaderAndFooterRecyclerViewAdapter = new HeaderAndFooterRecyclerViewAdapter(this, mDataAdapter); mRecyclerView.setAdapter(mHeaderAndFooterRecyclerViewAdapter); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); //add a HeaderView RecyclerViewUtils.setHeaderView(mRecyclerView, new SampleHeader(this)); //add a FooterView RecyclerViewUtils.setFooterView(mRecyclerView, new SampleFooter(this)); 注意: mHeaderAndFooterRecyclerViewAdapter = new HeaderAndFooterRecyclerViewAdapter(this, mDataAdapter); HeaderAndFooterRecyclerViewAdapter提供了一些实用的功能,使用者不用关心它的实现,只需构造的时候把自己的mDataAdapter以参数形式传进去即可。 下拉刷新和加载更多 为了大家使用方便,将需要用的方法统一封装到接口LScrollListener中。 mRecyclerView.setLScrollListener(new LRecyclerView.LScrollListener() { @Override public void onRefresh() { } @Override public void onScrollUp() { } @Override public void onScrollDown() { } @Override public void onBottom() { } @Override public void onScrolled(int distanceX, int distanceY) { } }); LScrollListener实现了nRefresh()、onScrollUp()、onScrollDown()、onBottom()、onScrolled五个事件,如下所示: void onRefresh();//pull down to refresh void onScrollUp();//scroll down to up void onScrollDown();//scroll from up to down void onBottom();//load next page void onScrolled(int distanceX, int distanceY);// moving state,you can get the move distance onRefresh()——RecyclerView下拉刷新事件; onScrollUp()——RecyclerView向上滑动的监听事件; onScrollDown()——RecyclerView向下滑动的监听事件; onBottom()——RecyclerView滑动到底部的监听事件; onScrollDown()——RecyclerView正在滚动的监听事件; 加载更多(加载下页数据) 从上面的LScrollListener介绍中就可以看出,实现加载更多只要在onBottom()接口中处理即可。 下拉刷新 为了达到和Listview的下拉刷新效果,本项目没有借助SwipeRefreshLayout控件,而是在自定义RecyclerView头部实现的刷新效果。 这里的下拉刷新效果借鉴了开源库:AVLoadingIndicatorView 设置加载样式: mRecyclerView.setRefreshProgressStyle(ProgressStyle.BallSpinFadeLoader); mRecyclerView.setArrowImageView(R.drawable.iconfont_downgrey); AVLoadingIndicatorView库有多少效果,LRecyclerView就支持多少下拉刷新效果,当然你也可以自定义下拉效果。 效果图: 下拉刷新逻辑处理: 从上面的LScrollListener介绍中就可以看出,实现下拉刷新只要在onRefresh()接口中处理即可。 加载网络异常处理 加载数据时如果网络异常或者断网,LRecyclerView为你提供了重新加载的机制。 效果图: 网络异常出错代码处理如下: RecyclerViewStateUtils.setFooterViewState(getActivity(), mRecyclerView, getPageSize(), LoadingFooter.State.NetWorkError, mFooterClick); private View.OnClickListener mFooterClick = new View.OnClickListener() { @Override public void onClick(View v) { RecyclerViewStateUtils.setFooterViewState(getActivity(), mRecyclerView, getPageSize(), LoadingFooter.State.Loading, null); requestData(); } }; 上面的mFooterClick就是我们点击底部的Footer时的逻辑处理事件,很显然我们还是在这里做重新请求数据操作。 点击事件和长按事件处理 在Hongyang前辈的博客中有下描述: Click and LongClick 不过一个挺郁闷的地方就是,系统没有提供ClickListener和LongClickListener。 不过我们也可以自己去添加,只是会多了些代码而已。 实现的方式比较多,你可以通过mRecyclerView.addOnItemTouchListener去监听然后去判断手势, 当然你也可以通过adapter中自己去提供回调,这里我们选择后者,前者的方式,大家有兴趣自己去实现。 出自:http://blog.csdn.net/lmj623565791/article/details/45059587 Hongyang大神选择了后者,LRecyclerView早期选择了前者,经过实践总结,在adapter中实现点击事件会好点。 先看下怎么使用: mHeaderAndFooterRecyclerViewAdapter.setOnItemClickLitener(new OnItemClickLitener() { @Override public void onItemClick(View view, int position) { } @Override public void onItemLongClick(View view, int position) { } }); 原理就是实现viewHolder.itemView的点击和长按事件。由于代码过多就不贴出来了。 viewHolder.itemView是RecyclerView.Adapter中本身就具有的,不用额外定义。 源码如下: public static abstract class ViewHolder { public final View itemView; int mPosition = NO_POSITION; int mOldPosition = NO_POSITION; long mItemId = NO_ID; int mItemViewType = INVALID_TYPE; int mPreLayoutPosition = NO_POSITION; 设置空白View(setEmptyView) mRecyclerView.setEmptyView(view); 注意布局文件: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <com.cundong.recyclerview.LRecyclerView android:id="@+id/list" android:layout_width="fill_parent" android:layout_height="fill_parent"/> <include android:id="@+id/empty_view" layout="@layout/layout_empty" android:visibility="gone"/> </RelativeLayout> 分享 介绍完了LRecyclerView,似乎还少些什么,对了,那就是adapter了。 为了方便大家使用,分享个封装过的adapter。 public class ListBaseAdapter<T extends Entity> extends RecyclerView.Adapter { protected Context mContext; protected int mScreenWidth; public void setScreenWidth(int width) { mScreenWidth = width; } protected ArrayList<T> mDataList = new ArrayList<>(); @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return null; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } @Override public int getItemCount() { return mDataList.size(); } public List<T> getDataList() { return mDataList; } public void setDataList(Collection<T> list) { this.mDataList.clear(); this.mDataList.addAll(list); notifyDataSetChanged(); } public void addAll(Collection<T> list) { int lastIndex = this.mDataList.size(); if (this.mDataList.addAll(list)) { notifyItemRangeInserted(lastIndex, list.size()); } } public void clear() { mDataList.clear(); notifyDataSetChanged(); } } ListBaseAdapter使用了泛型,简单方便,消除了强制类型转换。 使用如下: private class DataAdapter extends ListBaseAdapter<ItemModel>{ private LayoutInflater mLayoutInflater; public DataAdapter(Context context) { mLayoutInflater = LayoutInflater.from(context); mContext = context; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ViewHolder(mLayoutInflater.inflate(R.layout.sample_item_text, parent, false)); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ItemModel item = mDataList.get(position); ViewHolder viewHolder = (ViewHolder) holder; viewHolder.textView.setText(item.title); } private class ViewHolder extends RecyclerView.ViewHolder { private TextView textView; public ViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.info_text); } } } ListBaseAdapter虽然功能不强大,但是使用很方便。 结语 LRecyclerView使用方便简单,无论你添加多少Header和Footer,你都不用担心position的问题,除了方便还是方便。 最后再介绍下项目地址:https://github.com/jdsjlzx/LRecyclerView 如果觉得上面的例子UI简单,这里再分享个公司项目:https://github.com/jdsjlzx/Community 效果图: 如果大家使用过程中有任何问题,请留言,我会及时修正。 后记:如果后续再增加功能,请详细看github上面的项目介绍,将不在博客里面更新。
1. android:button="@null" 可以屏蔽CheckBox的选择框 2.android:textColor="@drawable/selector_text_color" 文本text 设置selectColor 效果图: <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/dp_36" android:layout_marginRight="@dimen/dp_36" android:orientation="vertical"> <CheckBox android:id="@+id/course_checkbox" android:layout_width="match_parent" android:layout_height="@dimen/edit_box_height" android:layout_marginTop="@dimen/dp_10" android:gravity="center" android:button="@null" android:background="@drawable/selector_btn_checkbox" android:textColor="@drawable/selector_text_color" android:textSize="@dimen/sp_18" android:text="课程类" /> <CheckBox android:id="@+id/article_checkbox" android:layout_width="match_parent" android:layout_height="@dimen/edit_box_height" android:layout_marginTop="@dimen/dp_10" android:gravity="center" android:button="@null" android:background="@drawable/selector_btn_checkbox" android:textColor="@drawable/selector_text_color" android:textSize="@dimen/sp_18" android:text="文章类" /> </LinearLayout> drawable/selector_text_color.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@color/colorAccent" android:state_checked="true"/> <item android:color="#ff979797" android:state_checked="false" /> </selector> 同样适用于RadioButton
最近有这样的需求,把每次统计到的数据,以txt形式保存到手机SD卡或是手机内存中,遇到一些问题,记录下来。 首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: [html] view plain copy <!-- SDCard中创建与删除文件权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!-- 向SDCard写入数据权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 接着在使用SDcard进行读写的时候 会用到Environment类下面的几个静态方法 : 1: getDataDirectory() 获取到Android中的data数据目录(sd卡中的data文件夹) 2:getDownloadCacheDirectory() 获取到下载的缓存目录(sd卡中的download文件夹) 3:getExternalStorageDirectory() 获取到外部存储的目录 一般指SDcard(/storage/sdcard0) 4:getExternalStorageState() 获取外部设置的当前状态 一般指SDcard,比较常用的应该是 MEDIA_MOUNTED(SDcard存在并且可以进行读写)还有其他的一些状态,可以在文档中进行查找。 5:getRootDirectory() 获取到Android Root路径 好,以下是具体操作,直接看代码: 1,判断SD卡是否存在 [java] view plain copy /** * 判断SDCard是否存在 [当没有外挂SD卡时,内置ROM也被识别为存在sd卡] * * @return */ public static boolean isSdCardExist() { return Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED); } 2,获取SD卡根目录 [java] view plain copy /** * 获取SD卡根目录路径 * * @return */ public static String getSdCardPath() { boolean exist = isSdCardExist(); String sdpath = ""; if (exist) { sdpath = Environment.getExternalStorageDirectory() .getAbsolutePath(); } else { sdpath = "不适用"; } return sdpath; } 3,获取默认的文件存放路径 [java] view plain copy /** * 获取默认的文件路径 * * @return */ public static String getDefaultFilePath() { String filepath = ""; File file = new File(Environment.getExternalStorageDirectory(), "abc.txt"); if (file.exists()) { filepath = file.getAbsolutePath(); } else { filepath = "不适用"; } return filepath; } 4-1,使用FileInputStream读取文件 [java] view plain copy try { le file = new File(Environment.getExternalStorageDirectory(), "test.txt"); FileInputStream is = new FileInputStream(file); byte[] b = new byte[inputStream.available()]; is.read(b); String result = new String(b); System.out.println("读取成功:"+result); } catch (Exception e) { e.printStackTrace(); } 4-2,使用BufferReader读取文件 [java] view plain copy try { File file = new File(Environment.getExternalStorageDirectory(), DEFAULT_FILENAME); BufferedReader br = new BufferedReader(new FileReader(file)); String readline = ""; StringBuffer sb = new StringBuffer(); while ((readline = br.readLine()) != null) { System.out.println("readline:" + readline); sb.append(readline); } br.close(); System.out.println("读取成功:" + sb.toString()); } catch (Exception e) { e.printStackTrace(); } httpConnection读取流保存成String数据 [java] view plain copy URL url = new URL(getForwardUrl("/queryUserByUNorIP")); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); InputStream is = conn.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String readline = null; while ((readline = br.readLine()) != null) { sb.append(readline); } System.out.println("result"+sb.toString()); 等效于使用ByteArrayOutputStream [java] view plain copy InputStream is = conn.getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len =-1 ; while ((len=is.read(buffer))!=-1) { bos.write(buffer, 0, len); } is.close(); bos.close(); String result = new String(bos.toByteArray()); System.out.println("result"+result); 5-1,使用FileOutputStream写入文件 [java] view plain copy try { File file = new File(Environment.getExternalStorageDirectory(), DEFAULT_FILENAME); FileOutputStream fos = new FileOutputStream(file); String info = "I am a chinanese!"; fos.write(info.getBytes()); fos.close(); System.out.println("写入成功:"); } catch (Exception e) { e.printStackTrace(); } 5-2,使用BufferedWriter写入文件 [java] view plain copy try { File file = new File(Environment.getExternalStorageDirectory(), DEFAULT_FILENAME); //第二个参数意义是说是否以append方式添加内容 BufferedWriter bw = new BufferedWriter(new FileWriter(file, true)); String info = " hey, yoo,bitch"; bw.write(info); bw.flush(); System.out.println("写入成功"); } catch (Exception e) { e.printStackTrace(); } 读取和写入我们都实现了,貌似很简单的样子,但是我们现在想每隔30秒进行一次数据整理,然后把他们写入到我们制定的txt文件中,但是我想每次都能在上一次的结尾处开始写入,这样在电脑上通过文本打开时,就能看到每一行的数据了。 这其实要求我们每一次写入数据时,都要有换行的操作符号,比如:\n,并且IO读写能以追加的方式写入到文件里。 刚开始我很笨的想到,每次写入前,先把文件读取出来并且生成一个StringBuffer,然后再append,然后再写入.....这种方式导致每次都要2次以上的IO操作,读和写。其实系统写入时就给我们自带了append方式,还是要勤看文档啊! BufferedWriter 使用BufferedWriter,在构造BufferedWriter时,把第二个参数设为true BufferedWriter out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(file, true))); out.write(conent); FileWriter 构造函数中的第二个参数true表示以追加形式写文件 FileWriter writer = new FileWriter(fileName, true); writer.write(content); writer.close(); // 打开一个随机访问文件流,按读写方式 RandomAccessFile randomFile = new RandomAccessFile(fileName, "rw"); // 文件长度,字节数 long fileLength = randomFile.length(); // 将写文件指针移到文件尾。 randomFile.seek(fileLength); randomFile.writeBytes(content); randomFile.close(); 问题:我在file写入时,没一次写完后,明明都添加了换行符(bw.write("\n")),为什么在Window的文本文档中看不到换行呢?而在EditPlus或是notepad++中就能看到换行后的效果? [java] view plain copy BufferedWriter bw = new BufferedWriter(new FileWriter(file, true)); String info = " hey, yoo,bitch"; bw.write(info); bw.write("\n"); bw.flush(); 如上代码所示,可是在windows的文本文档中: 但是在诸如notepad++或EditPlus中看到却是没问题的: 这是为什么呢? 这是windows与linux系统的编码模式不同造成的。android系统是linux内核,与windows不同。windows是采用的是DOS编码方式,所用的换行符是DOS换行符CR/LF,也就是我们俗称的\r\n,(如果不理解可以去百度一下转义字符,一般程序员会用到这些知识),而linux系统的换行符为UNIX换行符LF,也就是\n,苹果的MAC系统用的是MAC换行符CR,也就是\r,现在我想你也差不多理解了。你在android手机里建立的文档肯定用的是UNIX换行符,也就是一个\n,但是这个文档你拿到windows里用记事本打开的话,因为windows记事本是DOS换行符\r\n,所以你少了个\r,所以没法识别成换行,只能给你识别成一个小方块了,解决办法很简单,你可以用EditPlus或者UltraEdit软件打开,UltraEdit也能转换这些编码模式,转换成DOS模式就可以了。 所以,我们只需要添加:\r\n [java] view plain copy BufferedWriter bw = new BufferedWriter(new FileWriter(file, true)); String info = " hey, yoo,bitch"; bw.write(info); bw.write("\r\n"); bw.flush();
声明:该博文以socket中,关闭输出流为例进行说明。 为了方便讲解,我们把DataOutputstream dout = new DataOutputStream(new BufferedOutputStream(mySocket.getOutputStream()));中的dout做为Socket输出流的代言。同样的,din是输入流的代言。 可以造成dout被关闭的操作有: 1、调用dout.close();或din.close();因为使用这种流关闭,会造成socket被关闭,所以输入输出流都将不可再用。 2、调用socket.close(); 3、调用socket.shutdownOutputStream();单方面关闭dout,此时din还可正常使用。 以下,我将对socket中关闭输出流进行3个测试: 输出流关闭测试一:socket关闭吗? 输出流关闭测试二:该流是否可以重新开启? 输出流关闭测试三:输出缓冲区里的数据是丢弃,还是发送? 测试结果如下: 测试一:dout.close();会造成socket被关闭,但socket.shutdownOutputStream()不会。 测试二:不可以,会抛出异常! 测试三:丢弃 客户端程序: [java] view plain copy package com.test2; import java.io.*; import java.net.*; /** * @ClassName: SocketTest * @Description: 测试Socket中,流关闭后,socket是否关闭?是否可重开流?输出缓存区的数据是发送出去,还是丢弃? * @author 慢跑学Android * @date 2011-11-12 上午11:15:21 * */ public class SocketTest { Socket mySocket; DataOutputStream dout; public static void main(String[] args){ new SocketTest(); } public SocketTest(){ // 输出流关闭的测试一:socket关闭吗? test1(); // 输出流关闭测试二:该流是否可以重新开启? test2(); // 输出流关闭测试三:输出缓冲区里的数据是丢弃,还是发送? test3(); } private void test1() { // 输出流关闭的测试一:socket关闭吗? System.out.println("\n****2种方式关闭输出流,Socket是否关闭?***\n"); try { mySocket = new Socket("27.154.122.233",9999); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { dout = new DataOutputStream(new BufferedOutputStream(mySocket.getOutputStream())); //下面这一句主要是用来证明socket确实处于开启状态 System.out.println("输出流刚打开,Socket是否关闭?" + mySocket.isClosed()); mySocket.shutdownOutput(); System.out.println("使用shutdownOutput关闭输出流,Socket是否关闭?" + mySocket.isClosed()); dout.close(); System.out.println("使用close关闭输出流,Socket是否关闭?" + mySocket.isClosed()); } catch (IOException e) { e.printStackTrace(); } } private void test2() { // 输出流关闭测试二:使用shutdownOutputStream后,输出流是否可以重新开启? System.out.println("\n****使用shutdownOutputStream后,输出流是否可以重新开启?***\n"); try { mySocket = new Socket("27.154.122.233",9999); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { dout = new DataOutputStream(new BufferedOutputStream(mySocket.getOutputStream())); mySocket.shutdownOutput(); // 重开输出流 dout = new DataOutputStream(mySocket.getOutputStream()); dout.writeUTF("是否允许我重开?"); // 清空输出缓存,确保当dout通道没问题时,消息可以到达服务器 dout.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { mySocket.close(); } catch (IOException e) { e.printStackTrace(); } } } private void test3(){ // 输出流关闭测试三:输出缓冲区里的数据是丢弃,还是发送? System.out.println("\n***输出缓冲区里的数据是丢弃,还是发送?****\n"); try { mySocket = new Socket("27.154.122.233",9999); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { dout = new DataOutputStream(new BufferedOutputStream(mySocket.getOutputStream())); dout.writeUTF("shutdownOutput后,数据发得得出去吗?"); mySocket.shutdownOutput(); } catch (IOException e) { e.printStackTrace(); } } } 服务器端程序: [java] view plain copy /** * @Title: ServerSocketTest.java * @Package com.test1 * @Description: TODO(该文件为”Socket中,流关闭后,发生什么事“的Sever测试端) * @author 慢跑学Android * @date 2011-11-12 上午11:31:05 * @version V1.0 */ package com.test1; import java.io.*; import java.net.*; public class ServerSocketTest extends Thread{ private ServerSocket myServerSocket; private final int PORT = 9999; public static void main(String[] args){ ServerSocketTest sst = new ServerSocketTest(); sst.start(); } public ServerSocketTest(){ // 初始化一个ServeSocket端 try { myServerSocket = new ServerSocket(PORT); } catch (IOException e) { e.printStackTrace(); } } public void run(){ while(true){ System.out.println("我是服务器,我在9999端口监听...."); try { Socket socket = myServerSocket.accept(); DataInputStream din = new DataInputStream(new BufferedInputStream(socket.getInputStream())); String msgIn = din.readUTF(); System.out.println(msgIn.trim()); } catch (IOException e) { e.printStackTrace(); } } } } 说明一点: 在test3()中,因为dout = new DataOutputStream(newBufferedOutputStream(mySocket.getOutputStream()));使用了Buffered,所以在dout.writeUTF()方法后,如果没有使用dout.flush();数据会存在输出缓存中,不会发送出去的。 如果我们队dout的声明是,dout = new DataOutputStream(mySocket.getOutputStream());那么,数据会立即发送出去。(除非,对方没有调用read()来读取数据,且数据量极大,超过了对方的输入缓存。不过,此时dout.writeUTF();这里会堵塞。) 以下是程序运行后,客户端与服务器各自的控制台输出情况: ----------------------------------客户端-------------------------- java.net.SocketException: Socket output is shutdown at java.net.Socket.getOutputStream(Unknown Source) at com.test2.SocketTest.test2(SocketTest.java:66) at com.test2.SocketTest.<init>(SocketTest.java:22) at com.test2.SocketTest.main(SocketTest.java:15) ****2种方式关闭输出流,Socket是否关闭?*** 输出流刚打开,Socket是否关闭?false 使用shutdownOutput关闭输出流,Socket是否关闭?false 使用close关闭输出流,Socket是否关闭?true ****使用shutdownOutputStream后,输出流是否可以重新开启?*** ***输出缓冲区里的数据是丢弃,还是发送?**** ---------------------------------服务器------------------------------ 我是服务器,我在9999端口监听.... 我是服务器,我在9999端口监听.... java.io.EOFException at java.io.DataInputStream.readUnsignedShort(Unknown Source) at java.io.DataInputStream.readUTF(Unknown Source) at java.io.DataInputStream.readUTF(Unknown Source) at com.test1.ServerSocketTest.run(ServerSocketTest.java:37) java.io.EOFException at java.io.DataInputStream.readUnsignedShort(Unknown Source) at java.io.DataInputStream.readUTF(Unknown Source) at java.io.DataInputStream.readUTF(Unknown Source) at com.test1.ServerSocketTest.run(ServerSocketTest.java:37) java.io.EOFException at java.io.DataInputStream.readUnsignedShort(Unknown Source) 我是服务器,我在9999端口监听.... at java.io.DataInputStream.readUTF(Unknown Source) at java.io.DataInputStream.readUTF(Unknown Source) at com.test1.ServerSocketTest.run(ServerSocketTest.java:37)