Android框架AndBase实现PullToRefrech下拉刷新

 更新时间:2016年9月20日 19:53  点击:1710
AndBase是为Android开发者量身打造的一款开源类库产品,AndBase框架的藕合性非常的低,你可以在你的项目中用完整的源码进行编译使用。

PullToRefrech下拉刷新..这个效果在绝大多数使用app貌似都使用的到..一个页面的、如果数据太多必然进行下拉..那么下拉之后就需要显示新的数据信息..这就是下拉刷新的完整体现.也可以说这种效果能够给用户一个更加良好的体验..那么我们看看下拉刷新到底是如何实现的..

1.普通View的PullReferch

  无论是普通View的下拉刷新还是ListView,还是GridView..下拉刷新实现的原理基本都是相同的..只不过实现上有一些简单的细节不太一样而已...首先先看看普通View的下拉刷新..实现普通下拉刷新需要使用到AbPullView, AbPullView extends ScollView 可滑动视图...不难想象的一件事情就是无论是怎样进行滑动..其实都是对布局的覆盖..

  手机的屏幕大小是有限的...因此无论是怎样进行滑动..都是一层布局去覆盖原来的那层布局..原来的那层布局会被直接销毁..如果按照正常思路去想,那么就算是View被销毁了...那么自然有记录了这次布局的相关数据...当这些被销毁的布局需要再次显示的时候,那么直接调用记录数据就可以了..没有必要再建立新的布局..由于是AndBase内部把这个视图已经封装好了因此我们需要进行相关的引用..说白了就是需要进行动态的布局...因此布局文件中,需要这样去进行书写..我们通过获取ID的方式进行动态的布局..

  <com.ab.view.pullview.AbPullView
        android:id="@+id/mPullView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
  </com.ab.view.pullview.AbPullView>

  获取到了ID之后..通过动态加载xml数据..就可以设置好下拉需要显示的相关View..设置的方式其实就是一行代码就能解决的事情..

  mAbPullView.getHeaderView().setHeaderProgressBarDrawable(this.getResources().getDrawable(R.drawable.progress_circular));

  这个方式就是设置顶部下拉刷新的一个背景显示...这里我设置了一个动态的效果...R.drawable.progress_circular...


<!-- 单帧动画旋转功能的实现  -->
<!-- 持续时间:两秒
          起始角度:0度
          定义动画的旋转样式..起始和结束都减速..中间过程加速..
          偏移量:顶部和左部
          重复的样式:重新开始
          末端角度:360度
          背景设置:drawable-->
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200"
    android:fromDegrees="0.0"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:pivotX="50.0%"
    android:pivotY="50.0%"
    android:repeatMode="restart"
    android:toDegrees="360.0"
    android:drawable="@drawable/progress_loading2">
</animated-rotate>



  上面的这些过程都是设置下拉时需要显示的View数据..并没有真正涉及到下拉的过程..说白了这个就是下拉后需要发生的事情..而真正想要实现下拉..少不了监听事件的设置..有了对下拉的监听..系统才能够对下拉进行相关的响应..响应事件的绑定..

 mAbPullView.setAbOnRefreshListener(new AbOnRefreshListener(){
    @Override
    public void onRefresh() {
        mAbTaskQueue.execute(item); //下拉时需要执行的事件..
    }      
 });

  绑定上了监听之后..当我们在屏幕上进行下拉的时候..那么就会有事件进行响应了..这里我设置任务是一个异步线程任务...我们可以在这个任务中设置需要执行的事件..然后将事物放入到任务执行队列当中..那么任务就会在下拉的这个时间段内被执行..然后更新在UI界面上..


final AbTaskItem item = new AbTaskItem();
        item.listener = new AbTaskListener() {

            @Override
            public void update() {
                                //当任务完成之后..那么停止刷新..也就表示下拉时出现的视图需要被隐藏了...
                removeProgressDialog();
                mAbPullView.stopRefresh(); //停止刷新..
            }

            @Override
            public void get() {
                   try {
                       Thread.sleep(1000);
                       //这里是任务执行的过程...
                   } catch (Exception e) {
                   }
          };
        };



  这样通过几个简单的过程就可以完成普通View的下拉刷新...这个的源码我并没有进行特别深入的研究...其实关键地方就是ScollView 和 FrameLayout..总体的思想我也说过了..视图无论是滑动还是下拉刷新原理就是这样...就是View的覆盖..如果有更有兴趣的..可以去研究研究这两个类的源码...

2.ListView下拉刷新的实现...

  ListView下拉刷新,这个在绝大多数的app中都必然会使用到..就拿我们的博客园app来说吧..博客的放入在屏幕上的显示是有限的..那么如果我们想要看到更多的博客文章..那么无非需要下拉刷新加载更多数据,或者是使用分页的方式去显示更多的数据..博客园的app是下拉刷新来实现的..用过的人还是非常清楚的..因此ListView的下拉刷新还是非常的重要的..必须要进行掌握的...

  AndBase仍然把一些类进行了封装..提供给我们进行使用..去实现ListView的下拉刷新..原理基本是相同的..需要使用AbPullListView extends ListView implements OnScollListener在继承了ListView的情况下同时也就实现了滚动监听..下面这个接口是在为ListView设置监听后需要实现的方法...方法的实现由我们去实现..这个类仅仅提供了在下拉或上啦后需要做的事情..并没有涉及到滑动的过程...


