Android中使用TextView实现文字环绕图片效果实例

 更新时间:2016年9月20日 19:54  点击:2030
本文分享在Android开发中使用TextView实现实现文字和图片混排(文字环绕图片)效果的实例,后面还有一个支持Span折叠的实例。

在平时我们做项目中,或许有要对一张图片或者某一个东西进行文字和图片说明,这时候要求排版美观,所以会出现文字和图片混排的情况,如图:

Android实现文字和图片混排(文字环绕图片)效果


这种情况就是上下两个文字说明是连续在一起的,这就要求我们计算上面的文字说明怎么和下面的文字说明连贯结合在一起呢,这就要求我们进行计算了,下面给出代码,代码中也有详细的注释,原理也很简单。

因为算是比较简单,直接就在activity中去计算了:

package com.example.test;
import android.app.Activity;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.TextView;
public class MainActivity extends Activity {
  boolean imageMeasured = false;
  TextView tv_right;
  TextView tv_bottom;
  static final String text = "叶凡:小说主角,与众老同学在泰山聚会时一同被九龙拉棺带离地球," +
      "进入北斗星域,得知自己是荒古圣叶凡 叶凡体。历险禁地,习得源术,斗圣地世家,战太古生物," +
      "重组天庭,叶凡辗转四方得到许多际遇和挑战,功力激增,眼界也渐渐开阔。一个浩大的仙侠世界," +
      "就以他的视角在读者面前展开。姬紫月:姬家小姐,出场年龄十七岁。被叶凡劫持一同经历青铜古殿历险," +
      "依靠碎裂的神光遁符解除禁制,反过来挟持叶凡一同进入太玄派寻找秘术。" +
      "在叶凡逃离太玄后姬紫月在孔雀王之乱中被华云飞追杀,又与叶凡[2]相遇,被叶凡护送回姬家" +
      ",渐渐对叶凡产生微妙感情。后成为叶凡的妻子,千载后于飞仙星成仙,在叶凡也进入仙路后再见庞博:" +
      "叶凡大学时最好的朋友,壮硕魁伟,直率义气。到达北斗星域后因服用了圣果被灵墟洞天作为仙苗," +
      "在青帝坟墓处为青帝十九代孙附体离去,肉身被锤炼至四极境界。后叶凡与黑皇镇压老妖神识," +
      "庞博重新掌控自己身躯,取得妖帝古经和老妖本体祭炼成的青莲法宝,习得妖帝九斩和天妖八式," +
      "但仍伪装成老妖留在妖族。出关后找上叶凡,多次与他共进退。星空古路开启后由此离开北斗," +
      "被叶凡从妖皇墓中救出,得叶凡授予者字秘、一气化三清,与叶凡同闯试炼古路,一起建设天庭";
  // 屏幕的高度
  int screenWidth = 0;
  // 总共可以放多少个字
  int count = 0;
  // textView全部字符的宽度
  float textTotalWidth = 0.0f;
  // textView一个字的宽度
  float textWidth = 0.0f;
  Paint paint = new Paint();
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv_right = (TextView) findViewById(R.id.test_tv_right);
    tv_bottom = (TextView) findViewById(R.id.test_tv_bottom);
    final ImageView imageView = (ImageView) findViewById(R.id.test_image);
    imageView.setImageResource(R.drawable.ee);
    screenWidth = getWindowManager().getDefaultDisplay().getWidth();
    /**
     * 获取一个字的宽度
     */
    textWidth = tv_right.getTextSize();
    paint.setTextSize(textWidth);
    /**
     * 因为图片一开始的时候,高度是测量不出来的,通过增加一个监听器,即可获取其图片的高度和长度
     */
    ViewTreeObserver vto = imageView.getViewTreeObserver();
    vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
      public boolean onPreDraw() {
        if (!imageMeasured) {
          imageMeasured = true;
          int height = imageView.getMeasuredHeight();
          int width = imageView.getMeasuredWidth();
          drawImageViewDone(width, height);
        }
        return imageMeasured;
      }
    });
  }
  private void drawImageViewDone(int width, int height) {
    // 一行字体的高度
    int lineHeight = tv_right.getLineHeight();
    // 可以放多少行
    int lineCount = (int) Math.ceil((double) height / (double) lineHeight);
    // 一行的宽度
    float rowWidth = screenWidth - width - tv_right.getPaddingLeft() - tv_right.getPaddingRight();
    // 一行可以放多少个字
    int columnCount = (int) (rowWidth / textWidth);
    // 总共字体数等于 行数*每行个数
    count = lineCount * columnCount;
    // 一个TextView中所有字符串的宽度和(字体数*每个字的宽度)
    textTotalWidth = (float) ((float) count * textWidth);
    measureText();
    tv_right.setText(text.substring(0, count));
    // 检查行数是否大于设定的行数,如果大于的话,就每次减少一个字符,重新计算行数与设定的一致
    while (tv_right.getLineCount() > lineCount) {
      count -= 1;
      tv_right.setText(text.substring(0, count));
    }
    tv_bottom.setPadding(0, lineCount * lineHeight - height, 0, 0);
    tv_bottom.setText(text.substring(count));
  }
  /**
   * 测量已经填充的长度,计算其剩下的长度
   */
  private void measureText() {
    String string = text.substring(0, count);
    float size = paint.measureText(string);
    int remainCount = (int) ((textTotalWidth - size) / textWidth);
    if (remainCount > 0) {
      count += remainCount;
      measureText();
    }
  }
}

其中xml文件布局如下:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical" >
  <RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >
    <ImageView
      android:id="@+id/test_image"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:scaleType="fitXY" />
    <TextView
      android:id="@+id/test_tv_right"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_toRightOf="@id/test_image"
      android:gravity="fill_horizontal"
      android:paddingLeft="7dp"
      android:textSize="16sp" />
    <TextView
      android:id="@+id/test_tv_bottom"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_below="@id/test_image"
      android:gravity="fill_horizontal"
      android:textSize="16sp" />
  </RelativeLayout>
</ScrollView>

代码很少,原理也很简单,后来发现这种做法在大部分手机运行是完美的,但是少部分手机还是有点问题。是什么问题呢,是在我们测量textView的长度的是,因为是我们刚刚进行setText,然后马上进行测量,这样得到的结果是不正确的,所以大家可以优化一下。温馨提示,当我们setText之后,可以延时一些时间再去测量,这样获取的值就是挣钱的了,当然那个延迟的时间很短50毫秒就可以了,因为我们要相信textView的绘制速度还是很快的。



Android 文字环绕 图文混排 支持Span折叠

先直接上效果图


http://s5.sinaimg.cn/middle/47021dd4gb63d6d54ffa4&690


Android <wbr>文字环绕 <wbr>图文混排 <wbr>支持Span折叠


上图为实现目标,实现了Android图文混排,文字环绕,支持Span的识别,表情的嵌入,支持文字字体大小的设置等。

 


由于项目中需要用到图文混排技术,在此稍微研究了两天,出来一个效果还算不错的东西

图文混排技术,在不少Android应用中都已经实现,说穿了其实就是两个TextView加一个ImageView的布局罢了,代码里面实现下String的剪切就可以了,不过我这里的这个除了要实现混排效果外,还要支持Span,支持表情等,这就有点麻烦了。下面慢慢分解。先贴出RichTextImageView的布局。
    