public interface AbOnListViewListener {
    
    /**
     * On refresh.
     */
    public void onRefresh();

    /**
     * On load more.
     */
    public void onLoadMore();
}



  滑动的过程是由于AbPullListView实现了OnTouchEvent方法..OnTouchEvent表示触摸事件监听...当在屏幕上发生按下,抬起,移动等操作时需要执行..这也就是对滑动事件的一个监听设置..没了这个方法..我们怎么滑动屏幕都是徒劳..那么当发生了上拉或者是下拉就会去执行相应的监听事件..这个上拉或者是下拉的过程是通过滑动的坐标值来确定的..这里就涉及到了OnTouchEvent的源码..源码的实现也比较简单...大体的东西还是比较清晰的..


@Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mLastY == -1) {
            mLastY = ev.getRawY();
        }

        switch (ev.getAction()) {  //对事件的判断..
        case MotionEvent.ACTION_DOWN:  //按下操作..
            mLastY = ev.getRawY();       //获取数值坐标..
            break;
        case MotionEvent.ACTION_MOVE:   //移动操作..
            final float deltaY = ev.getRawY() - mLastY;  //原始值减掉最终值..
            mLastY = ev.getRawY();        //获取原始值..
            if (mEnablePullRefresh && getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {  //这里是对坐标的判断过程..if条件表示满足上拉条件..
                updateHeaderHeight(deltaY / OFFSET_RADIO);
            } else if (mEnablePullLoad && !mPullLoading && getLastVisiblePosition() == mTotalItemCount - 1 && deltaY<0) {  //这个表示下拉过程..
                startLoadMore();
            }
            break;
        case MotionEvent.ACTION_UP:  //抬起操作的执行过程..
            mLastY = -1;
            if (getFirstVisiblePosition() == 0) {
                //需要刷新的条件
                if (mEnablePullRefresh && mHeaderView.getVisiableHeight() >= mHeaderViewHeight) {
                    mPullRefreshing = true;
                    mHeaderView.setState(AbListViewHeader.STATE_REFRESHING);
                    if (mListViewListener != null) {
                        //刷新
                        mListViewListener.onRefresh();
                    }
                }
                
                if(mEnablePullRefresh){
                    //弹回
                    resetHeaderHeight();
                }
            }
            break;
        default:
            break;
        }
        return super.onTouchEvent(ev);
    }



  那么有了触摸事件的监听之后..当触摸事件发生之后就可以执行相关的监听事件..这样就可以去执行下拉和上拉过程中需要执行的任务了...那么现在就说一下ListView实现下拉刷新的过程...它的布局文件其实也是和普通View的下拉定义一样..


   <com.ab.view.pullview.AbPullListView
        android:id="@+id/mListView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:cacheColorHint="#00000000"
        android:divider="@drawable/list_divider"
        android:dividerHeight="1dip"
        android:fastScrollEnabled="true">
   </com.ab.view.pullview.AbPullListView>



  我们需要获取它的ID信息..然后再进行动态布局..这就是ListView的刷新过程..首先我们需要允许它能够进行下拉和上拉的操作..否则是无法滑动的..然后设置下拉时显示的东西..我这里是定义了一个动画效果..表示当前的这个View中的东西要实现旋转效果..效果就是普通View中定义的那个效果..


  //获取ListView对象
  mAbPullListView = (AbPullListView)this.findViewById(R.id.mListView);
        
  //打开关闭下拉刷新加载更多功能
  mAbPullListView.setPullRefreshEnable(true);
  mAbPullListView.setPullLoadEnable(true);
        
  //设置进度条的样式
  mAbPullListView.getHeaderView().setHeaderProgressBarDrawable(this.getResources().getDrawable(R.drawable.progress_circular));
  mAbPullListView.getFooterView().setFooterProgressBarDrawable(this.getResources().getDrawable(R.drawable.progress_circular));



  然后需要设置相关的监听事件...监听事件就需要重写这两个方法了..前面已经涉及到了这两个方法..就不再进行细说了..一个方法表示的是上拉过程需要进行重新刷新视图,一个表示的是下拉刷新需要加载更多的视图..两个方法..我们需要自定义去实现..


mAbPullListView.setAbOnListViewListener(new AbOnListViewListener(){

            @Override
            public void onRefresh() {
                mAbTaskQueue.execute(item1);
            }

            @Override
            public void onLoadMore() {
                mAbTaskQueue.execute(item2);
            }
            
        });



  我自己定义了两个异步任务..这两个异步任务用于对数据进行下载的一个操作..那么数据在下载之后是需要更新到ListView中的Item上的..那么Item的数据更新就需要适配器来完成了..因此ListView的适配器不可或缺的一个重要部分..在适配器内部去设置相关样式的显示..当发生下拉或者是上拉等操作的时候..我们去唤醒适配器去更新数据就可以了..更新数据的方法调用..

 myListViewAdapter.notifyDataSetChanged();

  在这里我只简单的说了一下原理...这个就是任务代码的设置..


final AbTaskItem item1 = new AbTaskItem();
        item1.listener = new AbTaskListener() {

            @Override
            public void update() {
                removeProgressDialog();
                list.clear();
                if(newList!=null && newList.size()>0){
                    list.addAll(newList);
                    myListViewAdapter.notifyDataSetChanged();
                    newList.clear();
                   }
                mAbPullListView.stopRefresh();
            }
            @Override
            public void get() {
                   try {
                       Thread.sleep(1000);
                       currentPage = 1;
                       newList = new ArrayList<Map<String, Object>>();
                       Map<String, Object> map = null;
                       
                       for (int i = 0; i < pageSize; i++) {
                           map = new HashMap<String, Object>();
                           map.put("itemsIcon",mPhotoList.get(new Random().nextInt(mPhotoList.size())));
                           map.put("itemsTitle", "item"+(i+1));
                           map.put("itemsText", "item..."+(i+1));
                           newList.add(map);
                           
                       }
                   } catch (Exception e) {
               }
          };
        };



  总体的过程就是这样..想要做出更好的效果..我们就需要放入我们自己的思想..就能够做出更好的下拉效果了...

3.GridView 下拉刷新..

  GridView网格视图..视图以网格的形式进行展现..每一个网格都是一个子视图..原理和ListView基本相同..只不过ListView是一行行的显示..GridView是一块块的去显示..少不了适配器的使用..只要我们获取到网格中的子选项..然后为每一个子选项设置样式..就可以完成一个网格View的设置...网格View的使用也应该不少..只要我们的设置合理..同样能够做出非常漂亮的网格视图..

  GridView采用的方式就不是FrameLayout了..它使用的是线性布局..AbPullGridView extends AbBaseGridView implements OnScollListener, OnTouchListener.. AbBaseGridView是继承了LinearLayout..原理采用线性布局的方式将视图进行展示..通过实现滚动监听和触摸监听就能够实现滚动..以及触摸事件的监听设置..有兴趣的还是可以去研究一下源码的实现过程..我就只说一下其中的调用方式..

  布局的定义方式仍然是差不多的..仍然是获取ID,然后通过动态加载的方式进行数据的加载..

<com.ab.view.pullview.AbPullGridView
        android:id="@+id/mPhotoGridView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
<com.ab.view.pullview.AbPullGridView/>

   调用的过程和ListView基本相同..


mAbPullGridView = (AbPullGridView)findViewById(R.id.mPhotoGridView);  
        
 //开关默认打开
mAbPullGridView.setPullRefreshEnable(true);
mAbPullGridView.setPullLoadEnable(true);
        
//设置进度条的样式
mAbPullGridView.getHeaderView().setHeaderProgressBarDrawable(this.getResources().getDrawable(R.drawable.progress_circular));
mAbPullGridView.getFooterView().setFooterProgressBarDrawable(this.getResources().getDrawable(R.drawable.progress_circular));
        
mGridView = mAbPullGridView.getGridView();  
mGridView.setColumnWidth(150);          //设置列的宽度..
mGridView.setGravity(Gravity.CENTER);   //对齐方式为中央对其..
mGridView.setHorizontalSpacing(5);     //设置水平间距..
mGridView.setNumColumns(GridView.AUTO_FIT);    //列的数量为自适应屏幕为限制..
mGridView.setPadding(5, 5, 5, 5);             //设置间距..
mGridView.setStretchMode(GridView.STRETCH_COLUMN_WIDTH);  
mGridView.setVerticalSpacing(5);              //GridView之间的垂直间距..



  只是GridView的子选项需要我们设置更多的东西..需要设置子选项的排列以及显示方式..设置好了一个子选项的排列方式,那么其他所有的子选项会按照这同一种方式显示在屏幕之上..这样只是设置好了排布的问题..具体需要显示的东西我们还是需要通过使用适配器来安排子选项需要显示的东西..

  同样滑动之后需要执行的监听事件我们是需要进行重写的..


    mAbPullGridView.setAbOnListViewListener(new AbOnListViewListener() {
            
            @Override
            public void onRefresh() {
                //第一次下载数据
                mAbTaskQueue.execute(item1);
            }
            
            @Override
            public void onLoadMore() {
                mAbTaskQueue.execute(item2);
            }
        });



  同时我们也可以设置子选项被点击时设置的相关监听..通过设置子选项的监听..可以通过触发子选项执行更多的操作..忘记说了..ListView也是可以触发子选项的监听事件的..比较简单就没必要再贴代码了...


mAbPullGridView.getGridView().setOnItemClickListener(new OnItemClickListener(){

            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                showToast(""+position);
            }
            
        });
        



  这里的异步任务我也就不再进行粘贴了..和ListVIew基本都是相同的..当任务执行完毕之后..通过唤醒适配器去更新数据信息就可以完成了..这样就实现了PullToRefrech下拉刷新..我们知道了调用过程之后..就能够通过自己定义的方式去设置更加良好的刷新效果..调用的过程不是很难..真正难的是我们自己的思想..如何设置出更加良好的UI才是更加重要的..



Android-PullToRefresh下拉刷新库基本用法
 