<?xml version="1.0" encoding="utf-8"?>
<!-- com.demonzym.richtextdemo.RichTextImageView -->
<com.demonzym.richtextdemo.RichTextImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:id="@+id/richview" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
       
        <RelativeLayout
            android:id="@+id/layout_preimage_isgif_left"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:visibility="gone" >

            <ImageView
                android:id="@+id/preimage_statues_left"
                android:layout_width="134dip"
                android:layout_height="134dip"
                android:background="@drawable/preview_back"
                android:cropToPadding="true"
                android:scaleType="centerCrop"
                android:src="@drawable/preview_back_small" />

            <ImageView
                android:id="@+id/preimage_isgif_left"
                android:layout_width="24dip"
                android:layout_height="17dip"
                android:layout_alignBottom="@+id/preimage_statues"
                android:layout_alignRight="@+id/preimage_statues"
                android:layout_marginBottom="9dip"
                android:layout_marginRight="7dip" />
        </RelativeLayout>

        <com.demonzym.richtextdemo.RichTextView
            android:id="@+id/lefttext"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:layout_weight="1"/>

        <RelativeLayout
            android:id="@+id/layout_preimage_isgif_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:visibility="gone" >

            <ImageView
                android:id="@+id/preimage_statues_right"
                android:layout_width="134dip"
                android:layout_height="134dip"
                android:background="@drawable/preview_back"
                android:cropToPadding="true"
                android:scaleType="centerCrop"
                android:src="@drawable/preview_back_small" />

            <ImageView
                android:id="@+id/preimage_isgif_right"
                android:layout_width="24dip"
                android:layout_height="17dip"
                android:layout_alignBottom="@+id/preimage_statues"
                android:layout_alignRight="@+id/preimage_statues"
                android:layout_marginBottom="9dip"
                android:layout_marginRight="7dip" />
        </RelativeLayout>
    </LinearLayout>

    <com.demonzym.richtextdemo.RichTextView
        android:id="@+id/bottomtext"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>

</com.demonzym.richtextdemo.RichTextImageView>

 

布局中的RichTextView是另外封装的一个实现Span的TextView,就是实现效果图中表情啊,@啊,#话题# 之类的,了解Span的都懂的,就不细说了。读者研究这个图文混排的时候,可以直接用TextView替代。

 

RichTextImageView  即本文中的图文混排的View。继承于LinearLayout,该类实现的关键代码如下:

 

public void setText(String t) {
  mTopText.setText("");
  mBottomText.setText("");
  mTopText.setVisibility(View.INVISIBLE);
  mBottomText.setVisibility(View.GONE);
  bRequestBottomLayout = true;

  if (t == null)
   t = "";

//  Log.e("setText", t);
 
  mTextContent = t;

  mTopText.setText(mTextContent);

  requestLayout();
 
 }

 


public void setImage(int id) {
  mImageContent = id;
  mPreviewImage.setImageResource(id);
  mPreview.setVisibility(View.VISIBLE);

  if (!Util.isStringEmpty(mTextContent))
   setText(mTextContent);
 }

 

private void iniViews() {
  mLinearLayout = (LinearLayout) findViewById(R.id.linearLayout1);
  mTopText = (RichTextView) findViewById(R.id.lefttext);
  mBottomText = (RichTextView) findViewById(R.id.bottomtext);

  if(IMAGE_LOCATION == IMAGE_LOCATION_RIGHT){
   mPreview = (RelativeLayout) findViewById(R.id.layout_preimage_isgif_right);
   mPreviewImage = (ImageView) findViewById(R.id.preimage_statues_right);
   mPreviewImageIsGif = (ImageView) findViewById(R.id.preimage_isgif_right);
  }else if(IMAGE_LOCATION == IMAGE_LOCATION_LEFT){
   mPreview = (RelativeLayout) findViewById(R.id.layout_preimage_isgif_left);
   mPreviewImage = (ImageView) findViewById(R.id.preimage_statues_left);
   mPreviewImageIsGif = (ImageView) findViewById(R.id.preimage_isgif_left);   
  }
 }
 


 public void setImageLocation(int l){
  if(l != IMAGE_LOCATION_RIGHT && l != IMAGE_LOCATION_LEFT)
   return;
  else{
   IMAGE_LOCATION = l;
   mPreview.setVisibility(View.GONE);
   iniViews();
   setImage(mImageContent);
  }
 }

 


@Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
  // TODO Auto-generated method stub
  super.onLayout(changed, l, t, r, b);

  if (Util.isStringEmpty(mTextContent))
   return;

//  Log.e("top text 宽 高",
//    mTopText.getWidth() + " " + mTopText.getHeight());
//  Log.e("top text 单行高度", "" + mTopText.getLineHeight());
 
//  Rect rect = getStringRect(mTextContent, mTopText.getPaint());
//  Log.e("文本的宽 高", rect.width() + " " + rect.height());
 
  // toptext最多能显示的行数
  int topTextMaxRows = mTopText.getHeight() / mTopText.getLineHeight();
    
  // 显示这些内容真实占用的行数
  int textRows = mTopText.getLineCount();
//  Log.e("top text最多能显示的行数", "" + topTextMaxRows);
//  Log.e("当前文本实际占用的行数", "" + textRows);
 
  //由于文本可能带有表情,重新计算显示行数,以保证显示正确
  Rect lineR = new Rect();
  int realH = 0;     //toptext真实高度
  int i = 0;
  for(i = 0; i < topTextMaxRows; i++){
   try{
    mTopText.getLineBounds(i, lineR);
   }catch(IndexOutOfBoundsException e){
    break;
   }
   realH += lineR.height();
   if(realH >= mTopText.getHeight())
    break;
  }