PullToRefresh是一套实现非常好的下拉刷新库,它支持:
ListView
ExpandableListView
GridView
WebView
ScrollView
HorizontalScrollView
ViewPager
等多种常用的需要刷新的View类型,而且使用起来也十分方便。
(下载地址:https://github.com/chrisbanes/Android-PullToRefresh)

PullToRefresh基本用法:
1、在布局文件中添加PullToRefresh控件,比如PullToRefreshListView;
2、在Activity中,设置监听器OnRefreshListener以响应用户下拉操作;
3、在监听器的onRefresh()方法中执行数据刷新操作,可以通过AsyncTask来实现;
4、在AsyncTask中获取到数据后,记得调用onRefreshComplete()方法通知PullToRefresh控件数据已获取完毕,可以结束刷新操作。

实例:PullToRefreshDemo
运行效果:

视图结构


代码清单:

布局文件:activity_main.xml


    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:paddingBottom="@dimen/activity_vertical_margin"  
        android:paddingLeft="@dimen/activity_horizontal_margin"  
        android:paddingRight="@dimen/activity_horizontal_margin"  
        android:paddingTop="@dimen/activity_vertical_margin"  
        tools:context=".MainActivity" >  
       
        <com.handmark.pulltorefresh.library.PullToRefreshListView  
            android:id="@+id/pull_to_refresh_listview"  
            android:layout_height="fill_parent"  
            android:layout_width="fill_parent" />  
      
    </RelativeLayout>  


Java源代码文件:MainActivity.java


    package com.rainsong.pulltorefreshdemo;  
      
    import java.util.Arrays;  
    import java.util.LinkedList;  
      
    import android.app.Activity;  
    import android.os.AsyncTask;  
    import android.os.Bundle;  
    import android.text.format.DateUtils;  
    import android.view.Menu;  
    import android.widget.ArrayAdapter;  
    import android.widget.ListView;  
      
    import com.handmark.pulltorefresh.library.PullToRefreshBase;  
    import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;  
    import com.handmark.pulltorefresh.library.PullToRefreshListView;  
      
    public class MainActivity extends Activity {  
        private PullToRefreshListView mPullToRefreshListView;  
        private LinkedList<String> mListItems;  
        private ArrayAdapter<String> mAdapter;  
      
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
              
            // Set a listener to be invoked when the list should be refreshed.  
            mPullToRefreshListView = (PullToRefreshListView) findViewById(R.id.pull_to_refresh_listview);  
            mPullToRefreshListView.setOnRefreshListener(new OnRefreshListener<ListView>() {  
                @Override  
                public void onRefresh(PullToRefreshBase<ListView> refreshView) {  
                    String label = DateUtils.formatDateTime(getApplicationContext(), System.currentTimeMillis(),  
                            DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL);  
                      
                    // Update the LastUpdatedLabel  
                    refreshView.getLoadingLayoutProxy().setLastUpdatedLabel(label);  
                      
                    // Do work to refresh the list here.  
                    new GetDataTask().execute();  
                }  
            });  
              
            ListView actualListView = mPullToRefreshListView.getRefreshableView();  
              
            mListItems = new LinkedList<String>();  
            mListItems.addAll(Arrays.asList(mStrings));  
              
            mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListItems);  
            actualListView.setAdapter(mAdapter);  
        }  
          
        private class GetDataTask extends AsyncTask<Void, Void, String[]> {  
      
            @Override  
            protected String[] doInBackground(Void... params) {  
                // Simulates a background job.  
                try {  
                    Thread.sleep(4000);  
                } catch (InterruptedException e) {  
                }  
                return mStrings;  
            }  
            @Override  
            protected void onPostExecute(String[] result) {  
                mListItems.addFirst("Added after refresh...");  
                mAdapter.notifyDataSetChanged();  
                  
                // Call onRefreshComplete when the list has been refreshed.  
                mPullToRefreshListView.onRefreshComplete();  
                super.onPostExecute(result);  
            }  
        }  
      
        @Override  
        public boolean onCreateOptionsMenu(Menu menu) {  
            // Inflate the menu; this adds items to the action bar if it is present.  
            getMenuInflater().inflate(R.menu.main, menu);  
            return true;  
        }  
      
        private String[] mStrings = { "John", "Michelle", "Amy", "Kim", "Mary",  
                "David", "Sunny", "James", "Maria", "Michael", "Sarah", "Robert",  
                "Lily", "William", "Jessica", "Paul", "Crystal", "Peter",  
                "Jennifer", "George", "Rachel", "Thomas", "Lisa", "Daniel", "Elizabeth",  
                "Kevin" };  
    } 

Layout_weight是Android开发中常用布局属性,本文详细介绍了Layout_weight的属性,它是用来分配属于空间的一个属性,你可以设置他的权重。

Layout_weight是Android开发中一个比较常用的布局属性,在面试中也经常被问到.下面通过实例彻底搞懂Layout_weight的用法.

先看下面的布局代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <TextView
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="1"
        android:background="#44ff0000"
        android:gravity="center"
        android:text="111111111111"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="2"
        android:background="#4400ff00"
        android:gravity="center"
        android:text="2"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="3"
        android:background="#440000ff"
        android:gravity="center"
        android:text="3"/>
</LinearLayout>

在水平方向的线性布局中,有三个TextView,layout_width都是0dp,layout_weight分别为1,2,3,所以这三个TextView应该按1:2:3的比例分布,

   显示效果:

  


  可以看到,虽然三个TextView是按1:2:3的比例进行分布的,但是第一个TextView却没有和另外两个对齐,这是为什么呢?

  仔细看就能发现,虽然三个TextView不是对齐的,但是第一行的文本是对齐的.

 

  我们只需将LinearLayout的baselineAligned属性设置为false即可,baselineAligned表示是否以文本基准线对齐.

  
  这只是对layout_weight属性最基本的用法.假如把第一个TextView的layout_width该为wrap_content,会出现什么情况呢?


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:baselineAligned="false">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_weight="1"
        android:background="#44ff0000"
        android:gravity="center"
        android:text="111111111111"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="2"
        android:background="#4400ff00"
        android:gravity="center"
        android:text="2"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="3"
        android:background="#440000ff"
        android:gravity="center"
        android:text="3"/>
</LinearLayout>

  看下效果:

  


  发现三个TextView并没有再按照1:2:3的比例进行分配.其实,layout_weight属性是先按控件声明的尺寸进行分配,然后将剩余的尺寸按layout_weight的比例进行分配.  

  设屏幕总宽度为sum,第一个TextView声明的尺寸为x,那么,

  第一个TextView的宽度为: x + (sum - x) * 1/6

  第二个TextView的宽度为: (sum - x) * 2/6

  第三个TextView的宽度为: (sum - x) * 3/6

  为了证实这个结论,接下来继续修改布局,将三个TextView的layout_width都设置为match_parent,把layout_weight分别设置为1,2,2


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:baselineAligned="false">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_weight="1"
        android:background="#44ff0000"
        android:gravity="center"
        android:text="111111111111"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_weight="2"
        android:background="#4400ff00"
        android:gravity="center"
        android:text="2"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_weight="2"
        android:background="#440000ff"
        android:gravity="center"
        android:text="3"/>
</LinearLayout>

  先看下效果:

  


  看到这个结果可能会感到很诧异,第一个TextView的layout_weight明明比其它两个的小,为什么宽度却比它们大呢?

  我们按刚才得出的结论算一下.

  依然设屏幕总宽度为sum,由于这三个TextView声明的尺寸都是match_parent,也就是sum,那么,

  第一个TextView的宽度为: sum + (sum - 3*sum) * 1/5 = sum*3/5

  第二个TextView的宽度为: sum + (sum - 3*sum) * 2/5 = sum*1/5

  第三个TextView的宽度为: sum + (sum - 3*sum) * 2/5 = sum*1/5

  三个TextView的宽度比例为 3:1:1,所以我们的结论是正确的.    

  需要注意的是,我们结论所说的剩余的尺寸可能是负的,如 sum - 3*sum,

  另外,通过最后这个例子可以看出,并不是layout_weight越大,宽度越大.



Android Layout_weight的深刻理解

首先看一下Layout_weight属性的作用:它是用来分配属于空间的一个属性,你可以设置他的权重。很多人不知道剩余空间是个什么概念,下面 我先来说说剩余空间。

看下面代码:
 

    <?xml version="1.0" encoding="utf-8"?>     

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     

        android:orientation="vertical"     

        android:layout_width="fill_parent"     

        android:layout_height="fill_parent"     

        >     

    <EditText     

        android:layout_width="fill_parent"     

        android:layout_height="wrap_content"     

        android:gravity="left"     

        android:text="one"/>     

    <EditText     

        android:layout_width="fill_parent"     

        android:layout_height="wrap_content"     

        android:gravity="center"     

        android:layout_weight="1.0"     

        android:text="two"/>     

        <EditText     

        android:layout_width="fill_parent"     

        android:layout_height="wrap_content"     

        android:gravity="right"     

        android:text="three"/>     

    </LinearLayout>     

运行结果是:

\
看上面代码:只有Button2使用了Layout_weight属性,并赋值为了1,而Button1和Button3没有设置 Layout_weight这个属性,根据API,可知,他们默认是0

下面我就来讲,Layout_weight这个属性的真正的意思:Android系统先按照你设置的3个Button高度 Layout_height值wrap_content,给你分配好他们3个的高度,

然后会把剩下来的屏幕空间全部赋给Button2,因为只有他的权重值是1,这也是为什么Button2占了那么大的一块空间。

有了以上的理解我们就可以对网上关于Layout_weight这个属性更让人费解的效果有一个清晰的认识了。

我们来看这段代码:

 

     <?xml version="1.0" encoding="UTF-8"?>   

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"   

        android:layout_width="fill_parent"   

        android:layout_height="wrap_content"   

        android:orientation="horizontal" >   

        <TextView   

            android:background="#ff0000"   

            android:layout_width="**"   

            android:layout_height="wrap_content"   

            android:text="1"   

            android:textColor="@android:color/white"   

            android:layout_weight="1"/>   

        <TextView   

            android:background="#cccccc"   

            android:layout_width="**"   

            android:layout_height="wrap_content"   

            android:text="2"   

            android:textColor="@android:color/black"   

            android:layout_weight="2" />   

         <TextView   

            android:background="#ddaacc"   

            android:layout_width="**"   

            android:layout_height="wrap_content"   

            android:text="3"   

            android:textColor="@android:color/black"   

            android:layout_weight="3" />   

    </LinearLayout>