//  Log.e("当前view实际能显示的行数为", "" + i);
  topTextMaxRows = i;
 
  //如果toptext显示不下的话,显示到bottomtext里面去
  if (textRows >= topTextMaxRows && bRequestBottomLayout) {
   // toptext最后一个可见字符的位置
   int lastindex = mTopText.getLayout().getLineVisibleEnd(
     topTextMaxRows - 1);

   
   
   ClickableSpan[] cs = mTopText.getSpans();
   int spanstart = 0;
   int spanend = 0;
   spanstring = "";
   STATE = 0; // 1网页 2人名 3话题
   for (ClickableSpan c : cs) {
    spanstart = mTopText.getSpanStart(c);
    spanend = mTopText.getSpanEnd(c);
    if (spanstart <= lastindex && spanend > lastindex) {
     if (c instanceof LinkClickableSpan) {
//      Log.e("转角span类型", "网页");
      spanstring = ((LinkClickableSpan) c).getLink();
      STATE = 1;
     }
     if (c instanceof NameClickableSpan) {
//      Log.e("转角span类型", "人名");
      spanstring = ((NameClickableSpan) c).getName();
      STATE = 2;
     }
     if (c instanceof TopicClickableSpan) {
//      Log.e("转角span类型", "话题");
      spanstring = ((TopicClickableSpan) c).getTopic();
      STATE = 3;
     }
     break;
    }
   }
   

   mTopText.setText(mTextContent.substring(0, lastindex));
   mTopText.setVisibility(View.VISIBLE);
   // 当行数不是整数时,调整内容会有下面半行没遮住,所以干脆都处理完以后再显示出来
   mBottomText.setText(mTextContent.substring(lastindex,
     mTextContent.length()) + mBottomText.getText().toString());
   if (mBottomText.getText().length() > 0 && bRequestBottomLayout){
    mBottomText.setVisibility(View.VISIBLE);
    mHandler.post(new Runnable() {
     
     @Override
     public void run() {
      // TODO Auto-generated method stub
      mBottomText.requestLayout();
      bRequestBottomLayout = false;
     }
    });
   }

   

   if (STATE != 0 || !bRequestBottomLayout) {

    Log.e("spanstring", spanstring);

//    Log.e("", "移除转角span");
    mTopText.getSpannable().removeSpan(mTopText.getSpans()[mTopText.getSpans().length - 1]);

    switch (STATE) {
    case 1:
     mTopText.getSpannable().setSpan(
       mTopText.new LinkClickableSpan(spanstring),
       spanstart,
       lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
     mBottomText.getSpannable().setSpan(
       mBottomText.new LinkClickableSpan(spanstring),
       0,
       spanend - lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//     Log.e("", "网页");
     break;
    case 2:
     mTopText.getSpannable().setSpan(
       mTopText.new NameClickableSpan(spanstring),
       spanstart,
       lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
     mBottomText.getSpannable().setSpan(
       mBottomText.new NameClickableSpan(spanstring),
       0,
       spanend - lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//     Log.e("", "人名");
     break;
    case 3:
     mTopText.getSpannable().setSpan(
       mTopText.new TopicClickableSpan(spanstring),
       spanstart,
       lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
     mBottomText.getSpannable().setSpan(
       mBottomText.new TopicClickableSpan(spanstring),
       0,
       spanend - lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//     Log.e("", "话题");
     break;
    }
    
    mTopText.setText(mTopText.getSpannable(), BufferType.SPANNABLE);
    mBottomText.setText(mBottomText.getSpannable(), BufferType.SPANNABLE);
   }
   

  }
 
 }

 

说明下难点:

第一,转角处可能存在Span,比如一个话题 #话题话题话题话题话题话题话题话题话题#,可能一半内容在mTopText里面的最后一行显示,而另一半显示不下了,这就需要剪切剩余的String显示到mBottomText里面去,可是Span的内容就会出错,这就需要重新设置下转角处的Span效果了

第二,由于存在图片Span,会导致行高不正确,所以需要在获取到mTopText能显示的最多行数以后,重新判断一次是否正确,如果不正确的话,需要重新调整,不然mTopText会有一部分内容显示不下。


该View实现完成后,在布局中使用include条用,代码中find出来就可以直接使用了


不考虑Span的话,直接将代码中转角处理Span部分删除即可使用,我注释的挺清楚了

在Android开发,性能是一个大问题,弄不好会使内存崩溃。本文我们分享总结的一些 Android 性能调优的技术点,Android如何做内存性能优化。

Android性能调优涉及到多方面的工作,因本人技术水平有限,目前只总结了以下部分,希望大家继续补充。

要点

使用异步

    保持 APP 的高度响应,不要在 UI 线程做耗时操作,多使用异步任务
    使用线程时要做好线程控制;使用队列、线程池
    谨慎使用糟糕的 AysncTask 、 Timer
    警惕异步任务引起的内存泄露
    应该异步任务分类,比如 HTTP ,图片下载,文件读写,每一类的异步任务维护一个任务队列,而不是每一个任务都开一个线程( Volley 表示我一个可以搞定这些全部 _(:з」∠)_)
    这些常用的任务应该做好优先级处理(一般 JSON 数据优先于图片等静态数据的请求)
    一般异步任务应该开启一个 SingleAsyncTask ,保证一时只有一个线程在工作
    HTTP 和图片下载尽量使用同一套网络请求
    使用 MVP 模式规范大型 Activity 类的行为,避免异步任务造成的内存泄露

避免内存泄露

    了解虚拟机内存回收机制
    频繁 GC 也会造成卡顿,避免不必要的内存开销
    错误的引用姿♂势造成的内存泄露(啊~要泄了~)
    常见的 Activity 泄露(单例、 Application 、后台线程、无限动画、静态引用)
    Bitmap 泄露( HoneyComb 这个问题之前压力好大)
    尽量使用 IntentService 代替 Service ,前者会自动 StopItself
    排查内存泄露问题的方法(我一直以来都是简单暴力的人肉 dump 检查大法)
    使用 LeakCanary 自动检查 Activity 泄露问题
    对内存负载要保持敏感( Sharp )

视图优化

    布局优化、减少层次, Include Merge
    使用 ViewStub 避免不必要的 LayoutInflate ,使用 GONE 代替重复 LayoutInflate 同一个布局
    避免过度绘制,应该减少不必要的布局背景;布局层次太深会造成过度绘制以及 Measure 、 Layout 等方法时间复杂度的指数增长
    使用过渡动画,比如给图片的呈现加一个轻量的淡入效果会让视觉上变得流畅许多
    避免过度的动画,不要让一个界面同时出现多出动画,比如 List 滚动时 Item 项要停止播放动画或者 GIF
    复杂动画使用 SurfaceView 或 TextureView
    尽量提供多套分辨率的图片,使用矢量图

Adapter 优化

    复用 convertView ,用 ViewHolder 代替频繁 findViewById
    不要重复 setListener ,要使用 v.getId 来复用 Listener ,不然会创建一堆 Listener 导致频繁 GC
    多布局要采用 MutilItemView ,而不是使用一个大布局然后动态控制需要现实的部分
    不要在 getView 方法做做耗时的操作
    快速滚动列表的时候,可以停止加载列表项的图片,停止列表项的动画,不要在这时候改变列表项的布局
    尽量用 RecyclerView (增量 Notify 和 RecycledViewPool 带你飞)

代码优化

    算法优化,减少时间复杂度,参考一些经典的优化算法
    尽量使用 int ,而不是 float 或者 double
    尽量采用基本类型,避免无必要的自动装箱和拆箱,浪费时间和空间
    选用合适的集合类(尽量以空间换时间)、选用 Android 家的 SparseArray,SparseBooleanArray 和 LongSparseArray
    避免创建额外的对象( StringBuilder )
    使用 SO 库完成一些比较独立的功能(高斯模糊)
    预处理(提前操作)一些比较耗时的初始化工作统一放到启动图处理
    懒加载(延迟处理)规避 Activity 的敏感生命周期
    Log 工具类,要在编译时删掉调试代码,而不是在运行时通过判断条件规避
    优先使用静态方法、公有方法还是公有方法?速度区别很大哦
    类内部直接对成员变量进行操作,不要使用 getter/setter 方法,调用方法耗额外的时间
    给内部类访问的外部类成员变量要声明称包内可访问,而不是私有,不然编译的时候还是会自动创建用于访问外部类成员变量的方法
    遍历集合时,使用 i++代替 Iterator ,后者需要额外的对象操作,应在循环体内避免这种情况
    如果一个基本类型或者 String 的值不会改变,尽量用 final static ,编译时会直接用变量的值替换变量,也就不需要在查询变量的值了

其他优化

    数据库优化:使用索引、使用异步线程
    网络优化 …… 一堆优秀的轮子
    避免过度使用依赖注入框架,大量的反射
    不过过度设计 /抽象,多态看起来很有设计感,代价就是额外的代码、空间、时间
    尽量不要开启多进程,进程的开销很大

APK 瘦身

    开启混淆
    使用 zipalign 工具优化 APK
    适当有损图片压缩、使用矢量图
    删除项目中冗余的资源,之前写过一些删除没有 res 资源的脚本
    动态加载模块化,项目拆分啊!

性能问题的排查方法

    GPU 条形图,没事开来看看淘宝
    过度绘制颜色,嗯,不要一篇姨妈红就好
    LeakCanary ,自动检测 Activity 泄露,挺好用的
    TraceView ( Device Monitor ), Systrace ,分析哪些代码占用的 CPU 时间太大,屡试不爽
    Lint ,检查不合理的 res 资源
    layoutopt (还是 optlayout ?),对当前布局提出优化建议,已被 lint 替代,但是还能用
    HierarchyViewer ,查看手机当前界面的布局层次,布局优化时常用(只用于模拟器,真机上用要 ROOT ,不想 ROOT 加得使用 ViewServer )
    StrictMode , UI 操作、网络操作等容易出现性能问题的地方,如果出现异常情况 StrictMode 会报警


Android内存性能优化


刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成。其实Java中没有指针的概念,但是指针的使用方式依然存在,一味的依赖系统的gc,很容易就造成了内存的浪费。


Java基于垃圾回收的内存机制


Java的内存管理机制会自动回收无用对象所占用的内存,减轻手工管理内存的负担

1、C/C++: 从申请、使用、释放都需要手工管理

2、Java:无用的对象的内存会被自动回收


\

什么样的对象是无用的对象

1、Java通过引用来操作一个具体的对象,引用类似于C 中的指针。一个对象可以持有其他对象的引用。

2、从一组根对象(GC Roots)开始,按对象之前的引用关系遍历所有对象,在遍历过程中标记所有的可达对象。如果一个对象由根对象出发不可达,则将它作为垃圾收集。

GCRoot 都有哪些?

1、 Class:由系统的类加载器加载的类对象

2、 Static Fields

3、 Thread:活着的线程

4、 Stack Local: java方法的局部变量或参数

5、 JNI Local: JNI方法中的局部引用

6、 JNI Global: 全局的JNI引用

7、 Monitor used: 用于同步的监控对象

8、Help by VM: 用于JVM特殊目的由GC保留的对象


\


\


\

Java程序中的内存泄漏

对象的内存在分配之后无法通过程序的执行逻辑释放对该对象的引用,不能被回收该对象所占内存

内存泄漏的危害

1、 引起OutOfMemoryError

2、 内存占用高时JVM虚拟机会频繁触发GC, 影响程序响应速度

3、内存占用大的程序容易被各种清理优化程序中止,用户也更倾向于卸载这些程序

Android应用的开发语言为Java,每个应用最大可使用的堆内存受到Android系统的限制

Android每一个应用的堆内存大小有限

1、 通常的情况为16M-48M

2、 通过ActivityManager的getMemoryClass()来查询可用堆内存限制

3、3.0(HoneyComb)以上的版本可以通过largeHeap=“true”来申请更多的堆内存

Nexus S(4.2.1):normal 192, largeHeap 512

4、如果试图申请的内存大于当前余下的堆内存就会引发OutOfMemoryError()

5、应用程序由于各方面的限制,需要注意减少内存占用,避免出现内存泄漏。

用MAT工具来检测内存泄漏

在试图窗口中新建一个Memory Analysis会出现一个


\

没有的可以去http://www.eclipse.org/mat/downloads.php安装一下MAT

在Android 的调试环境DDMS下,找到Heap dump


\


\

Dump下当前内存中的镜像文件,*****.hprof


\

能清楚的看到每一个部分暂用的内存大小。

也可以切换试图,group查看不同包不同类的占用细节。


\

Heap dump

? 包含了触发Heap dump生成的时刻Java进程的内存快照,主要内容为各个Java类和对象在堆内存中的分配情况

Memory Analyzer Tool (MAT)


常见内存泄露原因

Context对象泄漏

1、如果一个类持有Context对象的强引用,就需要检查其生存周期是否比Context对象更长。否则就可能发生Context泄漏。

2、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

例如View#setTag(int, Object)的内存泄漏https://code.google.com/p/android/issues/detail?id=18273

3、把Context对象赋给static变量。

避免Context对象泄漏Checklist

1、检查所有持有对Context对象强引用的对象的生命周期是否超出其所持有的Context对象的生命周期。

2、检查有没有把View传出到View所在Context之外的地方,如果有的话就需要检查生命周期。

3、工具类中最好不要有Context成员变量,尽量在调用函数时直接通过调用参数传入。如果必须有Context成员变量时,可以考虑使用WeakReference来引用Context对象。

4、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

5、 检查把Context或者View对象赋给static变量的地方,看是否有Context泄漏。

6、检查所有把View放入容器类的地方(特别是static容器类),看是否有内存泄漏。7、使用WeakHashMap也需要注意有没有value-key的引用。

7、尽量使用ApplicationContext。

Handler对象泄漏

1、发送到Handler的Message实际上是加入到了主线程的消息队列等待处理,每一个Message持有其目标Handler的强引用。

如我们通常使用的匿名内部类Handler


HandlermHandler = new Handler() {
    @Override
    public voidhandleMessage(Message msg) {
       mImageView.setImageBitmap(mBitmap);
    }
}


上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用,因为View会依附着一个Activity。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给 Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条 Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

当然,应为是Handler对外部持有引用的原因,我们就可以将Activity设置为一个弱引用,在不必要的时候,不再执行内部方法。



/**
 * @author zhoushengtao
 * @since 2013-12-16 下午3:25:36
 */
 
import android.app.Activity;
importandroid.content.Context;
importandroid.os.Handler;
importandroid.os.Message;
 
importjava.lang.ref.WeakReference;
 
publicclass WeakRefHandler extends Handler
{
    WeakReference<context> mWeakContext;
 
    public WeakRefHandler(Context context)
    {
        mWeakContext = newWeakReference<context>(context);
    }
 
    @Override
    public void handleMessage(Message msg)
    {
        if((mWeakContext.get() instanceofActivity )&& ((Activity)mWeakContext.get()).isFinishing())
                return ;
        if(mWeakContext==null){
            return ;
        }
        super.handleMessage(msg);
    }
}


2、Non-staticinner class 和anonymous class持有其outer class的引用。


Drawable.Callback引起的内存泄漏

Drawable对象持有Drawable.callback的引用。当把一个Drawable对象设置到一个View时,Drawable对象会持有该View的引用作为Drawable.Callback


\

避免Drawable.Callback引起内存泄漏

? 尽量不要在static成员中保存Drawable对象

? 对于需要保存的Drawable对象, 在需要时调用Drawable#setCallback(null).


\

其他内存泄漏<??#65533;"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjwvcD4KPHAgYWxpZ249"left"> 1、Android DigitalClock引起的内存泄漏http://code.google.com/p/android/issues/detail?id=17015

2、使用Map容器类时,作为Key 的类没有正确的实现hashCode和equal函数

其他内存泄漏

? JNI程序中的内存泄漏

1、 Malloc/free。

2、 JNI Global reference

? Thread-Local Variable

1、 相当于Thread对象的成员变量, 可以存储线程相关的状态

2、 如果thread是alive状态,那么Thread-Local中的对象就无法被GC。

进程内存占用监测工具

Dumpsys

? $ dumpsys meminfo [pid]


\

Procrank + Shell脚本

? #procrank

1、 VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)

2、 RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)

3、 PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)

4、 USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)


\

Shell脚本

#!/bin/bash

while true; do

adbshell procrank " grep "com.qihoo360.mobilesafe"

sleep1

done


当然,部分机型的sh都是经过第三方手机商精简过的,很多命令都用不了。Procrank,就是一个经常被精简掉的命令。

鉴于此:

自己写了一个小工具,检测内存的实时变化,

Github地址:https://github.com/stchou/JasonTest


\


\

小结

1. 保存对象前要三思

I. 对象本身有无隐含的引用

II. 保存后何时能够回收

2. 要了解常见的隐含引用

I. anonymous class outer class

II. View to context

3. 要通过各种工具检查内存占用是否有异常

4. 创建大对象时,要检查它的生命周期


下面我们一起来看一个关于SpannableString与SpannableStringBuilder使用例子了,希望这个例子能够对各位同学带来有效帮助的哦。

1、SpannableString、SpannableStringBuilder与String的关系

首先SpannableString、SpannableStringBuilder基本上与String差不多,也是用来存储字符串,但它们俩的特殊就在于有一个SetSpan()函数,能给这些存储的String添加各种格式或者称样式(Span),将原来的String以不同的样式显示出来,比如在原来String上加下划线、加背景色、改变字体颜色、用图片把指定的文字给替换掉,等等。所以,总而言之,SpannableString、SpannableStringBuilder与String一样, 首先也是传字符串,但SpannableString、SpannableStringBuilder可以对这些字符串添加额外的样式信息,但String则不行。

注意:如果这些额外信息能被所用的方式支持,比如将SpannableString传给TextView;也有对这些额外信息不支持的,比如前一章讲到的Canvas绘制文字,对于不支持的情况,SpannableString和SpannableStringBuilder就是退化为String类型,直接显示原来的String字符串,而不会再显示这些附加的额外信息。

2、SpannableString与SpannableStringBuilder区别

它们的区别在于 SpannableString像一个String一样,构造对象的时候传入一个String,之后再无法更改String的内容,也无法拼接多个 SpannableString;而SpannableStringBuilder则更像是StringBuilder,它可以通过其append()方法来拼接多个String;

3、SetSpan()

void setSpan (Object what, int start, int end, int flags)

函数意义:给SpannableString或SpannableStringBuilder特定范围的字符串设定Span样式,可以设置多个(比如同时加上下划线和删除线等),Falg参数标识了当在所标记范围前和标记范围后紧贴着插入新字符时的动作,即是否对新插入的字符应用同样的样式。

参数说明:

object what :对应的各种Span,后面会提到;
int start:开始应用指定Span的位置,索引从0开始
int end:结束应用指定Span的位置,特效并不包括这个位置。比如如果这里数为3(即第4个字符),第4个字符不会有任何特效。从下面的例子也可以看出来。
int flags:取值有如下四个
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式
Spannable.SPAN_EXCLUSIVE_INCLUSIVE :前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式
Spannable.SPAN_INCLUSIVE_EXCLUSIVE :前面包括,后面不包括。
Spannable.SPAN_INCLUSIVE_INCLUSIVE :前后都包括。

下面写了个小demo,先看一下效果图:

代码如下所示:

 代码如下 复制代码

public class MainActivity extends Activity implements OnClickListener {
    private TextView tv;
    private Button underline_btn;
    private Button strike_btn;
    private Button style_btn;
    private Button font_btn;
    private Button color_btn1;
    private Button color_btn2;
    private Button url_btn;
    private Button image_btn;
    private Button maskfilte_btn;
    private Button Rasterizer_btn;
    private Button spannablestringbuilder;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) this.findViewById(R.id.tv);
        underline_btn = (Button) this.findViewById(R.id.underline_btn);
        strike_btn = (Button) this.findViewById(R.id.strike_btn);
        style_btn = (Button) this.findViewById(R.id.style_btn);
        font_btn = (Button) this.findViewById(R.id.font_btn);
        color_btn1 = (Button) this.findViewById(R.id.color_btn1);
        color_btn2 = (Button) this.findViewById(R.id.color_btn2);
        url_btn = (Button) this.findViewById(R.id.url_btn);
        image_btn = (Button) this.findViewById(R.id.image_btn);
        maskfilte_btn = (Button) this.findViewById(R.id.maskfilte_btn);
        Rasterizer_btn = (Button) this.findViewById(R.id.Rasterizer_btn);
        spannablestringbuilder = (Button) this.findViewById(R.id.spannablestringbuilder);
 
        underline_btn.setOnClickListener(this);
        strike_btn.setOnClickListener(this);
        style_btn.setOnClickListener(this);
        font_btn.setOnClickListener(this);
        color_btn1.setOnClickListener(this);
        color_btn2.setOnClickListener(this);
        url_btn.setOnClickListener(this);
        image_btn.setOnClickListener(this);
        maskfilte_btn.setOnClickListener(this);
        Rasterizer_btn.setOnClickListener(this);
        spannablestringbuilder.setOnClickListener(this);
 
    }
 
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.underline_btn:
            addUnderLineSpan();
            break;
        case R.id.strike_btn:
            addStrikeSpan();
            break;
        case R.id.style_btn:
            addStyleSpan();
            break;
        case R.id.font_btn:
            addFontSpan();
            break;
        case R.id.color_btn1:
            addForeColorSpan();
            break;
        case R.id.color_btn2:
            addBackColorSpan();
            break;
        case R.id.url_btn:
            addUrlSpan();
            break;
        case R.id.image_btn:
            addImageSpan();
            break;
        case R.id.maskfilte_btn:
            addmaskfilteSpan();
            break;
        case R.id.Rasterizer_btn:
            addRasterizerSpan();
            break;
        case R.id.spannablestringbuilder:
            addspannablestringbuilderSpan();
            break;
        }
    }
    /**
     * spannablestringbuilder
     */
    private void addspannablestringbuilderSpan() {
        SpannableStringBuilder ss=new SpannableStringBuilder("红色超链接斜体删除线绿色下划线图片:.");
           //用颜色标记文本
        ss.setSpan(new ForegroundColorSpan(Color.RED), 0, 2,
                //setSpan时需要指定的 flag,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE(前后都不包括).
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用超链接标记文本
        ss.setSpan(new URLSpan("tel:4155551212"), 2, 5,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用样式标记文本(斜体)
        ss.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 5, 7,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用删除线标记文本
        ss.setSpan(new StrikethroughSpan(), 7, 10,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用下划线标记文本
        ss.setSpan(new UnderlineSpan(), 10, 16,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用颜色标记
        ss.setSpan(new ForegroundColorSpan(Color.GREEN), 10, 12,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //获取Drawable资源
        Drawable d = getResources().getDrawable(R.drawable.ic_launcher);
        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        //创建ImageSpan
        ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
        //用ImageSpan替换文本
        ss.setSpan(span, 18, 19, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.setText(ss);
        tv.setMovementMethod(LinkMovementMethod.getInstance()); //实现文本的滚动
    }
 
    /*
     * Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式
     * Spannable.SPAN_EXCLUSIVE_INCLUSIVE :前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式
     * Spannable.SPAN_INCLUSIVE_EXCLUSIVE :前面包括,后面不包括。
     * Spannable.SPAN_INCLUSIVE_INCLUSIVE :前后都包括。
     */
    /**
     * 光栅效果
     */
    private void addRasterizerSpan() {
        SpannableString spanText = new SpannableString("StrikethroughSpan");
        spanText.setSpan(new StrikethroughSpan(), 0, 7, Spannable.
        SPAN_INCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanText);
        
    }
    /**
     * 修饰效果
     */
    private void addmaskfilteSpan() {
        SpannableString spanText = new SpannableString("benzlocke6666666");
        int length = spanText.length();
        //模糊(BlurMaskFilter)
        MaskFilterSpan maskFilterSpan = new MaskFilterSpan(new BlurMaskFilter(3, Blur.OUTER));
        spanText.setSpan(maskFilterSpan, 0, length - 10, Spannable.
        SPAN_INCLUSIVE_EXCLUSIVE);
        //浮雕(EmbossMaskFilter)
        maskFilterSpan = new MaskFilterSpan(new EmbossMaskFilter(new float[]{1,1,3}, 1.5f, 8, 3));
        spanText.setSpan(maskFilterSpan, length - 10, length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanText);
        
    }
 
    /**
     * 超链接
     */
    private void addUrlSpan() {
        SpannableString spanString = new SpannableString("超链接");
        URLSpan span = new URLSpan("tel:0123456789");
        spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
        
        tv.setMovementMethod(LinkMovementMethod.getInstance());
    }
 
    /**
     * 文字背景颜色
     */
    private void addBackColorSpan() {
        SpannableString spanString = new SpannableString("文字背景颜色");
        BackgroundColorSpan span = new BackgroundColorSpan(Color.YELLOW);
        spanString.setSpan(span, 0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 文字颜色
     */
    private void addForeColorSpan() {
        SpannableString spanString = new SpannableString("文字颜色");
        ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);
        spanString.setSpan(span, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 字体大小
     */
    private void addFontSpan() {
        SpannableString spanString = new SpannableString("36号字体");
        AbsoluteSizeSpan span = new AbsoluteSizeSpan(36);
        spanString.setSpan(span, 0, 5, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 粗体,斜体
     */
    private void addStyleSpan() {
        SpannableString spanString = new SpannableString("ABCDEF");
        StyleSpan span = new StyleSpan(Typeface.BOLD_ITALIC);
        spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 删除线
     */
    private void addStrikeSpan() {
        SpannableString spanString = new SpannableString("删除线");
        StrikethroughSpan span = new StrikethroughSpan();
        spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 下划线
     */
    private void addUnderLineSpan() {
        SpannableString spanString = new SpannableString("下划线");
        UnderlineSpan span = new UnderlineSpan();
        spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 图片
     */
    private void addImageSpan() {
        SpannableString spanString = new SpannableString(" ");
        Drawable d = getResources().getDrawable(R.drawable.ic_launcher);
        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
        spanString.setSpan(span, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
}

下文来为各位介绍一篇关于Fiddler 对 Android手机进行抓包的例子,希望这篇文章能够对各位同学带来帮助,具体如下所示。

Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的“进出”Fiddler的数据(指cookie,html,js,css等文件,这些都可以让你胡乱修改的意思)。 Fiddler 要比其他的网络调试器要更加简单,因为它不仅仅暴露http通讯还提供了一个用户友好的格式。

我们在电脑上进行网络访问请求调试的时候通常在浏览器中可以使用F12,但是如果要看手机访问时进行了哪些请求那就不那么简单了,而使用 Fiddler 则可以很方便的看到数据请求与响应。

下面介绍 Fiddler 的安装与使用。

前提:电脑和手机保持在同一局域网内

1. 安装Fiddler


安装程序:

2. 安装完成后启动软件,依次点击 Tools --> Fiddler Options...  如下图:

3. 点击 Connections , 将 Allow remote computers connect 勾选上,监听端口为8888,如图:

4.依次点击关闭后,重启软件。

5. 查看电脑的IP,我这里是 192.168.1.4

6. 在手机上,将手机代理设置为电脑上设置 192.168.1.4  和 8888   如下图:

7. 如下图,已经开始工作了:

ndbase是为Android开发者量身打造的一款开源类库产品,本文我们介绍使用AndBase框架实现无参Http Get请求,有参Http Post请求,有参Http Get请求。

AndBase框架为我们提供了一些相关的方法提供给我们使用,用来完成Http网络请求...总体就是对Http请求的一个封装,不过个人认为,网络请求这一模块更加推荐使用Volley框架..楼主对比了两个框架中的源码...Volley更多的地方是使用抽象方法封装在接口内,然后对外暴露接口,其他类在实现接口的同时需要实现内部的抽象方法...而AndBase则是使用继承的方式..继承父类..实现类通过重写的方式对封装的方法重写从而进行下一步的操作...

相比二者网络请求的源码,Volley源码的书写还是更胜一筹...Volley是Google推出的,针对的也仅仅是网络请求这一模块...同样AndBase也是非常优秀的,是国内牛人写的一款重量级框架,涉及的模块非常的广泛,还是非常好用的...

1.使用AndBase框架实现无参Http Get请求...

一般普通的网络请求如果不涉及到数据信息的变化,也就是不涉及一些安全性问题,都可以使用Get方式来完成网络请求...Get请求也是分为有参和无参的,给我的感觉有参一般可以用于向服务器上传资源数据...先介绍一下无参的Get请求...还是先从源码的地方来看看...

源码的调用方式是先使用AbHttpUtils.get()函数调用...不过这无关紧要...通过这个方法走向AbHttpClient类内部...执行下面这段源码...无论是有参还是无参..都会调用这个方法..无参的时候第二个参数传递null就行了...


public void get(final String url,final AbRequestParams params,final AbHttpResponseListener responseListener) {
        
        responseListener.setHandler(new ResponderHandler(responseListener));
        executorService.submit(new Runnable() {
            public void run() {
                try {
                    doGet(url,params,responseListener);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }                 
        });                
    }



我们可以看到,这段函数首先通过Handler发送Message...同时开启一个线程池,来提交当前的请求...最后将执行doGet()方法...同时Handler一直对responseListener的消息进行处理..doGet()方法的源码过程如下...


private void doGet(String url,AbRequestParams params,AbHttpResponseListener responseListener){
          try {
              
              responseListener.sendStartMessage();
              
              if(!debug && !AbAppUtil.isNetworkAvailable(mContext)){
                    responseListener.sendFailureMessage(AbConstant.CONNECT_FAILURE_CODE,AbConstant.CONNECTEXCEPTION, new AbAppException(AbConstant.CONNECTEXCEPTION));
                    return;
              }
              
              //HttpGet连接对象  
              if(params!=null){
                  url += params.getParamString(); //如果有参,那么获取相关参数...
              }
              HttpGet httpRequest = new HttpGet(url); //定义连接对象..
              
              BasicHttpParams httpParams = new BasicHttpParams();
              
              // 从连接池中取连接的超时时间,设置为1秒
              ConnManagerParams.setTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);
              ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(DEFAULT_MAX_CONNECTIONS));
              ConnManagerParams.setMaxTotalConnections(httpParams, DEFAULT_MAX_CONNECTIONS);
              // 读响应数据的超时时间
              HttpConnectionParams.setSoTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);
              HttpConnectionParams.setConnectionTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);
              HttpConnectionParams.setTcpNoDelay(httpParams, true);
              HttpConnectionParams.setSocketBufferSize(httpParams, DEFAULT_SOCKET_BUFFER_SIZE);
              //设置协议版本...
              HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
              HttpProtocolParams.setUserAgent(httpParams, String.format("andbase-http/%s (http://www.418log.org/)", 1.0));
              // 设置请求参数
              httpRequest.setParams(httpParams);
              
              //取得HttpClient对象  
              HttpClient httpClient = new DefaultHttpClient();  
              //请求HttpClient,取得HttpResponse  
              HttpResponse httpResponse = httpClient.execute(httpRequest);  
              //请求成功  
              int statusCode = httpResponse.getStatusLine().getStatusCode();
              
              //取得返回的字符串  
              HttpEntity  mHttpEntity = httpResponse.getEntity();
              if (statusCode == HttpStatus.SC_OK){  
                  if(responseListener instanceof AbStringHttpResponseListener){
                      String content = EntityUtils.toString(mHttpEntity);
                      ((AbStringHttpResponseListener)responseListener).sendSuccessMessage(statusCode, content);
                  }else if(responseListener instanceof AbBinaryHttpResponseListener){
                      readResponseData(mHttpEntity,((AbBinaryHttpResponseListener)responseListener));
                  }else if(responseListener instanceof AbFileHttpResponseListener){
                      //获取文件名
                      String fileName = AbFileUtil.getFileNameFromUrl(url, httpResponse);
                      writeResponseData(mHttpEntity,fileName,((AbFileHttpResponseListener)responseListener));
                  }
              }else{
                  String content = EntityUtils.toString(mHttpEntity);
                  responseListener.sendFailureMessage(statusCode, content, new AbAppException(AbConstant.UNKNOWNHOSTEXCEPTION));
              }
        } catch (Exception e) {
            e.printStackTrace();
            //发送失败消息
            responseListener.sendFailureMessage(AbConstant.UNTREATED_CODE,e.getMessage(),new AbAppException(e));
        }finally{
            responseListener.sendFinishMessage();
        }
    }
    



有了上面的源码调用过程其实就非常的清晰了..

无论是doGet()方法还是doPost()方法模式基本是相同的,都是需要先建立一个连接对象,HttpGet或HttpPost..不同之处在于有参的Get请求直接将params加入到url后面即可,而Post请求需要获取实体数据..在实体数据中加入我们传递的params..设置连接过程和读取数据过程中的相关参数,比如说超时的时间,使用的Http版本,设置UserAgent等等...设置完之后执行请求获取响应了...

中间涉及到了一个判断的过程..判断返回的响应数据到底属于什么类型的数据,是基本的String类型,还是与图片或者视频相关的Byte类型,还是与文件相关的File类型...通过对相关类型的判断,执行不同的方法,虽然方法不相同,但是最后的目的是一样的,都是把实体数据进行封装...封装完毕后调用sendSuccessMessage然后Handler自动回去处理Message...最后调用OnSuccess方法..将数据返回给客户端..

还是看一下实际的调用过程...

无参的Get请求调度...这里需要设置相应监听...

public void FileClick(View v){
    url="http://192.168.199.172:8080/JSP/imageview.jpg";
    getView();
    httpUtil.get(url, new FileResponseListener(this, this, v,max_tv,num_tv,progressBar));
}

GetResponseListener.java

对响应的监听的一个重写过程...通过为请求设置上url+相关监听就能够完成网络请求,并对请求数据进行相关处理了...这里完成了一个图片数据的下载,然后通过对数据进行封装,就成了一个Bitmap..这样就能够在控件上进行显示了..


package com.example.andbasehttp;

import java.io.File;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.graphics.Bitmap;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.ab.activity.AbActivity;
import com.ab.http.AbFileHttpResponseListener;
import com.ab.util.AbFileUtil;
import com.ab.view.progress.AbHorizontalProgressBar;

public class FileResponseListener extends AbFileHttpResponseListener{

    
    private int max=100;
    private int progress=0;
    private AbActivity activity;
    private Context context;
    private AlertDialog dialog;
    private View view;
    private TextView max_tv,num_tv;
    private AbHorizontalProgressBar progressBar;
    
    public FileResponseListener(AbActivity activity,Context context,View v,TextView v1,TextView v2, AbHorizontalProgressBar progressBar ){
        this.activity=activity;
        this.context=context;
        this.view=v;
        this.max_tv=v1;
        this.num_tv=v2;
        this.progressBar=progressBar;
    }
    
    @Override
    public void onSuccess(int statusCode, File file){
        Bitmap bitmap=AbFileUtil.getBitmapFromSD(file);
        ImageView view=new ImageView(context);
        view.setImageBitmap(bitmap);
        activity.showDialog("返回结果", view, new OnClickListener() {
            
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub
                
            }
        });
    }
    
    @Override
    public void onFailure(int statusCode, String content,Throwable error){
        activity.showToast(error.toString());
    }
    
    @Override
    public void onStart(){
        max_tv.setText(progress+"/"+String.valueOf(max));
        progressBar.setMax(max);
        progressBar.setProgress(progress);
        dialog=activity.showDialog("正在下载", view);
    }
    
    @Override
    public void onProgress(int bytesWritten, int totalSize){
        max_tv.setText(bytesWritten/(totalSize/max)+"/"+max);
        progressBar.setProgress(bytesWritten/(totalSize/max));
    }
    
    @Override
    public void onFinish(){
        dialog.cancel();
        dialog=null;
    }
}



2.使用AndBase框架实现有参Http Post请求...

其实调用的方式都是相同的,,只不过Post请求需要传递相关的参数...使用有参的Post请求...这里是向一个JSP传递相关参数来完成数据信息的验证...


public void PostClick(View v){
    url="http://192.168.199.172:8080/JSP/post.jsp";
    params=new AbRequestParams();
    params.put("name", "darker");
    params.put("password", "49681888");
    httpUtil.post(url, params, new PostResponseListener(this));
}
    



这里我就不粘贴PostResponseListener的代码了...贴一下JSP页面的代码..相关的JSP代码如下...这里的JSP代码非常的简单..并且前面在使用Volley的时候也使用过..JSP页面我们完全可以自己书写的更加复杂一些,那么就能够实现更多的功能...


<%
  String name=request.getParameter("name");
  String password=request.getParameter("password");
  if("darker".equals(name)&& "49681888".equals(password)){
      out.println("Receive name is:"+name);
    out.println("Receive password is:"+password);%>
    Your Message are right!
  <%}else{
      out.println("Receive name is:"+name);
    out.println("Receive password is:"+password);%>
    Your Message are wrong!
  <%}%>  



3.使用AndBase框架实现有参Http Get请求...

有参的Get请求一般用于文件,数据资源的上传...将上传的资源以及名称作为参数传递给服务器..这里不涉及安全上的问题..因此可以使用带有参数的Get请求...这里向服务器上传文件..需要添加相关参数...

public void FileLoadClick(View v){
        url="http://192.168.199.172:8080";
        AbRequestParams params = new AbRequestParams();
        File pathRoot = Environment.getExternalStorageDirectory();
        String path = pathRoot.getAbsolutePath();
        File file1 = new File(path+"/download/cache_files/aa.txt");
        params.put(file1.getName(),file1);
        
        getView();
        httpUtil.get(url, params, new FileSendResponseListener(this, this, v, max_tv, num_tv, progressBar));
    }


这里的监听事件简单的粘贴一下...监听事件之所以传递控件..是为了更好的向用户进行展示...这里设置了一个进度条的方式,来贯穿整个请求——响应的过程...如果下载或者是上传的文件和资源过多...我们是必须通知用户相关进度的..总不能一直卡死在界面上..这样用户也无法知道到底是否完成了数据的上传或者是下载...


package com.example.andbasehttp;



import android.app.AlertDialog;
import android.content.Context;
import android.view.View;
import android.widget.TextView;
import com.ab.activity.AbActivity;
import com.ab.http.AbStringHttpResponseListener;
import com.ab.view.progress.AbHorizontalProgressBar;

public class FileSendResponseListener extends AbStringHttpResponseListener{

    private int max=100;
    private int progress=0;
    private AbActivity activity;
    private Context context;
    private AlertDialog dialog;
    private View view;
    private TextView max_tv,num_tv;
    private AbHorizontalProgressBar progressBar;
    
    public FileSendResponseListener(AbActivity activity,Context context,View v,TextView v1,TextView v2, AbHorizontalProgressBar progressBar ){
        this.activity=activity;
        this.context=context;
        this.view=v;
        this.max_tv=v1;
        this.num_tv=v2;
        this.progressBar=progressBar;
    }
    
    @Override
    public void onSuccess(int statusCode, String content){
        activity.showToast("OnSuccess");
        System.out.println(content);
    }
    
    @Override
    public void onFailure(int statusCode, String content,Throwable error){
        activity.showToast(error.toString());
    }
    
    @Override
    public void onStart(){
        max_tv.setText(progress+"/"+String.valueOf(max));
        progressBar.setMax(max);
        progressBar.setProgress(progress);
        activity.showToast("正在下载");
        dialog=activity.showDialog("正在下载", view);
    }
    
    @Override
    public void onProgress(int bytesWritten, int totalSize){
        max_tv.setText(bytesWritten/(totalSize/max)+"/"+max);
        progressBar.setProgress(bytesWritten/(totalSize/max));
    }
    
    @Override
    public void onFinish(){
        dialog.cancel();
        dialog=null;
    }
}



涉及到的类为com.ab.http保内的所有类...

1.AbStringHttpResponseListener.java
2.AbBinaryHttpResponseListener.java
3.AbFileHttpResponseListener.java

这三个类是对AbHttpResponseListener.java的一个继承...继承了其内部的一些相关方法..包括请求开始,结束,失败等等函数...

AbHttpClient.java就是用来完成请求——连接过程的实现...其中还包含数据的封装..

AbHttpUtils.java则是对post,get等方法调用的一个中间层...

AbRequestParams.java 则是对请求参数处理的一个类...不仅包含对请求参数的处理,还包含对实体的创建..为实体添加相关参数等方法的实现过程...


Android Http Get和Post请求

通过HttpGet和HttpPost向服务器提交请求,并从服务器返回结果信息。通过如下3步访问Http资源。

(1)创建HttpGet或者HttpPost对象,将要请求的URL通过构造方法传入HttpGet或HttpPost对象。
(2)使用DefaultHttpClient.execute方法发送Http Get或Http Post请求,并返回HttpResponse对象。
(3)通过HttpResponse.getEntity方法返回响应信息,并进行相应的处理。

如果使用HttpPost方法提交Http Post请求,还需要使用HttpPost.setEntity方法设置请求参数。


实例代码如下:

package mobile.android.ch13.httpgetpost;

import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class Main extends Activity implements OnClickListener
{

    @Override
    public void onClick(View view)
    {
        String url = "http://172.22.20.194:8080/querybooks/QueryServlet";
        TextView tvQueryResult = (TextView) findViewById(R.id.tvQueryResult);
        EditText etBookName = (EditText) findViewById(R.id.etBookName);
        HttpResponse httpResponse = null;
        try
        {
            
            switch (view.getId())
            {
                case R.id.btnGetQuery:
                    url += "?bookname=" + etBookName.getText().toString();
                    HttpGet httpGet = new HttpGet(url);
                    httpResponse = new DefaultHttpClient().execute(httpGet);
                    if (httpResponse.getStatusLine().getStatusCode() == 200)
                    {
                        
                        String result = EntityUtils.toString(httpResponse
                                .getEntity());
                        tvQueryResult.setText(result.replaceAll("\r", ""));
                    }
                    break;

                case R.id.btnPostQuery:
                    HttpPost httpPost = new HttpPost(url);
                    List<NameValuePair> params = new ArrayList<NameValuePair>();
                    params.add(new BasicNameValuePair("bookname", etBookName
                            .getText().toString()));
                    httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
                    
                    httpResponse = new DefaultHttpClient().execute(httpPost);
                    if (httpResponse.getStatusLine().getStatusCode() == 200)
                    {
                        String result = EntityUtils.toString(httpResponse
                                .getEntity());
                        tvQueryResult.setText(result.replaceAll("\r", ""));
                    }
                    break;
            }
        }
        catch (Exception e)
        {
            tvQueryResult.setText(e.getMessage());
        }

    }

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btnGetQuery = (Button) findViewById(R.id.btnGetQuery);
        Button btnPostQuery = (Button) findViewById(R.id.btnPostQuery);
        btnGetQuery.setOnClickListener(this);
        btnPostQuery.setOnClickListener(this);
        
    }
}

[!--infotagslink--]

相关文章

  • 使用PHP+JavaScript将HTML页面转换为图片的实例分享

    这篇文章主要介绍了使用PHP+JavaScript将HTML元素转换为图片的实例分享,文后结果的截图只能体现出替换的字体,也不能说将静态页面转为图片可以加快加载,只是这种做法比较interesting XD需要的朋友可以参考下...2016-04-19
  • php抓取网站图片并保存的实现方法

    php如何实现抓取网页图片,相较于手动的粘贴复制,使用小程序要方便快捷多了,喜欢编程的人总会喜欢制作一些简单有用的小软件,最近就参考了网上一个php抓取图片代码,封装了一个php远程抓取图片的类,测试了一下,效果还不错分享...2015-10-30
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • Photoshop古装美女图片转为工笔画效果制作教程

    今天小编在这里就来给各位Photoshop的这一款软件的使用者们来说说把古装美女图片转为细腻的工笔画效果的制作教程,各位想知道方法的使用者们,那么下面就快来跟着小编一...2016-09-14
  • 图解PHP使用Zend Guard 6.0加密方法教程

    有时为了网站安全和版权问题,会对自己写的php源码进行加密,在php加密技术上最常用的是zend公司的zend guard 加密软件,现在我们来图文讲解一下。 下面就简单说说如何...2016-11-25
  • Python 图片转数组,二进制互转操作

    这篇文章主要介绍了Python 图片转数组,二进制互转操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-09
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • ps怎么使用HSL面板

    ps软件是现在很多人都会使用到的,HSL面板在ps软件中又有着非常独特的作用。这次文章就给大家介绍下ps怎么使用HSL面板,还不知道使用方法的下面一起来看看。 &#8195;...2017-07-06
  • 利用JS实现点击按钮后图片自动切换的简单方法

    下面小编就为大家带来一篇利用JS实现点击按钮后图片自动切换的简单方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-25
  • jquery左右滚动焦点图banner图片鼠标经过显示上下页按钮

    jquery左右滚动焦点图banner图片鼠标经过显示上下页按钮...2013-10-13
  • js实现上传图片及时预览

    这篇文章主要为大家详细介绍了js实现上传图片及时预览的相关资料,具有一定的参考价值,感兴趣的朋友可以参考一下...2016-05-09
  • Plesk控制面板新手使用手册总结

    许多的朋友对于Plesk控制面板应用不是非常的了解特别是英文版的Plesk控制面板,在这里小编整理了一些关于Plesk控制面板常用的使用方案整理,具体如下。 本文基于Linu...2016-10-10
  • 使用insertAfter()方法在现有元素后添加一个新元素

    复制代码 代码如下: //在现有元素后添加一个新元素 function insertAfter(newElement, targetElement){ var parent = targetElement.parentNode; if (parent.lastChild == targetElement){ parent.appendChild(newEl...2014-05-31
  • Photoshop枪战电影海报图片制作教程

    Photoshop的这一款软件小编相信很多的人都已经是使用过了吧,那么今天小编在这里就给大家带来了用Photoshop软件制作枪战电影海报的教程,想知道制作步骤的玩家们,那么下面...2016-09-14
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • jQuery 1.9使用$.support替代$.browser的使用方法

    jQuery 从 1.9 版开始,移除了 $.browser 和 $.browser.version , 取而代之的是 $.support 。 在更新的 2.0 版本中,将不再支持 IE 6/7/8。 以后,如果用户需要支持 IE 6/7/8,只能使用 jQuery 1.9。 如果要全面支持 IE,并混合...2014-05-31
  • 使用percona-toolkit操作MySQL的实用命令小结

    1.pt-archiver 功能介绍: 将mysql数据库中表的记录归档到另外一个表或者文件 用法介绍: pt-archiver [OPTION...] --source DSN --where WHERE 这个工具只是归档旧的数据,不会对线上数据的OLTP查询造成太大影响,你可以将...2015-11-24
  • 使用GruntJS构建Web程序之构建篇

    大概有如下步骤 新建项目Bejs 新建文件package.json 新建文件Gruntfile.js 命令行执行grunt任务 一、新建项目Bejs源码放在src下,该目录有两个js文件,selector.js和ajax.js。编译后代码放在dest,这个grunt会...2014-06-07
  • 如何使用php脚本给html中引用的js和css路径打上版本号

    在搜索引擎中搜索关键字.htaccess 缓存,你可以搜索到很多关于设置网站文件缓存的教程,通过设置可以将css、js等不太经常更新的文件缓存在浏览器端,这样访客每次访问你的网站的时候,浏览器就可以从浏览器的缓存中获取css、...2015-11-24