三个文本框的都是 layout_width=“wrap_content ”时,会得到以下效果

\

按照上面的理解,系统先给3个TextView分配他们的宽度值 wrap_content(宽度足以包含他们的内容1,2,3即可),然后会把剩下来的屏幕空间按照1:2:3的比列分配给3个textview,所以就 出现了上面的图像。

而当layout_width=“fill_parent”时, 如果分别给三个TextView设置他们的Layout_weight为1、2、2的话,就会出现下面的效果:

\

你会发现 1的权重小,反而分的多了,这是为什么呢???网上很多人说是当layout_width=“fill_parent”时,weighth 值越小权重越大,优先级越高,就好像在背口诀

一样,其 实他们并没有真正理解这个问题,真正的原因是Layout_width="fill_parent"的原因造成的。依照上面理解我们来分析:

系统先给3个textview分配他们所要的宽度fill_parent,也就是说每一都是填满他的父控件,这里就死屏幕的宽度

那么这时候的剩余空间=1个parent_width-3个parent_width=-2个parent_width (parent_width指的是屏幕宽度 )

那么第一个TextView的实际所占宽度应该=fill_parent的宽度,即parent_width + 他所占剩余空间的权重比列1/5 * 剩余空间大小(-2 parent_width)=3/5parent_width

同理第二个TextView的实际所占宽度=parent_width + 2/5*(-2parent_width)=1/5parent_width;

第三个TextView的实际所占宽度=parent_width + 2/5*(-2parent_width)=1/5parent_width;所以就是3:1:1的比列显示了。

这样你也就会明白为什么当你把三个Layout_weight设置为1、2、3的话,会出现下面的效果了:

\

第三个直接不显示了,为什么呢?一起来按上面方法算一下吧:

系统先给3个textview分配他们所要的宽度fill_parent,也就是说每一都是填满他的父控件,这里就死屏幕的宽度

那么这时候的剩余空间=1个parent_width-3个parent_width=-2个parent_width (parent_width指的是屏幕宽度 )

那么第一个TextView的实际所占宽度应该=fill_parent的宽度,即parent_width + 他所占剩余空间的权重比列1/6 * 剩余空间大小(-2 parent_width)=2/3parent_width

同理第二个TextView的实际所占宽度=parent_width + 2/6*(-2parent_width)=1/3parent_width;

第三个TextView的实际所占宽度=parent_width + 3/6*(-2parent_width)=0parent_width;所以就是2:1:0的比列显示了。第三个就直接没有空间了。

Android中的动画有很多类型,主要有帧动画,补间动画,属性动画。Fragment是为了适应Android屏幕的尺寸而出现的。

Fragment:

Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应平板神马超级大屏的。难道无法做到一个App可以同时适应手机和平板么,当然了,必须有啊。Fragment的出现就是为了解决这样的问题。你可以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全有不同的Fragment组成,更帅气的是Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添加、替换和移除某个Fragment。


Fragment的生命周期,Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。


使用Fragment可以让我们更加充分地利用手机的屏幕空间,他可以在一个activity中有多个Fragment,多个Fragment可以显示不同的内容。

我们通过简单的demo来了解什么是Fragment和Fragment如果使用

    在这个demo中我们使用FrameLayout(框架布局),Framelayout一般很少用到,在下面的Demo中使用会很方便,所有添加到这个布局中的视图都以层叠的方式显示。第一个添加的控件被放在最底层,最后一个添加到框架布局中的视图显示在最顶层,上一层的控件会覆盖下一层的控件。

    需求:我们在一个布局文件中的左边竖直分布,三个button,右边是一个Framelayout,当我们点击不同的button时,右边显示不同的Fragment。

    第一步:写布局文件,布局文件左边包含三个按钮,右边是一个FrameLayout。 再写三个填充用的布局文件,这三个布局文件中background的颜色都不一样,有一个textView用来区分不同的布局文件。

    第二不写三个 fragment的Java类  分别将三个布局文件填充到对应的java类中作为内容显示在屏幕母上

public class fragment01 extends Fragment {
 
    //返回的view对象会作为fragment01的内容显示在屏幕上
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
 
        View v = inflater.inflate(R.layout.fragment, null);
        return v;
    }

    第三步 在MainActivity中写对三个Button做处理

        当点击按钮一的时候,屏幕右边显示的是Fragment01

        第一步 new出Fragment01的对象

        第二步  获取Fragment的管理器getFragmentManager()

        第三步  打开事物beginTransaction()

        第四步  把内容显示到帧布局replace 最后提交事物commit

public void click1(View v) {
       fragment01 fg = new fragment01();
       FragmentManager fm  = getFragmentManager();
       FragmentTransaction ft = fm.beginTransaction();
       ft.replace(R.id.fl,fg );
       ft.commit();
   }

 

fragment是3.0之后出来的,我们可以做向下兼容,让低版本可以使用。这个时候就用到了安卓支持类库。 我们在引包的时候都改成support的包但是要注意的是,但是在获去fragment管理器,没有支持类库,我们可以使用getSupportFragmentManager()来获得兼容低版本的管理器


在Fragment和activity中传递数据

把Fragment中的数据传递到activity中做法:
在Fragment的xml文件中,放一个EditText。还有一个button 但我们点击button的时候把数据传递到 activity的EditText中。
把activity中的数据传递到Fragment中

      步骤:1 我们在fragment中的布局文件中,定义一个输入框, 在定义一个button,但是onClick属性只能在上下文中使用,在Fragment中不可以使用

我们可以给按钮设置点击侦听

          2 在MainActivity的布局文件中,定义一个TextView 用来显示数据

          3我们在MainActivity定义一个方法,用来给TextView 设置显示的数据

          4 在Fragment的java类中 我们拿到button 给button设置监听,在按钮点击的时候,拿到输入框中的数据通过getActivity()方法将它强转成MainActivity就可以拿到和Fragment相关联的activity,这个时候就可以调用activity中的方法给textview设置值了。

注意 :如果将fragment和activity中的EditText和TextView的id设置成相同,虽然findViewById是在各自的布局文件中拿,但是由于Activity中给TextView设置值得方法是在fragment中调用,那么Activity中的方法会优先在fragment的布局文件中找,而不再自己的布局文件中寻找。
如果两个id相同的话解决方案是,在activity中把拿去布局文件的方法变量提成全局,在activity创建方法中取拿到

   把activity中的数据传递到Fragment中的做法:

      步骤:1 在fragment的xml文件中定义一个TextView,用来显示数据。

         2 我们在fragment的java类中拿到 定义的TextView, 创建一个公开的方法,这个方法内可以个TextView设置值

         3 我们在MainActivity中定义一个EditText用来输入数据,定义一个button,点击的时候传递数据

         4 当我们点击按钮的时候我们拿到EditText中的数据,同时new出对应要传递的fragment的java类的对象,直接用fragment对象调用它的方法传值。

 

fragment的生命周期方法和activity的生命周期方法相同并且是绑定的,fragment切换时旧fragment对象会销毁,新的fragment对象会被创建

 

帧动画:

    一张张图片不断的切换,形成动画效果,安卓手机的开机界面就是通过帧动画做的

     如何自己使用帧动画:

        步骤:1将素材拷贝到drawable中

           2 在drawable目录下定义xml文件(在api中的drawable Animation中可以看到xml文件的格式和使用代码)

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> //这里的false表示循环播放 true表示只播放一次
            <item android:drawable="@drawable/g1" android:duration="200" />//表示duration表示每张图片显示的时长
            <item android:drawable="@drawable/g2" android:duration="200" />
            <item android:drawable="@drawable/g3" android:duration="200" />
        </animation-list>

 

          3 在屏幕上播放帧动画

ImageView iv = (ImageView) findViewById(R.id.iv); //首先拿到imageView 将动画播放在imageView
   //把动画文件设置为imageView的背景
   iv.setBackgroundResource(R.drawable.animations); ////把帧动画的资源文件指定为iv的背景
   AnimationDrawable ad = (AnimationDrawable) iv.getBackground();//获取iv的北京
   //播放动画       
   ad.start();

 

补间动画:

原形态变成新形态时为了过渡变形过程,生成的动画就叫补间动画  位移、旋转、缩放、透明

  位移
    
/*位移:
    参数10指的是X的起点坐标,但不是指屏幕x坐标为10的位置,而是imageview的 真实X + 10
     参数150指的是X的终点坐标,它的值是imageview的 真实X + 150
     
        //创建为位移动画对象,设置动画的初始位置和结束位置
        //TranslateAnimation ta = new TranslateAnimation(10, 150, 20, 140);
* x坐标的起点位置,如果相对于自己,传0.5f,那么起点坐标就是 真实X + 0.5 * iv宽度
* x坐标的终点位置,如果传入2,那么终点坐标就是 真实X + 2 * iv的宽度
* y坐标的起点位置,如果传入0.5f,那么起点坐标就是 真实Y + 0.5 * iv高度
* y坐标的终点位置,如果传入2,那么终点坐标就是 真实Y + 2 * iv高度*/
     
        TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2);
        //设置动画持续时间
        ta.setDuration(2000);
        //动画重复播放的次数
        ta.setRepeatCount(1);
        //动画重复播放的模式
        ta.setRepeatMode(Animation.REVERSE);
        //动画播放完毕后,组件停留在动画结束的位置上
        ta.setFillAfter(true);
        //播放动画
        iv.startAnimation(ta); //iv表示动画显示显示在哪个地方 通过findviewbyid拿到

 

  旋转  

public void rotate(View v){<br>          //表示从0到720旋转                            表示从真实的x+图片的宽度的一半 在中心旋转
        RotateAnimation ra = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f,
        //设置动画持续时间                              Animation.RELATIVE_TO_SELF, 0.5f);
        ra.setDuration(2000);<br>         //设置动画重发次数表示重发播放两次
        ra.setRepeatCount(1);
        ra.setRepeatMode(Animation.REVERSE);
        iv.startAnimation(ra);
    }

 缩放

public void scale(View v){
//      sa = new ScaleAnimation(fromX, toX, fromY, toY, iv.getWidth() / 2, iv.getHeight() / 2);
        sa = new ScaleAnimation(0.5f, 2, 0.1f, 3, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);  //o.5f表示动画的起始宽度是真实宽度的0.5倍 2表示动画结束宽度是真实宽度的2倍  后面参数表示旋转中心点的位置 默认是坐上角
        sa.setDuration(2000);
        //填充动画的结束位置
        sa.setRepeatCount(1);
        sa.setRepeatMode(Animation.REVERSE);
        sa.setFillAfter(true);
        iv.startAnimation(sa);
    }

 透明

public void alpha(View v){
        aa = new AlphaAnimation(0, 1); //0表示完全透明。1表示不透明
        aa.setDuration(2000);
        sa.setRepeatCount(1);
        iv.startAnimation(aa);
    }

  我们可以设置所有的动画一起播放

public void fly(View v){
        AnimationSet set = new AnimationSet(false);//false表示使用自己的叫对器
        set.addAnimation(ta);
        set.addAnimation(sa);
        set.addAnimation(ra);
        set.addAnimation(aa);
         
        iv.startAnimation(set);
    }

  

 

属性动画
    补间动画,只是一个动画效果,组件其实还在原来的位置上,xy没有改变。而属性动画是xy真实改变了,属性动画是3.0后加入的特性。没有向下支持类库

 位移:

public void translate(View v){
         
        //target:动画作用于哪个组件  第一个参数target指定要显示动画的组件  第二个参数propertyName指定要改变组件的哪个属性  第三个参数values是可变参数,就是赋予属性的新的值
        ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "translationX", 10, 70, 20, 100); //他可以实现来回移动 我们可以动过set和get来看属性是什么
        oa.setDuration(2000);
        oa.setRepeatCount(1);
        oa.setRepeatMode(ValueAnimator.REVERSE);
        oa.start();
    }

  缩放

public void scale(View v){
        ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "scaleX", 1, 1.6f, 1.2f, 2);
        oa.setDuration(2000);
        oa.start();
    }

  

透明:

public void alpha(View v){
        ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "alpha", 0, 0.5f, 0.2f, 1);
        oa.setDuration(2000);
        oa.start();
    }

旋转


public void rotate(View v){
        ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "rotationY", 0, 180, 90, 360); //rotation指定是顺时针旋转

//属性指定为rotationX是竖直翻转
//属性指定为rotationY是水平翻转

    oa.setDuration(2000);
    oa.setRepeatCount(1);
    oa.setRepeatMode(ValueAnimator.REVERSE);
    oa.start();
}

所有动画一起播放  

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

使用xml文件定义属性动画

<?xml version="1.0" encoding="utf-8"?>  在animator文件下 resource type 选择property animator root element是set
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <objectAnimator
        android:propertyName="translationX"
        android:duration="200"
        android:repeatCount="1"
        android:repeatMode="reverse"
        android:valueFrom="-100"
        android:valueTo="100"
        >
         
    </objectAnimator>
</set>


public void xml(View v){
        Animator at = AnimatorInflater.loadAnimator(this, R.animator.objanimator); //加载指定xml
        //设置作用于哪个组件
        at.setTarget(iv);
        at.start();
    }



关于Android的屏幕适应Fragment及各类动画的类型介绍,我们就讲到这里

在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. 创建大对象时,要检查它的生命周期


[!--infotagslink--]

相关文章

  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Android开发中findViewById()函数用法与简化

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

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • android.os.BinderProxy cannot be cast to com解决办法

    本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20
  • Android 实现钉钉自动打卡功能

    这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
  • Android 开发之布局细节对比:RTL模式

    下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
  • Android中使用SDcard进行文件的读取方法

    首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
  • Android开发之PhoneGap打包及错误解决办法

    下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
  • 用Intel HAXM给Android模拟器Emulator加速

    Android 模拟器 Emulator 速度真心不给力,, 现在我们来介绍使用 Intel HAXM 技术为 Android 模拟器加速,使模拟器运行度与真机比肩。 周末试玩了一下在Eclipse中使...2016-09-20
  • Android判断当前屏幕是全屏还是非全屏

    在安卓开发时我碰到一个问题就是需要实现全屏,但又需要我们来判断出用户是使用了全屏或非全屏了,下面我分别找了两段代码,大家可参考。 先来看一个android屏幕全屏实...2016-09-20
  • Android开发中布局中的onClick简单完成多控件时的监听的利与弊

    本文章来为各位介绍一篇关于Android开发中布局中的onClick简单完成多控件时的监听的利与弊的例子,希望这个例子能够帮助到各位朋友. 首先在一个控件加上这么一句:and...2016-09-20
  • Ubuntu 系统下安装Android开发环境 Android Studio 1.0 步骤

    Android Studio 是一个Android开发环境,基于IntelliJ IDEA. 类似 Eclipse ADT,Android Studio 提供了集成的 Android 开发工具用于开发和调试,可以在Linux,Mac OS X,Window...2016-09-20
  • Android实现简单用户注册案例

    这篇文章主要为大家详细介绍了Android实现简单用户注册案例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-05-26