`
George_ghc
  • 浏览: 90843 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论
收藏列表
标题 标签 来源
Android 进阶学习:Android自定义View的实现方法,带你一步步深入了解View(四) android基础
http://blog.csdn.net/guolin_blog/article/details/17357967
不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了LayoutInflater的原理分析、视图的绘制流程、视图的状态及重绘等知识,算是把View中很多重要的知识点都涉及到了。如果你还没有看过我前面的几篇文章,建议先去阅读一下,多了解一些原理方面的东西。
之前我有承诺过,会在View这个话题上多写几篇博客,讲一讲View的工作原理,以及自定义View的方法。现在前半部分的承诺已经如约兑现了,那么今天我就要来兑现后面部分的承诺,讲一讲自定义View的实现方法,同时这也是带你一步步深入了解View系列的完结篇。
一些接触Android不久的朋友对自定义View都有一丝畏惧感,总感觉这是一个比较高级的技术,但其实自定义View并不复杂,有时候只需要简单几行代码就可以完成了。
如果说要按类型来划分的话,自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件。那么下面我们就来依次学习一下,每种方式分别是如何自定义View的。
一、自绘控件
自绘控件的意思就是,这个View上所展现的内容全部都是我们自己绘制出来的。绘制的代码是写在onDraw()方法中的,而这部分内容我们已经在 Android视图绘制流程完全解析,带你一步步深入了解View(二) 中学习过了。
下面我们准备来自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次。新建一个CounterView继承自View,代码如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public class CounterView extends View implements OnClickListener {  
  
    private Paint mPaint;  
      
    private Rect mBounds;  
  
    private int mCount;  
      
    public CounterView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        mBounds = new Rect();  
        setOnClickListener(this);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  
        mPaint.setColor(Color.BLUE);  
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);  
        mPaint.setColor(Color.YELLOW);  
        mPaint.setTextSize(30);  
        String text = String.valueOf(mCount);  
        mPaint.getTextBounds(text, 0, text.length(), mBounds);  
        float textWidth = mBounds.width();  
        float textHeight = mBounds.height();  
        canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2  
                + textHeight / 2, mPaint);  
    }  
  
    @Override  
    public void onClick(View v) {  
        mCount++;  
        invalidate();  
    }  
  
}  
可以看到,首先我们在CounterView的构造函数中初始化了一些数据,并给这个View的本身注册了点击事件,这样当CounterView被点击的时候,onClick()方法就会得到调用。而onClick()方法中的逻辑就更加简单了,只是对mCount这个计数器加1,然后调用invalidate()方法。通过 Android视图状态及重绘流程分析,带你一步步深入了解View(三) 这篇文章的学习我们都已经知道,调用invalidate()方法会导致视图进行重绘,因此onDraw()方法在稍后就将会得到调用。
既然CounterView是一个自绘视图,那么最主要的逻辑当然就是写在onDraw()方法里的了,下面我们就来仔细看一下。这里首先是将Paint画笔设置为蓝色,然后调用Canvas的drawRect()方法绘制了一个矩形,这个矩形也就可以当作是CounterView的背景图吧。接着将画笔设置为黄色,准备在背景上面绘制当前的计数,注意这里先是调用了getTextBounds()方法来获取到文字的宽度和高度,然后调用了drawText()方法去进行绘制就可以了。
这样,一个自定义的View就已经完成了,并且目前这个CounterView是具备自动计数功能的。那么剩下的问题就是如何让这个View在界面上显示出来了,其实这也非常简单,我们只需要像使用普通的控件一样来使用CounterView就可以了。比如在布局文件中加入如下代码:
[html] view plaincopy在CODE上查看代码片派生到我的代码片 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
  
    <com.example.customview.CounterView  
        android:layout_width="100dp"  
        android:layout_height="100dp"  
        android:layout_centerInParent="true" />  
  
</RelativeLayout>  
可以看到,这里我们将CounterView放入了一个RelativeLayout中,然后可以像使用普通控件来给CounterView指定各种属性,比如通过layout_width和layout_height来指定CounterView的宽高,通过android:layout_centerInParent来指定它在布局里居中显示。只不过需要注意,自定义的View在使用的时候一定要写出完整的包名,不然系统将无法找到这个View。
好了,就是这么简单,接下来我们可以运行一下程序,并不停地点击CounterView,效果如下图所示。

怎么样?是不是感觉自定义View也并不是什么高级的技术,简单几行代码就可以实现了。当然了,这个CounterView功能非常简陋,只有一个计数功能,因此只需几行代码就足够了,当你需要绘制比较复杂的View时,还是需要很多技巧的。
二、组合控件
组合控件的意思就是,我们并不需要自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但我们可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。
举个例子来说,标题栏就是个很常见的组合控件,很多界面的头部都会放置一个标题栏,标题栏上会有个返回按钮和标题,点击按钮后就可以返回到上一个界面。那么下面我们就来尝试去实现这样一个标题栏控件。
新建一个title.xml布局文件,代码如下所示:
[html] view plaincopy在CODE上查看代码片派生到我的代码片 
<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="50dp"  
    android:background="#ffcb05" >  
  
    <Button  
        android:id="@+id/button_left"  
        android:layout_width="60dp"  
        android:layout_height="40dp"  
        android:layout_centerVertical="true"  
        android:layout_marginLeft="5dp"  
        android:background="@drawable/back_button"  
        android:text="Back"  
        android:textColor="#fff" />  
  
    <TextView  
        android:id="@+id/title_text"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_centerInParent="true"  
        android:text="This is Title"  
        android:textColor="#fff"  
        android:textSize="20sp" />  
  
</RelativeLayout>  
在这个布局文件中,我们首先定义了一个RelativeLayout作为背景布局,然后在这个布局里定义了一个Button和一个TextView,Button就是标题栏中的返回按钮,TextView就是标题栏中的显示的文字。
接下来创建一个TitleView继承自FrameLayout,代码如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public class TitleView extends FrameLayout {  
  
    private Button leftButton;  
  
    private TextView titleText;  
  
    public TitleView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        LayoutInflater.from(context).inflate(R.layout.title, this);  
        titleText = (TextView) findViewById(R.id.title_text);  
        leftButton = (Button) findViewById(R.id.button_left);  
        leftButton.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                ((Activity) getContext()).finish();  
            }  
        });  
    }  
  
    public void setTitleText(String text) {  
        titleText.setText(text);  
    }  
  
    public void setLeftButtonText(String text) {  
        leftButton.setText(text);  
    }  
  
    public void setLeftButtonListener(OnClickListener l) {  
        leftButton.setOnClickListener(l);  
    }  
  
}  
TitleView中的代码非常简单,在TitleView的构建方法中,我们调用了LayoutInflater的inflate()方法来加载刚刚定义的title.xml布局,这部分内容我们已经在 Android LayoutInflater原理分析,带你一步步深入了解View(一) 这篇文章中学习过了。
接下来调用findViewById()方法获取到了返回按钮的实例,然后在它的onClick事件中调用finish()方法来关闭当前的Activity,也就相当于实现返回功能了。
另外,为了让TitleView有更强地扩展性,我们还提供了setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法,分别用于设置标题栏上的文字、返回按钮上的文字、以及返回按钮的点击事件。
到了这里,一个自定义的标题栏就完成了,那么下面又到了如何引用这个自定义View的部分,其实方法基本都是相同的,在布局文件中添加如下代码:
[html] view plaincopy在CODE上查看代码片派生到我的代码片 
<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" >  
  
    <com.example.customview.TitleView  
        android:id="@+id/title_view"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content" >  
    </com.example.customview.TitleView>  
  
</RelativeLayout>  
这样就成功将一个标题栏控件引入到布局文件中了,运行一下程序,效果如下图所示:

现在点击一下Back按钮,就可以关闭当前的Activity了。如果你想要修改标题栏上显示的内容,或者返回按钮的默认事件,只需要在Activity中通过findViewById()方法得到TitleView的实例,然后调用setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法进行设置就OK了。
三、继承控件
继承控件的意思就是,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能,比如 Android PowerImageView实现,可以播放动画的强大ImageView 这篇文章中介绍的PowerImageView就是一个典型的继承控件。
为了能够加深大家对这种自定义View方式的理解,下面我们再来编写一个新的继承控件。ListView相信每一个Android程序员都一定使用过,这次我们准备对ListView进行扩展,加入在ListView上滑动就可以显示出一个删除按钮,点击按钮就会删除相应数据的功能。
首先需要准备一个删除按钮的布局,新建delete_button.xml文件,代码如下所示:
[html] view plaincopy在CODE上查看代码片派生到我的代码片 
<?xml version="1.0" encoding="utf-8"?>  
<Button xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@+id/delete_button"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:background="@drawable/delete_button" >  
  
</Button>  
这个布局文件很简单,只有一个按钮而已,并且我们给这个按钮指定了一张删除背景图。
接着创建MyListView继承自ListView,这就是我们自定义的View了,代码如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public class MyListView extends ListView implements OnTouchListener,  
        OnGestureListener {  
  
    private GestureDetector gestureDetector;  
  
    private OnDeleteListener listener;  
  
    private View deleteButton;  
  
    private ViewGroup itemLayout;  
  
    private int selectedItem;  
  
    private boolean isDeleteShown;  
  
    public MyListView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        gestureDetector = new GestureDetector(getContext(), this);  
        setOnTouchListener(this);  
    }  
  
    public void setOnDeleteListener(OnDeleteListener l) {  
        listener = l;  
    }  
  
    @Override  
    public boolean onTouch(View v, MotionEvent event) {  
        if (isDeleteShown) {  
            itemLayout.removeView(deleteButton);  
            deleteButton = null;  
            isDeleteShown = false;  
            return false;  
        } else {  
            return gestureDetector.onTouchEvent(event);  
        }  
    }  
  
    @Override  
    public boolean onDown(MotionEvent e) {  
        if (!isDeleteShown) {  
            selectedItem = pointToPosition((int) e.getX(), (int) e.getY());  
        }  
        return false;  
    }  
  
    @Override  
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,  
            float velocityY) {  
        if (!isDeleteShown && Math.abs(velocityX) > Math.abs(velocityY)) {  
            deleteButton = LayoutInflater.from(getContext()).inflate(  
                    R.layout.delete_button, null);  
            deleteButton.setOnClickListener(new OnClickListener() {  
                @Override  
                public void onClick(View v) {  
                    itemLayout.removeView(deleteButton);  
                    deleteButton = null;  
                    isDeleteShown = false;  
                    listener.onDelete(selectedItem);  
                }  
            });  
            itemLayout = (ViewGroup) getChildAt(selectedItem  
                    - getFirstVisiblePosition());  
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(  
                    LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);  
            params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);  
            params.addRule(RelativeLayout.CENTER_VERTICAL);  
            itemLayout.addView(deleteButton, params);  
            isDeleteShown = true;  
        }  
        return false;  
    }  
  
    @Override  
    public boolean onSingleTapUp(MotionEvent e) {  
        return false;  
    }  
  
    @Override  
    public void onShowPress(MotionEvent e) {  
  
    }  
  
    @Override  
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,  
            float distanceY) {  
        return false;  
    }  
  
    @Override  
    public void onLongPress(MotionEvent e) {  
    }  
      
    public interface OnDeleteListener {  
  
        void onDelete(int index);  
  
    }  
  
}  
由于代码逻辑比较简单,我就没有加注释。这里在MyListView的构造方法中创建了一个GestureDetector的实例用于监听手势,然后给MyListView注册了touch监听事件。然后在onTouch()方法中进行判断,如果删除按钮已经显示了,就将它移除掉,如果删除按钮没有显示,就使用GestureDetector来处理当前手势。
当手指按下时,会调用OnGestureListener的onDown()方法,在这里通过pointToPosition()方法来判断出当前选中的是ListView的哪一行。当手指快速滑动时,会调用onFling()方法,在这里会去加载delete_button.xml这个布局,然后将删除按钮添加到当前选中的那一行item上。注意,我们还给删除按钮添加了一个点击事件,当点击了删除按钮时就会回调onDeleteListener的onDelete()方法,在回调方法中应该去处理具体的删除操作。
好了,自定义View的功能到此就完成了,接下来我们需要看一下如何才能使用这个自定义View。首先需要创建一个ListView子项的布局文件,新建my_list_view_item.xml,代码如下所示:
[html] view plaincopy在CODE上查看代码片派生到我的代码片 
<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:descendantFocusability="blocksDescendants"  
    android:orientation="vertical" >  
  
    <TextView  
        android:id="@+id/text_view"  
        android:layout_width="wrap_content"  
        android:layout_height="50dp"  
        android:layout_centerVertical="true"  
        android:gravity="left|center_vertical"  
        android:textColor="#000" />  
  
</RelativeLayout>  
然后创建一个适配器MyAdapter,在这个适配器中去加载my_list_view_item布局,代码如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public class MyAdapter extends ArrayAdapter<String> {  
  
    public MyAdapter(Context context, int textViewResourceId, List<String> objects) {  
        super(context, textViewResourceId, objects);  
    }  
  
    @Override  
    public View getView(int position, View convertView, ViewGroup parent) {  
        View view;  
        if (convertView == null) {  
            view = LayoutInflater.from(getContext()).inflate(R.layout.my_list_view_item, null);  
        } else {  
            view = convertView;  
        }  
        TextView textView = (TextView) view.findViewById(R.id.text_view);  
        textView.setText(getItem(position));  
        return view;  
    }  
  
}  
到这里就基本已经完工了,下面在程序的主布局文件里面引入MyListView这个控件,如下所示:
[html] view plaincopy在CODE上查看代码片派生到我的代码片 
<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" >  
  
    <com.example.customview.MyListView  
        android:id="@+id/my_list_view"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content" >  
    </com.example.customview.MyListView>  
  
</RelativeLayout>  
最后在Activity中初始化MyListView中的数据,并处理了onDelete()方法的删除逻辑,代码如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public class MainActivity extends Activity {  
  
    private MyListView myListView;  
  
    private MyAdapter adapter;  
  
    private List<String> contentList = new ArrayList<String>();  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
        initList();  
        myListView = (MyListView) findViewById(R.id.my_list_view);  
        myListView.setOnDeleteListener(new OnDeleteListener() {  
            @Override  
            public void onDelete(int index) {  
                contentList.remove(index);  
                adapter.notifyDataSetChanged();  
            }  
        });  
        adapter = new MyAdapter(this, 0, contentList);  
        myListView.setAdapter(adapter);  
    }  
  
    private void initList() {  
        contentList.add("Content Item 1");  
        contentList.add("Content Item 2");  
        contentList.add("Content Item 3");  
        contentList.add("Content Item 4");  
        contentList.add("Content Item 5");  
        contentList.add("Content Item 6");  
        contentList.add("Content Item 7");  
        contentList.add("Content Item 8");  
        contentList.add("Content Item 9");  
        contentList.add("Content Item 10");  
        contentList.add("Content Item 11");  
        contentList.add("Content Item 12");  
        contentList.add("Content Item 13");  
        contentList.add("Content Item 14");  
        contentList.add("Content Item 15");  
        contentList.add("Content Item 16");  
        contentList.add("Content Item 17");  
        contentList.add("Content Item 18");  
        contentList.add("Content Item 19");  
        contentList.add("Content Item 20");  
    }  
  
}  
这样就把整个例子的代码都完成了,现在运行一下程序,会看到MyListView可以像ListView一样,正常显示所有的数据,但是当你用手指在MyListView的某一行上快速滑动时,就会有一个删除按钮显示出来,如下图所示:

点击一下删除按钮就可以将第6行的数据删除了。此时的MyListView不仅保留了ListView原生的所有功能,还增加了一个滑动进行删除的功能,确实是一个不折不扣的继承控件。
到了这里,我们就把自定义View的几种实现方法全部讲完了,虽然每个例子都很简单,但是万变不离其宗,复杂的View也是由这些简单的原理堆积出来的。经过了四篇文章的学习,相信每个人对View的理解都已经较为深入了,那么带你一步步深入了解View系列的文章就到此结束,感谢大家有耐心看到最后。
Android 进阶学习:Android视图绘制流程完全解析,带你一步步深入了解View(二) android基础
http://blog.csdn.net/guolin_blog/article/details/16330267
在上一篇文章中,我带着大家一起剖析了一下LayoutInflater的工作原理,可以算是对View进行深入了解的第一步吧。那么本篇文章中,我们将继续对View进行深入探究,看一看它的绘制流程到底是什么样的。如果你还没有看过我的上一篇文章,可以先去阅读 Android LayoutInflater原理分析,带你一步步深入了解View(一)  。
相信每个Android程序员都知道,我们每天的开发工作当中都在不停地跟View打交道,Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View的,如TextView、Button、ImageView、ListView等。这些控件虽然是Android系统本身就提供好的,我们只需要拿过来使用就可以了,但你知道它们是怎样被绘制到屏幕上的吗?多知道一些总是没有坏处的,那么我们赶快进入到本篇文章的正题内容吧。
要知道,任何一个视图都不可能凭空突然出现在屏幕上,它们都是要经过非常科学的绘制流程后才能显示出来的。每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw(),下面我们逐个对这三个阶段展开进行探讨。
一. onMeasure()
measure是测量的意思,那么onMeasure()方法顾名思义就是用于测量视图的大小的。View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:
1. EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
那么你可能会有疑问了,widthMeasureSpec和heightMeasureSpec这两个值又是从哪里得到的呢?通常情况下,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?这就需要去分析ViewRoot中的源码了,观察performTraversals()方法可以发现如下代码:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  
可以看到,这里调用了getRootMeasureSpec()方法去获取widthMeasureSpec和heightMeasureSpec的值,注意方法中传入的参数,其中lp.width和lp.height在创建ViewGroup实例的时候就被赋值了,它们都等于MATCH_PARENT。然后看下getRootMeasureSpec()方法中的代码,如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
private int getRootMeasureSpec(int windowSize, int rootDimension) {  
    int measureSpec;  
    switch (rootDimension) {  
    case ViewGroup.LayoutParams.MATCH_PARENT:  
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
        break;  
    case ViewGroup.LayoutParams.WRAP_CONTENT:  
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
        break;  
    default:  
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
        break;  
    }  
    return measureSpec;  
}  
可以看到,这里使用了MeasureSpec.makeMeasureSpec()方法来组装一个MeasureSpec,当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的。
介绍了这么多MeasureSpec相关的内容,接下来我们看下View的measure()方法里面的代码吧,如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
    if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
            widthMeasureSpec != mOldWidthMeasureSpec ||  
            heightMeasureSpec != mOldHeightMeasureSpec) {  
        mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
        if (ViewDebug.TRACE_HIERARCHY) {  
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);  
        }  
        onMeasure(widthMeasureSpec, heightMeasureSpec);  
        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
            throw new IllegalStateException("onMeasure() did not set the"  
                    + " measured dimension by calling"  
                    + " setMeasuredDimension()");  
        }  
        mPrivateFlags |= LAYOUT_REQUIRED;  
    }  
    mOldWidthMeasureSpec = widthMeasureSpec;  
    mOldHeightMeasureSpec = heightMeasureSpec;  
}  
注意观察,measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。然后在第9行调用了onMeasure()方法,这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小,如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public static int getDefaultSize(int size, int measureSpec) {  
    int result = size;  
    int specMode = MeasureSpec.getMode(measureSpec);  
    int specSize = MeasureSpec.getSize(measureSpec);  
    switch (specMode) {  
    case MeasureSpec.UNSPECIFIED:  
        result = size;  
        break;  
    case MeasureSpec.AT_MOST:  
    case MeasureSpec.EXACTLY:  
        result = specSize;  
        break;  
    }  
    return result;  
}  
这里传入的measureSpec是一直从measure()方法中传递过来的。然后调用MeasureSpec.getMode()方法可以解析出specMode,调用MeasureSpec.getSize()方法可以解析出specSize。接下来进行判断,如果specMode等于AT_MOST或EXACTLY就返回specSize,这也是系统默认的行为。之后会在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。
当然,一个界面的展示可能会涉及到很多次的measure,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小,如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
    final int size = mChildrenCount;  
    final View[] children = mChildren;  
    for (int i = 0; i < size; ++i) {  
        final View child = children[i];  
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {  
            measureChild(child, widthMeasureSpec, heightMeasureSpec);  
        }  
    }  
}  
这里首先会去遍历当前布局下的所有子视图,然后逐个调用measureChild()方法来测量相应子视图的大小,如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
protected void measureChild(View child, int parentWidthMeasureSpec,  
        int parentHeightMeasureSpec) {  
    final LayoutParams lp = child.getLayoutParams();  
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
            mPaddingLeft + mPaddingRight, lp.width);  
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
            mPaddingTop + mPaddingBottom, lp.height);  
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
}  
可以看到,在第4行和第6行分别调用了getChildMeasureSpec()方法来去计算子视图的MeasureSpec,计算的依据就是布局文件中定义的MATCH_PARENT、WRAP_CONTENT等值,这个方法的内部细节就不再贴出。然后在第8行调用子视图的measure()方法,并把计算出的MeasureSpec传递进去,之后的流程就和前面所介绍的一样了。
当然,onMeasure()方法是可以重写的,也就是说,如果你不想使用系统默认的测量方式,可以按照自己的意愿进行定制,比如:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public class MyView extends View {  
  
    ......  
      
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        setMeasuredDimension(200, 200);  
    }  
  
}  
这样的话就把View默认的测量流程覆盖掉了,不管在布局文件中定义MyView这个视图的大小是多少,最终在界面上显示的大小都将会是200*200。
需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。
由此可见,视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。
到此为止,我们就把视图绘制流程的第一阶段分析完了。
二. onLayout()
measure过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。正如其名字所描述的一样,这个方法是用于给视图进行布局的,也就是确定视图的位置。ViewRoot的performTraversals()方法会在measure结束后继续执行,并调用View的layout()方法来执行此过程,如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);  
layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。可以看到,这里还把刚才测量出的宽度和高度传到了layout()方法中。那么我们来看下layout()方法中的代码是什么样的吧,如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public void layout(int l, int t, int r, int b) {  
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  
    boolean changed = setFrame(l, t, r, b);  
    if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {  
        if (ViewDebug.TRACE_HIERARCHY) {  
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);  
        }  
        onLayout(changed, l, t, r, b);  
        mPrivateFlags &= ~LAYOUT_REQUIRED;  
        if (mOnLayoutChangeListeners != null) {  
            ArrayList<OnLayoutChangeListener> listenersCopy =  
                    (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();  
            int numListeners = listenersCopy.size();  
            for (int i = 0; i < numListeners; ++i) {  
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);  
            }  
        }  
    }  
    mPrivateFlags &= ~FORCE_LAYOUT;  
}  
在layout()方法中,首先会调用过来的四个参数分别setFrame()方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘,同时还会在这里把传递赋值给mLeft、mTop、mRight和mBottom这几个变量。接下来会在第11行调用onLayout()方法,正如onMeasure()方法中的默认行为一样,也许你已经迫不及待地想知道onLayout()方法中的默认行为是什么样的了。进入onLayout()方法,咦?怎么这是个空方法,一行代码都没有?!
没错,View中的onLayout()方法就是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。既然如此,我们来看下ViewGroup中的onLayout()方法是怎么写的吧,代码如下:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
@Override  
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);  
可以看到,ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。没错,像LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。由于LinearLayout和RelativeLayout的布局规则都比较复杂,就不单独拿出来进行分析了,这里我们尝试自定义一个布局,借此来更深刻地理解onLayout()的过程。
自定义的这个布局目标很简单,只要能够包含一个子视图,并且让子视图正常显示出来就可以了。那么就给这个布局起名叫做SimpleLayout吧,代码如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public class SimpleLayout extends ViewGroup {  
  
    public SimpleLayout(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  
  
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
        if (getChildCount() > 0) {  
            View childView = getChildAt(0);  
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);  
        }  
    }  
  
    @Override  
    protected void onLayout(boolean changed, int l, int t, int r, int b) {  
        if (getChildCount() > 0) {  
            View childView = getChildAt(0);  
            childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());  
        }  
    }  
  
}  
代码非常的简单,我们来看下具体的逻辑吧。你已经知道,onMeasure()方法会在onLayout()方法之前调用,因此这里在onMeasure()方法中判断SimpleLayout中是否有包含一个子视图,如果有的话就调用measureChild()方法来测量出子视图的大小。
接着在onLayout()方法中同样判断SimpleLayout是否有包含一个子视图,然后调用这个子视图的layout()方法来确定它在SimpleLayout布局中的位置,这里传入的四个参数依次是0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight(),分别代表着子视图在SimpleLayout中左上右下四个点的坐标。其中,调用childView.getMeasuredWidth()和childView.getMeasuredHeight()方法得到的值就是在onMeasure()方法中测量出的宽和高。
这样就已经把SimpleLayout这个布局定义好了,下面就是在XML文件中使用它了,如下所示:
[html] view plaincopy在CODE上查看代码片派生到我的代码片 
<com.example.viewtest.SimpleLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
      
    <ImageView   
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:src="@drawable/ic_launcher"  
        />  
      
</com.example.viewtest.SimpleLayout>  
可以看到,我们能够像使用普通的布局文件一样使用SimpleLayout,只是注意它只能包含一个子视图,多余的子视图会被舍弃掉。这里SimpleLayout中包含了一个ImageView,并且ImageView的宽高都是wrap_content。现在运行一下程序,结果如下图所示:
                               
OK!ImageView成功已经显示出来了,并且显示的位置也正是我们所期望的。如果你想改变ImageView显示的位置,只需要改变childView.layout()方法的四个参数就行了。
在onLayout()过程结束后,我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了。说到这里,我相信很多朋友长久以来都会有一个疑问,getWidth()方法和getMeasureWidth()方法到底有什么区别呢?它们的值好像永远都是相同的。其实它们的值之所以会相同基本都是因为布局设计者的编码习惯非常好,实际上它们之间的差别还是挺大的。
首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
观察SimpleLayout中onLayout()方法的代码,这里给子视图的layout()方法传入的四个参数分别是0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight(),因此getWidth()方法得到的值就是childView.getMeasuredWidth() - 0 = childView.getMeasuredWidth() ,所以此时getWidth()方法和getMeasuredWidth() 得到的值就是相同的,但如果你将onLayout()方法中的代码进行如下修改:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
@Override  
protected void onLayout(boolean changed, int l, int t, int r, int b) {  
    if (getChildCount() > 0) {  
        View childView = getChildAt(0);  
        childView.layout(0, 0, 200, 200);  
    }  
}  
这样getWidth()方法得到的值就是200 - 0 = 200,不会再和getMeasuredWidth()的值相同了。当然这种做法充分不尊重measure()过程计算出的结果,通常情况下是不推荐这么写的。getHeight()与getMeasureHeight()方法之间的关系同上,就不再重复分析了。
到此为止,我们把视图绘制流程的第二阶段也分析完了。
三. onDraw()
measure和layout的过程都结束后,接下来就进入到draw的过程了。同样,根据名字你就能够判断出,在这里才真正地开始对视图进行绘制。ViewRoot中的代码会继续执行并创建出一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工作。draw()方法内部的绘制过程总共可以分为六步,其中第二步和第五步在一般情况下很少用到,因此这里我们只分析简化后的绘制过程。代码如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public void draw(Canvas canvas) {  
    if (ViewDebug.TRACE_HIERARCHY) {  
        ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);  
    }  
    final int privateFlags = mPrivateFlags;  
    final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&  
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);  
    mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;  
    // Step 1, draw the background, if needed  
    int saveCount;  
    if (!dirtyOpaque) {  
        final Drawable background = mBGDrawable;  
        if (background != null) {  
            final int scrollX = mScrollX;  
            final int scrollY = mScrollY;  
            if (mBackgroundSizeChanged) {  
                background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);  
                mBackgroundSizeChanged = false;  
            }  
            if ((scrollX | scrollY) == 0) {  
                background.draw(canvas);  
            } else {  
                canvas.translate(scrollX, scrollY);  
                background.draw(canvas);  
                canvas.translate(-scrollX, -scrollY);  
            }  
        }  
    }  
    final int viewFlags = mViewFlags;  
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;  
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;  
    if (!verticalEdges && !horizontalEdges) {  
        // Step 3, draw the content  
        if (!dirtyOpaque) onDraw(canvas);  
        // Step 4, draw the children  
        dispatchDraw(canvas);  
        // Step 6, draw decorations (scrollbars)  
        onDrawScrollBars(canvas);  
        // we're done...  
        return;  
    }  
}  
可以看到,第一步是从第9行代码开始的,这一步的作用是对视图的背景进行绘制。这里会先得到一个mBGDrawable对象,然后根据layout过程确定的视图位置来设置背景的绘制区域,之后再调用Drawable的draw()方法来完成背景的绘制工作。那么这个mBGDrawable对象是从哪里来的呢?其实就是在XML中通过android:background属性设置的图片或颜色。当然你也可以在代码中通过setBackgroundColor()、setBackgroundResource()等方法进行赋值。
接下来的第三步是在第34行执行的,这一步的作用是对视图的内容进行绘制。可以看到,这里去调用了一下onDraw()方法,那么onDraw()方法里又写了什么代码呢?进去一看你会发现,原来又是个空方法啊。其实也可以理解,因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现也是理所当然的。
第三步完成之后紧接着会执行第四步,这一步的作用是对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码。
以上都执行完后就会进入到第六步,也是最后一步,这一步的作用是对视图的滚动条进行绘制。那么你可能会奇怪,当前的视图又不一定是ListView或者ScrollView,为什么要绘制滚动条呢?其实不管是Button也好,TextView也好,任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已。绘制滚动条的代码逻辑也比较复杂,这里就不再贴出来了,因为我们的重点是第三步过程。
通过以上流程分析,相信大家已经知道,View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制。如果你去观察TextView、ImageView等类的源码,你会发现它们都有重写onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。Canvas这个类的用法非常丰富,基本可以把它当成一块画布,在上面绘制任意的东西,那么我们就来尝试一下吧。
这里简单起见,我只是创建一个非常简单的视图,并且用Canvas随便绘制了一点东西,代码如下所示:
[java] view plaincopy在CODE上查看代码片派生到我的代码片 
public class MyView extends View {  
  
    private Paint mPaint;  
  
    public MyView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        mPaint.setColor(Color.YELLOW);  
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);  
        mPaint.setColor(Color.BLUE);  
        mPaint.setTextSize(20);  
        String text = "Hello View";  
        canvas.drawText(text, 0, getHeight() / 2, mPaint);  
    }  
}  
可以看到,我们创建了一个自定义的MyView继承自View,并在MyView的构造函数中创建了一个Paint对象。Paint就像是一个画笔一样,配合着Canvas就可以进行绘制了。这里我们的绘制逻辑比较简单,在onDraw()方法中先是把画笔设置成黄色,然后调用Canvas的drawRect()方法绘制一个矩形。然后在把画笔设置成蓝色,并调整了一下文字的大小,然后调用drawText()方法绘制了一段文字。
就这么简单,一个自定义的视图就已经写好了,现在可以在XML中加入这个视图,如下所示:
[html] view plaincopy在CODE上查看代码片派生到我的代码片 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
  
    <com.example.viewtest.MyView   
        android:layout_width="200dp"  
        android:layout_height="100dp"  
        />  
  
</LinearLayout>  
将MyView的宽度设置成200dp,高度设置成100dp,然后运行一下程序,结果如下图所示:
                      
图中显示的内容也正是MyView这个视图的内容部分了。由于我们没给MyView设置背景,因此这里看不出来View自动绘制的背景效果。
当然了Canvas的用法还有很多很多,这里我不可能把Canvas的所有用法都列举出来,剩下的就要靠大家自行去研究和学习了。
到此为止,我们把视图绘制流程的第三阶段也分析完了。整个视图的绘制过程就全部结束了,你现在是不是对View的理解更加深刻了呢?感兴趣的朋友可以继续阅读 Android视图状态及重绘流程分析,带你一步步深入了解View(三) 。
Android视图状态及重绘流程分析,带你一步步深入了解View(三)
在前面一篇文章中,我带着大家一起从源码的层面上分析了视图的绘制流程,了解了视图绘制流程中onMeasure、onLayout、onDraw这三个最重要步骤的工作原理,那么今天我们将继续对View进行深入探究,学习一下视图状态以及重绘方面的知识。如果你还没有看过我前面一篇文章,可以先去阅读 Android视图绘制流程完全解析,带你一步步深入了解View(二) 。

相信大家在平时使用View的时候都会发现它是有状态的,比如说有一个按钮,普通状态下是一种效果,但是当手指按下的时候就会变成另外一种效果,这样才会给人产生一种点击了按钮的感觉。当然了,这种效果相信几乎所有的Android程序员都知道该如何实现,但是我们既然是深入了解View,那么自然也应该知道它背后的实现原理应该是什么样的,今天就让我们来一起探究一下吧。

一、视图状态

视图状态的种类非常多,一共有十几种类型,不过多数情况下我们只会使用到其中的几种,因此这里我们也就只去分析最常用的几种视图状态。

1. enabled

表示当前视图是否可用。可以调用setEnable()方法来改变视图的可用状态,传入true表示可用,传入false表示不可用。它们之间最大的区别在于,不可用的视图是无法响应onTouch事件的。

2. focused

表示当前视图是否获得到焦点。通常情况下有两种方法可以让视图获得焦点,即通过键盘的上下左右键切换视图,以及调用requestFocus()方法。而现在的Android手机几乎都没有键盘了,因此基本上只可以使用requestFocus()这个办法来让视图获得焦点了。而requestFocus()方法也不能保证一定可以让视图获得焦点,它会有一个布尔值的返回值,如果返回true说明获得焦点成功,返回false说明获得焦点失败。一般只有视图在focusable和focusable in touch mode同时成立的情况下才能成功获取焦点,比如说EditText。

3. window_focused

表示当前视图是否处于正在交互的窗口中,这个值由系统自动决定,应用程序不能进行改变。

4. selected

表示当前视图是否处于选中状态。一个界面当中可以有多个视图处于选中状态,调用setSelected()方法能够改变视图的选中状态,传入true表示选中,传入false表示未选中。

5. pressed

表示当前视图是否处于按下状态。可以调用setPressed()方法来对这一状态进行改变,传入true表示按下,传入false表示未按下。通常情况下这个状态都是由系统自动赋值的,但开发者也可以自己调用这个方法来进行改变。

我们可以在项目的drawable目录下创建一个selector文件,在这里配置每种状态下视图对应的背景图片。比如创建一个compose_bg.xml文件,在里面编写如下代码:

?
1
2
3
4
5
6
7
<selector xmlns:android="http://schemas.android.com/apk/res/android">
 
    <item android:drawable="@drawable/compose_pressed" android:state_pressed="true"></item>
    <item android:drawable="@drawable/compose_pressed" android:state_focused="true"></item>
    <item android:drawable="@drawable/compose_normal"></item>
 
</selector>
这段代码就表示,当视图处于正常状态的时候就显示compose_normal这张背景图,当视图获得到焦点或者被按下的时候就显示compose_pressed这张背景图。
 

创建好了这个selector文件后,我们就可以在布局或代码中使用它了,比如将它设置为某个按钮的背景图,如下所示:

?
1
2
<!--?xml version=1.0 encoding=utf-8?-->
<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"><button android:background="@drawable/compose_bg" android:id="@+id/compose" android:layout_gravity="center_horizontal" android:layout_height="40dp" android:layout_width="60dp"></button></linearlayout>
现在运行一下程序,这个按钮在普通状态和按下状态的时候就会显示不同的背景图片,如下图所示:
 

/

这样我们就用一个非常简单的方法实现了按钮按下的效果,但是它的背景原理到底是怎样的呢?这就又要从源码的层次上进行分析了。

我们都知道,当手指按在视图上的时候,视图的状态就已经发生了变化,此时视图的pressed状态是true。每当视图的状态有发生改变的时候,就会回调View的drawableStateChanged()方法,代码如下所示:

?
1
2
3
4
5
6
protected void drawableStateChanged() {
    Drawable d = mBGDrawable;
    if (d != null && d.isStateful()) {
        d.setState(getDrawableState());
    }
}
在这里的第一步,首先是将mBGDrawable赋值给一个Drawable对象,那么这个mBGDrawable是什么呢?观察setBackgroundResource()方法中的代码,如下所示:
?
1
2
3
4
5
6
7
8
9
10
11
public void setBackgroundResource(int resid) {
    if (resid != 0 && resid == mBackgroundResource) {
        return;
    }
    Drawable d= null;
    if (resid != 0) {
        d = mResources.getDrawable(resid);
    }
    setBackgroundDrawable(d);
    mBackgroundResource = resid;
}
可以看到,在第7行调用了Resource的getDrawable()方法将resid转换成了一个Drawable对象,然后调用了setBackgroundDrawable()方法并将这个Drawable对象传入,在setBackgroundDrawable()方法中会将传入的Drawable对象赋值给mBGDrawable。
 

而我们在布局文件中通过android:background属性指定的selector文件,效果等同于调用setBackgroundResource()方法。也就是说drawableStateChanged()方法中的mBGDrawable对象其实就是我们指定的selector文件。

接下来在drawableStateChanged()方法的第4行调用了getDrawableState()方法来获取视图状态,代码如下所示:

?
1
2
3
4
5
6
7
8
9
public final int[] getDrawableState() {
    if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
        return mDrawableState;
    } else {
        mDrawableState = onCreateDrawableState(0);
        mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;
        return mDrawableState;
    }
}
在这里首先会判断当前视图的状态是否发生了改变,如果没有改变就直接返回当前的视图状态,如果发生了改变就调用onCreateDrawableState()方法来获取最新的视图状态。视图的所有状态会以一个整型数组的形式返回。
 

在得到了视图状态的数组之后,就会调用Drawable的setState()方法来对状态进行更新,代码如下所示:

?
1
2
3
4
5
6
7
public boolean setState(final int[] stateSet) {
    if (!Arrays.equals(mStateSet, stateSet)) {
        mStateSet = stateSet;
        return onStateChange(stateSet);
    }
    return false;
}
这里会调用Arrays.equals()方法来判断视图状态的数组是否发生了变化,如果发生了变化则调用onStateChange()方法,否则就直接返回false。但你会发现,Drawable的onStateChange()方法中其实就只是简单返回了一个false,并没有任何的逻辑处理,这是为什么呢?这主要是因为mBGDrawable对象是通过一个selector文件创建出来的,而通过这种文件创建出来的Drawable对象其实都是一个StateListDrawable实例,因此这里调用的onStateChange()方法实际上调用的是StateListDrawable中的onStateChange()方法,那么我们赶快看一下吧:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected boolean onStateChange(int[] stateSet) {
    int idx = mStateListState.indexOfStateSet(stateSet);
    if (DEBUG) android.util.Log.i(TAG, onStateChange  + this +  states 
            + Arrays.toString(stateSet) +  found  + idx);
    if (idx < 0) {
        idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
    }
    if (selectDrawable(idx)) {
        return true;
    }
    return super.onStateChange(stateSet);
}
可以看到,这里会先调用indexOfStateSet()方法来找到当前视图状态所对应的Drawable资源下标,然后在第9行调用selectDrawable()方法并将下标传入,在这个方法中就会将视图的背景图设置为当前视图状态所对应的那张图片了。

那你可能会有疑问,在前面一篇文章中我们说到,任何一个视图的显示都要经过非常科学的绘制流程的,很显然,背景图的绘制是在draw()方法中完成的,那么为什么selectDrawable()方法能够控制背景图的改变呢?这就要研究一下视图重绘的流程了。

二、视图重绘

虽然视图会在Activity加载完成之后自动绘制到屏幕上,但是我们完全有理由在与Activity进行交互的时候要求动态更新视图,比如改变视图的状态、以及显示或隐藏某个控件等。那在这个时候,之前绘制出的视图其实就已经过期了,此时我们就应该对视图进行重绘。

调用视图的setVisibility()、setEnabled()、setSelected()等方法时都会导致视图重绘,而如果我们想要手动地强制让视图进行重绘,可以调用invalidate()方法来实现。当然了,setVisibility()、setEnabled()、setSelected()等方法的内部其实也是通过调用invalidate()方法来实现的,那么就让我们来看一看invalidate()方法的代码是什么样的吧。

View的源码中会有数个invalidate()方法的重载和一个invalidateDrawable()方法,当然它们的原理都是相同的,因此我们只分析其中一种,代码如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void invalidate(boolean invalidateCache) {
    if (ViewDebug.TRACE_HIERARCHY) {
        ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
    }
    if (skipInvalidate()) {
        return;
    }
    if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
            (invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) ||
            (mPrivateFlags & INVALIDATED) != INVALIDATED || isOpaque() != mLastIsOpaque) {
        mLastIsOpaque = isOpaque();
        mPrivateFlags &= ~DRAWN;
        mPrivateFlags |= DIRTY;
        if (invalidateCache) {
            mPrivateFlags |= INVALIDATED;
            mPrivateFlags &= ~DRAWING_CACHE_VALID;
        }
        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
            if (p != null && ai != null && ai.mHardwareAccelerated) {
                p.invalidateChild(this, null);
                return;
            }
        }
        if (p != null && ai != null) {
            final Rect r = ai.mTmpInvalRect;
            r.set(0, 0, mRight - mLeft, mBottom - mTop);
            p.invalidateChild(this, r);
        }
    }
}
在这个方法中首先会调用skipInvalidate()方法来判断当前View是否需要重绘,判断的逻辑也比较简单,如果View是不可见的且没有执行任何动画,就认为不需要重绘了。之后会进行透明度的判断,并给View添加一些标记位,然后在第22和29行调用ViewParent的invalidateChild()方法,这里的ViewParent其实就是当前视图的父视图,因此会调用到ViewGroup的invalidateChild()方法中,代码如下所示:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;
        if (dirty == null) {
            ......
        } else {
            ......
            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                    if (view.mLayerType != LAYER_TYPE_NONE &&
                            view.getParent() instanceof View) {
                        final View grandParent = (View) view.getParent();
                        grandParent.mPrivateFlags |= INVALIDATED;
                        grandParent.mPrivateFlags &= ~DRAWING_CACHE_VALID;
                    }
                }
                if (drawAnimation) {
                    if (view != null) {
                        view.mPrivateFlags |= DRAW_ANIMATION;
                    } else if (parent instanceof ViewRootImpl) {
                        ((ViewRootImpl) parent).mIsAnimating = true;
                    }
                }
                if (view != null) {
                    if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                            view.getSolidColor() == 0) {
                        opaqueFlag = DIRTY;
                    }
                    if ((view.mPrivateFlags & DIRTY_MASK) != DIRTY) {
                        view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag;
                    }
                }
                parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) boundingRect.left, (int) boundingRect.top,
                                (int) (boundingRect.right + 0.5f),
                                (int) (boundingRect.bottom + 0.5f));
                    }
                }
            } while (parent != null);
        }
    }
}
可以看到,这里在第10行进入了一个while循环,当ViewParent不等于空的时候就会一直循环下去。在这个while循环当中会不断地获取当前布局的父布局,并调用它的invalidateChildInParent()方法,在ViewGroup的invalidateChildInParent()方法中主要是来计算需要重绘的矩形区域,这里我们先不管它,当循环到最外层的根布局后,就会调用ViewRoot的invalidateChildInParent()方法了,代码如下所示:
?
1
2
3
4
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    invalidateChild(null, dirty);
    return null;
}
这里的代码非常简单,仅仅是去调用了invalidateChild()方法而已,那我们再跟进去瞧一瞧吧:
?
1
2
3
4
5
6
7
8
public void invalidateChild(View child, Rect dirty) {
    checkThread();
    if (LOCAL_LOGV) Log.v(TAG, Invalidate child:  + dirty);
    mDirty.union(dirty);
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
}
这个方法也不长,它在第6行又调用了scheduleTraversals()这个方法,那么我们继续跟进:
?
1
2
3
4
5
6
public void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        sendEmptyMessage(DO_TRAVERSAL);
    }
}
可以看到,这里调用了sendEmptyMessage()方法,并传入了一个DO_TRAVERSAL参数。了解Android异步消息处理机制的朋友们都会知道,任务一个Handler都可以调用sendEmptyMessage()方法来发送消息,并且在handleMessage()方法中接收消息,而如果你看一下ViewRoot的类定义就会发现,它是继承自Handler的,也就是说这里调用sendEmptyMessage()方法出的消息,会在ViewRoot的handleMessage()方法中接收到。那么赶快看一下handleMessage()方法的代码吧,如下所示:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void handleMessage(Message msg) {
    switch (msg.what) {
    case DO_TRAVERSAL:
        if (mProfile) {
            Debug.startMethodTracing(ViewRoot);
        }
        performTraversals();
        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
        break;
    ......
}
熟悉的代码出现了!这里在第7行调用了performTraversals()方法,这不就是我们在前面一篇文章中学到的视图绘制的入口吗?虽然经过了很多辗转的调用,但是可以确定的是,调用视图的invalidate()方法后确实会走到performTraversals()方法中,然后重新执行绘制流程。之后的流程就不需要再进行描述了吧,可以参考 Android视图绘制流程完全解析,带你一步步深入了解View(二) 这一篇文章。

了解了这些之后,我们再回过头来看看刚才的selectDrawable()方法中到底做了什么才能够控制背景图的改变,代码如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public boolean selectDrawable(int idx) {
    if (idx == mCurIndex) {
        return false;
    }
    final long now = SystemClock.uptimeMillis();
    if (mDrawableContainerState.mExitFadeDuration > 0) {
        if (mLastDrawable != null) {
            mLastDrawable.setVisible(false, false);
        }
        if (mCurrDrawable != null) {
            mLastDrawable = mCurrDrawable;
            mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
        } else {
            mLastDrawable = null;
            mExitAnimationEnd = 0;
        }
    } else if (mCurrDrawable != null) {
        mCurrDrawable.setVisible(false, false);
    }
    if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
        Drawable d = mDrawableContainerState.mDrawables[idx];
        mCurrDrawable = d;
        mCurIndex = idx;
        if (d != null) {
            if (mDrawableContainerState.mEnterFadeDuration > 0) {
                mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
            } else {
                d.setAlpha(mAlpha);
            }
            d.setVisible(isVisible(), true);
            d.setDither(mDrawableContainerState.mDither);
            d.setColorFilter(mColorFilter);
            d.setState(getState());
            d.setLevel(getLevel());
            d.setBounds(getBounds());
        }
    } else {
        mCurrDrawable = null;
        mCurIndex = -1;
    }
    if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
        if (mAnimationRunnable == null) {
            mAnimationRunnable = new Runnable() {
                @Override public void run() {
                    animate(true);
                    invalidateSelf();
                }
            };
        } else {
            unscheduleSelf(mAnimationRunnable);
        }
        animate(true);
    }
    invalidateSelf();
    return true;
}
这里前面的代码我们可以都不管,关键是要看到在第54行一定会调用invalidateSelf()方法,这个方法中的代码如下所示:
?
1
2
3
4
5
6
public void invalidateSelf() {
    final Callback callback = getCallback();
    if (callback != null) {
        callback.invalidateDrawable(this);
    }
}
可以看到,这里会先调用getCallback()方法获取Callback接口的回调实例,然后再去调用回调实例的invalidateDrawable()方法。那么这里的回调实例又是什么呢?观察一下View的类定义其实你就知道了,如下所示:
?
1
2
3
4
public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,
AccessibilityEventSource {
    ......
}
View类正是实现了Callback接口,所以刚才其实调用的就是View中的invalidateDrawable()方法,之后就会按照我们前面分析的流程执行重绘逻辑,所以视图的背景图才能够得到改变的。
 

另外需要注意的是,invalidate()方法虽然最终会调用到performTraversals()方法中,但这时measure和layout流程是不会重新执行的,因为视图没有强制重新测量的标志位,而且大小也没有发生过变化,所以这时只有draw流程可以得到执行。而如果你希望视图的绘制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而应该调用requestLayout()了。这个方法中的流程比invalidate()方法要简单一些,但中心思想是差不多的,这里也就不再详细进行分析了。

这样的话,我们就将视图状态以及重绘的工作原理都搞清楚了,相信大家对View的理解变得更加深刻了
Android的硬件加速
Android从3.0(API Level 11)开始,在绘制View的时候支持硬件加速,充分利用GPU的特性,使得绘制更加平滑,但是会多消耗一些内存。

      开启或关闭硬件加速:

      由于硬件加速自身并非完美无缺,所以Android提供选项来打开或者关闭硬件加速,默认是关闭。可以在4个级别上打开或者关闭硬件加速:

      Application级别:<applicationandroid:hardwareAccelerated="true" ...>

      Activity级别:<activity android:hardwareAccelerated="false" ...>

      Window级别:

getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
      注意:目前为止,Android还不支持在Window级别关闭硬件加速。

      View级别:

myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
      注意:目前为止,Android还不支持在View级别开启硬件加速。

      检测当前是否启用了硬件加速:

复制代码
// 方法一
// 此方法返回true,如果myView挂在一个开启了硬件加速的Window之下,
// 也就是说,它在绘制的时候不一定使用了硬件加速,getDrawingCache
myView.isHardwareAccelerated();
                        
// 方法二
// 返回true,如果canvas在绘制的时候启用了硬件加速
// 尽量采用此方法来判断是否开启了硬件加速
canvas.isHardwareAccelerated();
复制代码
      理解View的绘制模型:

      1.没有硬件加速:invalidate the view hierarchy ------> draw the view hierarchy

      2.有硬件加速:invalidate the view hierarchy ------> record and update the display list ------> draw the display list

      硬件加速的限制:

      目前,Android对硬件加速的支持并非完美,有些绘制操作在开启硬件加速的情况下不能正常工作(具体的列表可以参考Android开发者文档)。

      不过Android可以保证内置的组件和应用支持硬件加速。因此,如果应用中只使用了标准UI组件,可以放心开启硬件加速。

      随着Android的版本升级,相信一段时间之后,硬件加速可以得到完美的支持。

      开启硬件加速之后的异常反应:

      1.某些UI元素没有显示:可能是没有调用invalidate

      2.某些UI元素没有更新:可能是没有调用invalidate

      3.绘制不正确:可能使用了不支持硬件加速的操作, 需要关闭硬件加速或者绕过该操作

      4.抛出异常:可能使用了不支持硬件加速的操作, 需要关闭硬件加速或者绕过该操作

      本文基本上是按照Android开发者文档来写的,具体细节可以参考这里http://developer.android.com/guide/topics/graphics/hardware-accel.html
ScrollView的属性(纵向的用HorizontalScrollView)
android:background  设置背景色/背景图片。可以通过以下两种方法设置背景为透明:”@android:color/transparent”和”@null”。注意 TextView默认是透明的,不用写此属性,但是Buttom/ImageButton/ImageView想透明的话就得写这个属性了。
android:clickable  是否响应点击事件。
android:contentDescription  设置View的备注说明,作为一种辅助功能提供,为一些没有文字描述的View提供说明,如ImageButton。这里在界面上不会有效果,自己在程序中控制,可临时放一点字符串数据。
android:drawingCacheQuality  设置绘图时半透明质量。有以下值可设置:auto(默认,由框架决定)/high(高质量,使用较高的颜色深度,消耗更多的内存)/low(低质量,使用较低的颜色深度,但是用更少的内存)。
android:duplicateParentState  如果设置此属性,将直接从父容器中获取绘图状态(光标,按下等)。见下面代码部分,注意根据目前测试情况仅仅是获取绘图状态,而没有获取事件,也就是你点一下LinearLayout时Button有被点击的效果,但是不执行点击事件。
android:fadingEdge  设置拉滚动条时,边框渐变的放向。none(边框颜色不变),horizontal(水平方向颜色变淡),vertical(垂直方向颜色变淡)。参照fadingEdgeLength的效果图
android:fadingEdgeLength  设置边框渐变的长度。
android:fitsSystemWindows  设置布局调整时是否考虑系统窗口(如状态栏)
android:focusable  设置是否获得焦点。若有requestFocus()被调用时,后者优先处理。注意在表单中想设置某一个如EditText获取焦点,光设置这个是不行的,需要将这个EditText前面的focusable都设置为false才行。在Touch模式下获取焦点需要设置 focusableInTouchMode为true。
android:focusableInTouchMode  设置在Touch模式下View是否能取得焦点。
android:hapticFeedbackEnabled  设置长按时是否接受其他触摸反馈事件。这里模拟器没有试出效果,难道是多点触摸?找不到资料可以找找performHapticFeedback或HapticFeedback这个关键字的资料看看。
android:id  给当前View设置一个在当前layout.xml中的唯一编号,可以通过调用View.findViewById() 或Activity.findViewById()根据这个编号查找到对应的View。不同的layout.xml之间定义相同的id不会冲突。格式如”@+id/btnName”
android:isScrollContainer  设置当前View为滚动容器。这里没有测试出效果来,ListView/ GridView/ ScrollView根本就不用设置这个属性,而EdidText设置android:scrollbars也能出滚动条。
android:keepScreenOn  View在可见的情况下是否保持唤醒状态。

 

常在LinearLayout使用该属性,但是模拟器这里没有效果。
android:longClickable  设置是否响应长按事件.
android:minHeight  设置视图最小高度
android:minWidth  设置视图最小宽度度
android:nextFocusDown  设置下方指定视图获得下一个焦点。焦点移动是基于一个在给定方向查找最近邻居的算法。如果指定视图不存在,移动焦点时将报运行时错误。可以设置imeOptions= actionDone,这样输入完即跳到下一个焦点。
android:nextFocusLeft  设置左边指定视图获得下一个焦点。
android:nextFocusRight  设置右边指定视图获得下一个焦点。
android:nextFocusUp  设置上方指定视图获得下一个焦点。
android:onClick  点击时从上下文中调用指定的方法。这里指定一个方法名称,一般在Activity定义符合如下参数和返回值的函数并将方法名字符串指定为该值即可:

 

public void onClickButton(View view)

android:onClick=” onClickButton”
android:padding  设置上下左右的边距,以像素为单位填充空白。
android:paddingBottom  设置底部的边距,以像素为单位填充空白。
android:paddingLeft  设置左边的边距,以像素为单位填充空白。
android:paddingRight  设置右边的边距,以像素为单位填充空白。.
android:paddingTop  设置上方的边距,以像素为单位填充空白。
android:saveEnabled  设置是否在窗口冻结时(如旋转屏幕)保存View的数据,默认为true,但是前提是你需要设置id才能自动保存,参见这里。
android:scrollX  以像素为单位设置水平方向滚动的的偏移值,在GridView中可看的这个效果。
android:scrollY  以像素为单位设置垂直方向滚动的的偏移值
android:scrollbarAlwaysDrawHorizontalTrack  设置是否始终显示垂直滚动条。这里用ScrollView、ListView测试均没有效果。
android:scrollbarAlwaysDrawVerticalTrack  设置是否始终显示垂直滚动条。这里用ScrollView、ListView测试均没有效果。
android:scrollbarDefaultDelayBeforeFade  设置N毫秒后开始淡化,以毫秒为单位。
android:scrollbarFadeDuration  设置滚动条淡出效果(从有到慢慢的变淡直至消失)时间,以毫秒为单位。Android2.2中滚动条滚动完之后会消失,再滚动又会出来,在1.5、1.6版本里面会一直显示着。
android:scrollbarSize  设置滚动条的宽度。
android:scrollbarStyle  设置滚动条的风格和位置。设置值:insideOverlay、insideInset、outsideOverlay、outsideInset。这里没有试出太多效果,以下依次是outsideOverlay与outsideInset效果截图比较:
android:scrollbarThumbHorizontal  设置水平滚动条的drawable(如颜色)。
android:scrollbarThumbVertical  设置垂直滚动条的drawable(如颜色).
android:scrollbarTrackHorizontal  设置水平滚动条背景(轨迹)的色drawable(如颜色)
android:scrollbarTrackVertical  

设置垂直滚动条背景(轨迹)的drawable注意直接设置颜色值如”android:color/white”将得出很难看的效果,甚至都不理解这个属性了,这里可以参见ApiDemos里res/drawable/ scrollbar_vertical_thumb.xml和scrollbar_vertical_track.xml,设置代码为:android:scrollbarTrackVertical ="@drawable/scrollbar_vertical_track"

android:scrollbars  设置滚动条显示。none(隐藏),horizontal(水平),vertical(垂直)。见下列代码演示使用该属性让EditText内有滚动条。但是其他容器如LinearLayout设置了但是没有效果。
android:soundEffectsEnabled  设置点击或触摸时是否有声音效果
android:tag  设置一个文本标签。可以通过View.getTag()或 for with View.findViewWithTag()检索含有该标签字符串的View。但一般最好通过ID来查询View,因为它的速度更快,并且允许编译时类型检查。
android:visibility  设置是否显示View。设置值:visible(默认值
AsyncTask、View.post(Runnable)、ViewTreeObserver三种方式总结frame animation自动启动
在一些需求中,需要在程序运行时动画自动启动,我们也知道在android提供的Tween Animation和frame animation。但是当使用frame animation时候,启动Frame Animation动画的代码anim.start();不能在OnCreate()中,因为在OnCreate()中AnimationDrawable还没有完全的与ImageView绑定,在OnCreate()中启动动画,就只能看到第一张图片。现在问题是如何才能让程序启动时自动的启动动画?可以试一下在onStart方法中,但是结果同样不能如我们所愿。这样不行,继续尝试,使用Handler试一下!代码如下:

1
2
3
4
5
6
7
8
private Runnable runnable= new Runnable() {
            public void run() {
                frameAnimation.start();
            }
            };
Handler handler=  new Handler();
//在onCreate方法中:
handler.post(runnable); 
handler对象将通过post方法,将里面的Runnable对象放到UI执行队列中,UI消费这个队列,调用Runnable的run方法。这里并不生成新的线程,此时的 Runnable 是运行在UI所在的主线程中。但是这种方法也是不行!

下面即是总结的三种自动启动frame animation的方法:

首先使用AsyncTask:Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的。AsyncTask使创建需要与用户界面交互的长时间运行的任务变得更简单。不需要借助线程和Handler即可实现。对于AsyncTask这里就不多说了,也就是用到一点。

imageV.setBackgroundResource(R.anim.myframeanimation);
        frameAnim = (AnimationDrawable) imageV.getBackground();
class RunAnim extends AsyncTask<String, String, String>{
 
        @Override
        protected String doInBackground(String... params) { 
                if(!frameAnim.isRunning()){
                frameAnim.stop();
                frameAnim.start();  
                }
            return "";
        }
         
    }
//onCreate方法中执行
RunAnim runAnim=new RunAnim();
        runAnim.execute("");
这样就能在是程序自动执行frame animation了。

其次使用View.post(Runnable)的方式:

imageV.post(new Runnable(){
 
    @Override
    public void run() {
        frameAnim.start();
    }
     
});
文档:boolean android.view.View .post(Runnable action)

Causes the Runnable to be added to the message queue. The runnable will be run on the user interface thread.即可把你的Runnable对象增加到UI线程中运行。

这样也能正常启动frame Animation。

第三就是使用ViewTreeObserver.OnPreDrawListener listener:当一个视图树将要绘制时产生事件,可以添加一个其事件处理函数:onPreDraw

OnPreDrawListener opdl=new OnPreDrawListener(){
        @Override
        public boolean onPreDraw() {
            animDraw.start();
            return true;
        }
    };
 
//onCreate方法中
imageV.getViewTreeObserver().addOnPreDrawListener(opdl);
以上即是总结的三种自动启动frame animation的方法,当然,对于android线程的处理,UI更新操作实现,肯定有其他的方法。以上描述中如有错误,还望多多包含与指教!!
android自定义view获取bitmap方法
一、        使用BitmapFactory解析图片
     
 代码如下	复制代码
// --> 使用BitmapFactory解析图片
           public void myUseBitmapFactory(Canvas canvas){
           // 定义画笔
              Paint paint = new Paint();
           // 获取资源流
              Resources rec = getResources();
              InputStream in = rec.openRawResource(R.drawable.haha);
           // 设置图片
              Bitmap bitmap =BitmapFactory.decodeStream(in);
           // 绘制图片
              canvas.drawBitmap(bitmap, 0,20, paint);         
           }
二、        使用BitmapDrawable解析图片
 
 
 代码如下	复制代码
       // --> 使用BitmapDrawable解析图片
           public void myUseBitmapDrawable(Canvas canvas){
           // 定义画笔
              Paint paint = new Paint();
           // 获得资源
              Resources rec = getResources();
           // BitmapDrawable
              BitmapDrawable bitmapDrawable = (BitmapDrawable) rec.getDrawable(R.drawable.haha);
           // 得到Bitmap
              Bitmap bitmap = bitmapDrawable.getBitmap();
           // 在画板上绘制图片
              canvas.drawBitmap(bitmap, 20,120,paint);
           }
 
三、        使用InputStream和BitmapDrawable绘制
 
 代码如下	复制代码
 
       // --> 使用InputStream和BitmapDrawable解析图片
           public void myUseInputStreamandBitmapDrawable(Canvas canvas){
           // 定义画笔
              Paint paint = new Paint();
           // 获得资源
              Resources rec = getResources();
           // InputStream得到资源流
              InputStream in = rec.openRawResource(R.drawable.haha);
           // BitmapDrawable 解析数据流
              BitmapDrawable bitmapDrawable =  new BitmapDrawable(in);
           // 得到图片
              Bitmap bitmap = bitmapDrawable.getBitmap();
           // 绘制图片
              canvas.drawBitmap(bitmap, 100, 100,paint);
           }
Android上常见度量单位【xdpi、hdpi、mdpi、ldpi】解读
术语和概念 
屏幕尺寸 
屏幕的物理尺寸,以屏幕的对角线长度作为依据(比如 2.8寸, 3.5寸)。 
简而言之, Android把所有的屏幕尺寸简化为三大类:大,正常,和小。 
程序可以针对这三种尺寸的屏幕提供三种不同的布局方案,然后系统会负责把你的布局方案以合适的方式渲染到对应的屏幕上,这个过程是不需要程序员用代码来干预的。

屏幕长宽比 
屏幕的物理长度与物理宽度的比例。程序可以为制定长宽比的屏幕提供制定的素材,只需要用系统提供的资源分类符long和 notlong。

分辨率 
屏幕上拥有的像素的总数。注意,虽然大部分情况下分辨率都被表示为“宽度×长度”,但分辨率并不意味着屏幕长宽比。在 Android系统中,程序一般并不直接处理分辨率。

密度 
以屏幕分辨率为基础,沿屏幕长宽方向排列的像素。 
密度较低的屏幕,在长和宽方向都只有比较少的像素,而高密度的屏幕通常则会有很多 ——甚至会非常非常多——像素排列在同一区域。屏幕的密度是非常重要的,举个例子,长宽以像素为单位定义的界面元素(比如一个按钮),在低密度的屏幕上会 显得很大,但在高密度的屏幕上则会显得很小。

密度无关的像素( DIP ) 
指一个抽象意义上的像素,程序用它来定义界面元素。它作为一个与实际密度无关的单位,帮助程序员构建一个布局方案(界面元素的宽度,高度,位置)。 
一个与密度无关的像素,在逻辑尺寸上,与一个位于像素密度为 160DPI的屏幕上的像素是一致的,这也是Android平台所假定的默认显示设备。在运行的时候,平台会以目标屏幕的密度作为基准,“透明地”处理所 有需要的DIP缩放操作。要把密度无关像素转换为屏幕像素,可以用这样一个简单的公式: pixels = dips * (density / 160)。举个例子,在 DPI为 240的屏幕上, 1个 DIP等于 1.5个物理像素。我们强烈推荐你用 DIP来定义你程序的界面布局,因为这样可以保证你的 UI在各种分辨率的屏幕上都可以正常显示。

为了简化程序员面在对各种分辨率时的困扰,也为了具备各种分辨率的平台都可以直接运行这些程序, Android平台将所有的屏幕以密度和分辨率为分类方式,各自分成了三类: 
·三种主要的尺寸:大,正常,小; 
·三种不同的密度:高( hdpi),中( mdpi)和低( ldpi)。 【DPI是“dot per inch”的缩写,每英寸像素数。】
如果需要的话,程序可以为各种尺寸的屏幕提供不同的资源(主要是布局),也可以为 各种密度的屏幕提供不同的资源(主要是位图)。除此以外,程序不需要针对屏幕的尺寸或者密度作出任何额外的处理。在执行的时候,平台会根据屏幕本身的尺寸 与密度特性,自动载入对应的资源,并把它们从逻辑像素( DIP,用于定义界面布局)转换成屏幕上的物理像素。

 

 

关于Android的nodpi,xhdpi,hdpi,mdpi,ldpi

首先是几个基本概念:

1.屏幕尺寸Screen size

即显示屏幕的实际大小,按照屏幕的对角线进行测量。

为简单起见,Android把所有的屏幕大小分为四种尺寸:小,普通,大,超大(分别对应:small, normal, large, and extra large).

应用程序可以为这四种尺寸分别提供不同的自定义屏幕布局-平台将根据屏幕实际尺寸选择对应布局进行渲染,这种选择对于程序侧是透明的。

2.屏幕长宽比Aspect ratio

长宽比是屏幕的物理宽度与物理高度的比例关系。应用程序可以通过使用限定的资源来为指定的长宽比提供屏幕布局资源。

3.屏幕分辨率Resolution

在屏幕上显示的物理像素总和。需要注意的是:尽管分辨率通常用宽x高表示,但分辨率并不意味着具体的屏幕长宽比。

在Andorid系统中,应用程序不直接使用分辨率。

4.密度Density

根据像素分辨率,在屏幕指定物理宽高范围内能显示的像素数量。

在同样的宽高区域,低密度的显示屏能显示的像素较少,而高密度的显示屏则能显示更多的像素。

屏幕密度非常重要,因为其它条件不变的情况下,一共宽高固定的UI组件(比如一个按钮)在在低密度的显示屏上显得很大, 而在高密度显示屏上看起来就很小。

为简单起见,Android把所有的屏幕分辨率也分为四种尺寸:小,普通,大,超大(分别对应:small, normal, large, and extra large).

应用程序可以为这四种尺寸分别提供不同的资源-平台将透明的对资源进行缩放以适配指定的屏幕分辨率。

5.设备独立像素Density-independent pixel (dp)

应用程序可以用来定义UI组件的虚拟像素单元,通过密度无关的方式来描述布局尺寸和位置。

一个设备独立像素相当于一个160 dpi屏幕上的物理像素。

在程序运行时,系统根据屏幕的实际密度透明的处理任何需要缩放的设备独立像素单元,设备独立像素转换成屏幕实际像素的换算很简单:pixels = dps * (density / 160).

例如在240 dpi的屏幕上,1个设备独立像素等于1.5物理像素.为确保UI组件在不同的屏幕都能合适的展示,强烈建议使用设备独立像素单元来定义你的应用程序UI。

四种屏幕尺寸分类:: small, normal, large, and xlarge

四种密度分类: ldpi (low), mdpi (medium), hdpi (high), and xhdpi (extra high)

需要注意的是: xhdpi是从  Android   2.2 (API Level 8)才开始增加的分类.

xlarge是从Android 2.3 (API Level 9)才开始增加的分类.

DPI是“dot per inch”的缩写,每英寸像素数。

一般情况下的普通屏幕:ldpi是120,mdpi是160,hdpi是240,xhdpi是320。

参考:http://developer.android.com/images/screens_support/screens-ranges.png

两种获取屏幕分辨率信息的方法:

DisplayMetrics metrics = new DisplayMetrics();

Display display = activity.getWindowManager().getDefaultDisplay();

display.getMetrics(metrics);

//这里得到的像素值是设备独立像素dp

//DisplayMetrics metrics=activity.getResources().getDisplayMetrics(); 这样获得的参数信息不正确,不要使用这种方式。

不能使用android.content.res.Resources.getSystem().getDisplayMetrics()。这个得到的宽和高是空的。

复制代码
    private void initResolutionStr(Context context) {
        if (ApiConfig.getResolutionStr() == null || ApiConfig.getResolutionStr().equals("")) {
            WindowManager winMgr = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
            Display display = winMgr.getDefaultDisplay();
            int height = display.getHeight();
            int width = display.getWidth();
            String resolution = height > width ? height + "x" + width : width + "x" + height;
            ApiConfig.setResolutionStr(resolution);
            // densityDpi = 120dpi is ldpi, densityDpi = 160dpi is mdpi,
            // densityDpi = 240dpi is hdpi, densityDpi = 320dpi is xhdpi
            DisplayMetrics dm = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(dm);
            int densityDpi = dm.densityDpi;
            ApiConfig.setDensityDpi(densityDpi);
        }
    }
复制代码

 

 

如果需要为Android pad定制资源文件,则res目录下的目录可能为:

drawable

drawable-ldpi

drawable-mdpi

drawable-hdpi

drawable-xhdpi

drawable-nodpi

drawable-nodpi-1024×600

drawable-nodpi-1280×800

drawable-nodpi-800×480

values

values-ldpi

values-mdpi

values-hdpi

values-xhdpi

values-nodpi

values-nodpi-1024×600

values-nodpi-1280×800

values-nodpi-800×480

Android上常见度量单位:
  px(像素):屏幕上的点,绝对长度,与硬件相关。
  in(英寸):长度单位。
  mm(毫米):长度单位。
  pt(磅):1/72英寸,point。
  dp(与密度无关的像素):一种基于屏幕密度的抽象单位。在每英寸160点的显示器上,1dp = 1px。
  dip:Density-independent pixel,同dp相同。
  sp:在dp的基础上,还与比例无关,个人理解为是一个矢量图形单位。

引入dp/dip的原因: 
  过去,程序员通常以像素为单位设计计算机用户界面。例如,定义一个宽度为300像素的表单字段,列之间的间距为5个像素,图标大小为16×16像素 等。这样处理的问题在于,如果在一个每英寸点数(dpi)更高的新显示器上运行该程序,则用户界面会显得很小。在有些情况下,用户界面可能会小到难以看清 内容。与分辨率无关的度量单位可以解决这一问题。

如何计算密度(请参照原帖:http://www.devdiv.com/thread-28610-1-1.html);
1.标准是240*320画在1.5*2平方inch上。那么像每平方英寸有240*320/(1.5*2)=25600点,也就是一平方英寸的像素点为25600,所以dpi取为它的平方根160;如果你的dpi是120,那么它的密度就是0.75.
2.密度不只是与width有关,还与height有关,所以不管width是1.8还是1.3,它的密度都有可能是1;比如width是1.8,只要它 的height是3/1.8的话,如果pixel为240*320的话,它的密度仍旧是1;同样如果width为1.3,只要它的 height为3/1.3的话,像素点为240*320,则密度也是1.
3.320*480/(1.5*2)得到单位平方英寸的点为51200,所以单位平方英寸是240*320画在1.5*2屏幕的2倍。但是这是平方英寸啊,算密度的时候要开平方的啊,所以应该是2开平方,是1.414吧,大致密度为1.5。

如何做到与密度无关:
  如果屏幕密度为160,这时dp和sp和px是一样的。1dp=1sp=1px,但如果使用px作单位,如果屏幕大小不变(假设还是3.2寸),而屏 幕密度变成了320。那么原来TextView的宽度设成160px,在密度为320的3.2 寸屏幕里看要比在密度为160的3.2寸屏幕上看短了一半。但如果设置成160dp或160sp的话。系统会自动将width属性值设置成320px的。 也就是160 * 320 / 160。其中320 / 160可称为密度比例因子。也就是说,如果使用dp和sp,系统会根据屏幕密度的变化自动进行转换。官方文档总结的计算公式为:pixels = dps * (density /160).


附:
        传说iPhone/Mac的设计从一开始就考虑到对任意分辨率的支持,iOS的所有介面元素用的都已经是矢量化了的图片,UI界面是系统级别的与密度无关;而Android虽然支持任意分辨率,但不是系统全局的,求证。
Android 中的防锯齿
在Android中,目前,我知道有两种出现锯齿的情况。 
      1)当我们用Canvas绘制位图的时候,如果对位图进行了选择,则位图会出现锯齿。

      2)在用View的RotateAnimation做动画时候,如果View当中包含有大量的图形,也会出现锯齿。 
      我们分别以这两种情况加以考虑。

一,用Canvas绘制位的的情况。
       在用Canvas绘制位图时,一般地,我们使用drawBitmap函数家族,在这些函数中,都有一个Paint参数,要做到防止锯齿,我们就要使用到这个参数。

       如下:首先在你的构造函数中,需要创建一个Paint。

       Paint mPaint = new Paint()

       然后,您需要设置两个参数:

       1) mPaint.setAntiAlias(); 
       2) mPaint.setBitmapFilter(true)。

       第一个函数是用来防止边缘的锯齿,第二个函数是用来对位图进行滤波处理。最后,在画图的时候,调用drawBitmap函数,只需要将整个Paint传入即可。

二,RotateAnimation
       有时候,当你做RotateAnimation时,你会发现,讨厌的锯齿又出现了。这个时候,由于你不能控制位图的绘制,只能用其他方法来实现防止锯齿。另外,如果你画的位图很多。不想每个位图的绘制都传入一个Paint。还有的时候,你不可能控制每个窗口的绘制的时候,您就需要用下面的方法来处理------对整个Canvas进行处理。
       1)在您的构造函数中,创建一个Paint滤波器。

       PaintFlagsDrawFilter mSetfil = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG);

       第一个参数是你要清除的标志位,第二个参数是你要设置的标志位。此处设置为对位图进行滤波。

       2)当你在画图的时候,如果是View则在onDraw当中,如果是ViewGroup则在dispatchDraw中调用如下函数。

       canvas.setDrawFilter( mSetfil );
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG)); 

三,Drawable
       最后,另外,在Drawable类及其子类中,也有函数setFilterBitmap可以用来对Bitmap进行滤波处理,这样,当你选择Drawable时,会有抗锯齿的效果。
Android缩放drawable
一、  相关概念
1.        Drawable就是一个可画的对象,其可能是一张位图(BitmapDrawable),也可能是一个图形(ShapeDrawable),还有可能是一个图层(LayerDrawable),我们根据画图的需求,创建相应的可画对象
2.        Canvas画布,绘制的目的区域,用于绘图
3.        Bitmap位图,用于图的处理
4.        Matrix矩阵,此例中用于操作图片
二、  步骤
1.        把drawable画到位图对象上
2.        对位图对象做缩放(或旋转等)操作
3.        把位图再转换成drawable
三、示例
         static Bitmap drawableToBitmap(Drawable drawable)// drawable 转换成bitmap
         {
                   int width = drawable.getIntrinsicWidth();   // 取drawable的长宽
                   int height = drawable.getIntrinsicHeight();
                 Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888:Bitmap.Config.RGB_565;         // 取drawable的颜色格式
                   Bitmap bitmap = Bitmap.createBitmap(width, height, config);     // 建立对应bitmap
                   Canvas canvas = new Canvas(bitmap);         // 建立对应bitmap的画布
                   drawable.setBounds(0, 0, width, height);
                   drawable.draw(canvas);      // 把drawable内容画到画布中
                   return bitmap;
         }

         static Drawable zoomDrawable(Drawable drawable, int w, int h)
         {
                   int width = drawable.getIntrinsicWidth();
                   int height= drawable.getIntrinsicHeight();
                   Bitmap oldbmp = drawableToBitmap(drawable);// drawable转换成bitmap
                   Matrix matrix = new Matrix();   // 创建操作图片用的Matrix对象
                   float scaleWidth = ((float)w / width);   // 计算缩放比例
                   float scaleHeight = ((float)h / height);
                   matrix.postScale(scaleWidth, scaleHeight);         // 设置缩放比例
                   Bitmap newbmp = Bitmap.createBitmap(oldbmp, 0, 0, width, height, matrix, true);       // 建立新的bitmap,其内容是对原bitmap的缩放后的图
                   return new BitmapDrawable(newbmp);       // 把bitmap转换成drawable并返回
         }
}
四、其它
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
public class FunctionUtil {
 
 public static Bitmap imageScale(Bitmap bitmap, int dst_w, int dst_h){
  
  int  src_w = bitmap.getWidth();
  int  src_h = bitmap.getHeight();
  float scale_w = ((float)dst_w)/src_w;
  float  scale_h = ((float)dst_h)/src_h;
  Matrix  matrix = new Matrix();
  matrix.postScale(scale_w, scale_h);
  Bitmap dstbmp = Bitmap.createBitmap(bitmap, 0, 0, src_w, src_h, matrix, true);
  
  return dstbmp;
 }
或者使用Bitmap.createScaledBitmap(bitmap, width, heigth, true);方法,
需要指定缩放后的图片大小尺寸。
 
 
 public static Bitmap drawabletoBitmap(Drawable drawable){
  
  int width = drawable.getIntrinsicWidth();
  int height = drawable.getIntrinsicWidth();
  
  Bitmap bitmap = Bitmap.createBitmap(width, height, drawable.getOpacity() != PixelFormat.OPAQUE ?
     Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
  Canvas canvas = new Canvas(bitmap);
  drawable.setBounds(0, 0, width, height);
  
  drawable.draw(canvas);
  
  return bitmap;
 }
 
 
 public static Bitmap getRCB(Bitmap bitmap, float roundPX) //RCB means Rounded Corner Bitmap
 {
  Bitmap dstbmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
  Canvas canvas = new Canvas(dstbmp);
  
  final int color = 0xff424242;
  final Paint paint = new Paint();
  final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
  final RectF rectF = new RectF(rect);
  paint.setAntiAlias(true);
  canvas.drawARGB(0, 0, 0, 0);
  paint.setColor(color);
  canvas.drawRoundRect(rectF, roundPX, roundPX, paint);
  paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
  canvas.drawBitmap(bitmap, rect, rect, paint);
  return dstbmp;
 }
}
Android 图片平铺实现方式
Android 框架允许创建一个 drawable 包含一个 bitmap 并用于平铺、缩放和对齐处理。当我们需要让背景使用下面图片进行平铺时:

1)第一种利用系统提供的api实现

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);

//bitmap = Bitmap.createBitmap(100, 20, Config.ARGB_8888);
BitmapDrawable drawable = new BitmapDrawable(bitmap);
drawable.setTileModeXY(TileMode.REPEAT , TileMode.REPEAT );
drawable.setDither(true);
view.setBackgroundDrawable(drawable);

tileMode 属性就是用于定义背景的显示模式:

disabled 
默认值,表示不使用平铺 
clamp 
复制边缘色彩 
repeat 
X、Y 轴进行重复图片显示,也就是我们说要说的平铺 
mirror 
在水平和垂直方向上使用交替镜像的方式重复图片的绘制

2)第二种我们使用xml来轻松实现

< bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:src="@drawable/img"
android:tileMode="repeat" />

3)第三种 自己画出来

public static Bitmap createRepeater(int width, Bitmap src){
int count = (width + src.getWidth() - 1) / src.getWidth();
 
Bitmap bitmap = Bitmap.createBitmap(width, src.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);

for(int idx = 0; idx < count; ++ idx){


canvas.drawBitmap(src, idx * src.getWidth(), 0, null);
}
 
return bitmap;
}
注:前两种可能出现BUG,第三种比较实用

首先,两个单词的中文意思分别是dither(抖动)和tileMode(平铺)



1,先来介绍tileMode(平铺)

它的效果类似于 让背景小图不是拉伸而是多个重复(类似于将一张小图设置电脑桌面时的效果)
[html] view plaincopy 
<xml version="1.0" encoding="utf-8"?>  
<LinearLayout  
android:id="@+id/MainLayout"  
xmlns:android="http://schemas.android.com/apk/res/android"  
android:layout_width="fill_parent"  
android:layout_height="fill_parent"  
android:orientation="vertical"  
android:background="@drawable/backrepeat"  
>  

backrepeat.xml

[html] view plaincopy 
<bitmap   
    xmlns:android="http://schemas.android.com/apk/res/android"   
    android:src="@drawable/repeatimg"   
    android:tileMode="repeat"   
    android:dither="true" />   

代码方式:

[java] view plaincopy 
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);  
BitmapDrawable bd = new BitmapDrawable(bitmap);  
bd.setTileModeXY(TileMode.REPEAT , TileMode.REPEAT );  
bd.setDither(true);  
view.setBackgroundDrawable(bd);  

2,再来解释下 dither(抖动)

Dither(图像的抖动处理,当每个颜色值以低于8位表示时,对应图像做抖动处理可以实现在可显示颜色总数比较低(比如256色)时还保持较好的显示效果: Dither on Wikipedia
android画图----ShapeDrawable和shader
在这个例程中主要讲述了ShapeDrawable,以及各种Gradient 
首先初始化了 mDrawables = new ShapeDrawable[7];默认的是矩形, 
当然还有其他形状: 
  mDrawables[0] = new ShapeDrawable(new RectShape()); 
            mDrawables[1] = new ShapeDrawable(new OvalShape()); 
mDrawables[4] = new ShapeDrawable(new RoundRectShape(outerR, inset, 
                                                                 innerR)); 
            mDrawables[5] = new ShapeDrawable(new PathShape(path, 100, 100)); 
float[] outerR = new float[] { 12, 12, 12, 12, 0, 0, 0, 0 }; 
            RectF   inset = new RectF(20, 20, 20, 20); 
            float[] innerR = new float[] { 12, 12, 0, 0, 12, 12, 0, 0 }; 
            
            Path path = new Path(); 
            path.moveTo(50, 0); 
            path.lineTo(0, 50); 
            path.lineTo(50, 100); 
            path.lineTo(100, 50); 
            path.close(); 

实例化ShapeDrawable后就可以对他们进行设置: 
设置渐变 
mDrawables[0].getPaint().setShader(makeTiling()); 
设置颜色 
            mDrawables[1].getPaint().setColor(0xFF00FF00); 
设置影响 影响呢有很多 可以设置单个 也可以用组合 
PathEffect pe = new DiscretePathEffect(10, 2); 
            PathEffect pe2 = new CornerPathEffect(8); 
            mDrawables[3].getPaint().setPathEffect( 
                                                new SumPathEffect(pe2, pe)); 
其中渐变又包括: 
private static Shader makeSweep() { 
           return new SweepGradient(150, 25, new int[] { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFFF0000 }, null); 
        //return new SweepGradient(150, 25, new int[] { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF }, new float[]{0.9f,0.1f,0.9f}); 
        } 
        
        private static Shader makeLinear() { 
            return new LinearGradient(0, 0, 50, 50, 
                              new int[] { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF }, 
                              null, Shader.TileMode.REPEAT); 
        } 
        
        private  Shader makeTiling() { 
            int[] pixels = new int[] { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0}; 
           
          Bitmap bm = Bitmap.createBitmap(pixels, 2, 2,  Bitmap.Config.ARGB_8888); 
           // Bitmap bm = Bitmap.createBitmap(mBitmap, 60, 45,  Bitmap.Config.ARGB_8888); 
            
            return new BitmapShader(bm, Shader.TileMode.REPEAT, 
                                        Shader.TileMode.REPEAT); 
        } 

最后在这个实例中 可以自定义一个ShapeDrawable 
private static class MyShapeDrawable extends ShapeDrawable { 
            private Paint mStrokePaint = new Paint(Paint.LINEAR_TEXT_FLAG); 
            
            public MyShapeDrawable(Shape s) { 
                super(s); 
                mStrokePaint.setStyle(Paint.Style.STROKE); 
                mStrokePaint.setColor(Color.GREEN); 
            } 
            
            public Paint getStrokePaint() { 
                return mStrokePaint; 
            } 
            
            @Override protected void onDraw(Shape s, Canvas c, Paint p) { 
//第一个调用父类的p,第二个调用自己刚才设置的p,如果你不想使用默认的父类,默认的颜色为黑了           
    s.draw(c, p); 
                s.draw(c, mStrokePaint); 
            } 

mDrawables[6] = new MyShapeDrawable(new ArcShape(45, 360)); 
MyShapeDrawable msd = (MyShapeDrawable)mDrawables[6]; 
            msd.getStrokePaint().setStrokeWidth(1); 
        }
CreateProcess error=2问题解决
Runtime.getRuntime().exec("D:\\folder\\program.exe;");
以上代码修改为
Runtime.getRuntime().exec("D:\\folder\\program.exe");

即去掉exe后面的“;”。
三种方法实现Android平铺效果
需要实现平铺效果,大致有几下几种方法。

第一种,利用系统提供的api实现:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic); 
 
//bitmap = Bitmap.createBitmap(200, 30, Config.ARGB_8888); 
BitmapDrawable drawable = new BitmapDrawable(bitmap); 
drawable.setTileModeXY(TileMode.REPEAT , TileMode.REPEAT ); 
drawable.setDither(true); 
view.setBackgroundDrawable(drawable);  
第二种,使用xml来轻松实现,貌似是4.0以后出现的:

<bitmap xmlns:Android="http://schemas.android.com/apk/res/android"    
android:src="../../@drawable/img" 
Android:tileMode="repeat" />  
第三种,自己画出来:

public static Bitmap createRepeater(int width, Bitmap src){ 
int count = (width + src.getWidth() - 1) / src.getWidth(); 
 
Bitmap bitmap = Bitmap.createBitmap(width, src.getHeight(), Config.ARGB_8888); 
Canvas canvas = new Canvas(bitmap); 
 
    for(int idx = 0; idx < count; ++ idx){ 
        canvas.drawBitmap(src, idx * src.getWidth(), 0, null); 
     } 
 
     return bitmap; 
}  
一键锁屏
1. [代码]     
1
源码下载地址:
2
http://pan.baidu.com/share/link?shareid=3700458182&uk=3674817364
2. [文件] Dar.java ~ 654B     下载(30)     
01
package com.feng.onekeylockscreen;
02
 
03
import android.app.admin.DeviceAdminReceiver;
04
import android.content.ComponentName;
05
import android.content.Context;
06
import android.content.Intent;
07
 
08
public class Dar extends DeviceAdminReceiver{
09
 
10
    public static ComponentName getCn(Context context){
11
        return new ComponentName(context, Dar.class);
12
    }
13
 
14
    @Override
15
    public void onEnabled(Context context, Intent intent) {
16
        // TODO Auto-generated method stub
17
        super.onEnabled(context, intent);
18
    }
19
 
20
    @Override
21
    public void onDisabled(Context context, Intent intent) {
22
        // TODO Auto-generated method stub
23
        super.onDisabled(context, intent);
24
    }
25
}
3. [文件] MainActivity.java ~ 2KB     下载(14)     
01
package com.feng.onekeylockscreen;
02
 
03
import android.os.Bundle;
04
import android.app.Activity;
05
import android.app.admin.DevicePolicyManager;
06
import android.content.Intent;
07
import android.view.Menu;
08
 
09
public class MainActivity extends Activity {
10
 
11
    private DevicePolicyManager devicePolicyManager=null;
12
    private static final int REQUEST_CODE_ADD_DEVICE_ADMIN=10001;
13
     
14
    @Override
15
    protected void onCreate(Bundle savedInstanceState) {
16
        super.onCreate(savedInstanceState);
17
        devicePolicyManager = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
18
         
19
        if (devicePolicyManager.isAdminActive(Dar.getCn(this))) {
20
            devicePolicyManager.lockNow();
21
            finish();
22
        }else{
23
            startAddDeviceAdminAty();
24
        }
25
        setContentView(R.layout.activity_main);
26
    }
27
 
28
    private void startAddDeviceAdminAty(){
29
        Intent i = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
30
        i.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
31
                Dar.getCn(this));
32
        i.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
33
                "注册此组件后才能拥有锁屏功能");
34
        startActivityForResult(i, REQUEST_CODE_ADD_DEVICE_ADMIN);
35
    }
36
    @Override
37
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
38
 
39
        if (resultCode==Activity.RESULT_OK) {
40
            devicePolicyManager.lockNow();
41
            finish();
42
        }else{
43
            startAddDeviceAdminAty();
44
        }
45
 
46
        super.onActivityResult(requestCode, resultCode, data);
47
    }
48
     @Override
49
        protected void onPause() {
50
//          finish();
51
            super.onPause();
52
        }
53
 
54
        @Override
55
        public boolean onCreateOptionsMenu(Menu menu) {
56
//          getMenuInflater().inflate(R.menu.activity_main, menu);
57
            return true;
58
        }
59
 
60
         
61
}
4. [文件] device_admin.xml ~ 301B     下载(13)     
01
<?xml version="1.0" encoding="utf-8"?>
02
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
03
    <uses-policies>
04
        <limit-password />
05
        <watch-login />
06
        <reset-password />
07
        <force-lock />
08
        <wipe-data />
09
    </uses-policies>
10
</device-admin>
5. [文件] AndroidManifest.xml ~ 1KB     下载(13)     
01
<?xml version="1.0" encoding="utf-8"?>
02
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
03
    package="com.feng.onekeylockscreen"
04
    android:versionCode="1"
05
    android:versionName="1.0" >
06
 
07
    <uses-sdk
08
        android:minSdkVersion="8"
09
        android:targetSdkVersion="15" />
10
 
11
    <application
12
        android:allowBackup="true"
13
        android:icon="@drawable/ic_launcher"
14
        android:label="@string/app_name"
15
        android:theme="@style/AppTheme" >
16
        <activity
17
            android:name="com.feng.onekeylockscreen.MainActivity"
18
            android:label="@string/app_name" >
19
            <intent-filter>
20
                <action android:name="android.intent.action.MAIN" />
21
 
22
                <category android:name="android.intent.category.LAUNCHER" />
23
            </intent-filter>
24
        </activity>
25
        <receiver android:name=".Dar"
26
                    android:permission="android.permission.BIND_DEVICE_ADMIN">
27
 
28
            <meta-data android:name="android.app.device_admin"
29
                       android:resource="@xml/device_admin" />
30
 
31
            <intent-filter>
32
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
33
            </intent-filter>
34
        </receiver>
35
    </application>
36
 
37
</manifest>
6. 
卸载方法:

很多朋也安装了,但是想卸载的时候卸载失败,这个不用担心,下面我说一下操作方法

以小米1S为例

设置--安全和隐私--设备管理器--OneKeyLockScreen  取消激活

然后就可以卸载了!
ThreadGroup实则比ExecutorService更好
    用java做抓取的时候免不了要用到多线程的了,因为要同时抓取多个网站或一条线程抓取一个网站的话实在太慢,而且有时一条线程抓取同一个网站的话也比较浪费CPU资源。要用到多线程的等方面,也就免不了对线程的控制或用到线程池。   我在做我们现在的那一个抓取框架的时候,就曾经用过java.util.concurrent.ExecutorService作为线程池,关于ExecutorService的使用代码大概如下:
java.util.concurrent.Executors类的API提供大量创建连接池的静态方法:1.固定大小的线程池:

 1 package BackStage;
 2 
 3  import java.util.concurrent.Executors;
 4  import java.util.concurrent.ExecutorService;
 5 
 6  public class JavaThreadPool {
 7     public static void main(String[] args) {
 8         // 创建一个可重用固定线程数的线程池
 9          ExecutorService pool = Executors.newFixedThreadPool(2);
10         // 创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
11         Thread t1 = new MyThread();
12         Thread t2 = new MyThread();
13         Thread t3 = new MyThread();
14         Thread t4 = new MyThread();
15         Thread t5 = new MyThread();
16         // 将线程放入池中进行执行
17         pool.execute(t1);
18         pool.execute(t2);
19         pool.execute(t3);
20         pool.execute(t4);
21         pool.execute(t5);
22         // 关闭线程池
23         pool.shutdown();
24     }
25 }
26 
27 class MyThread extends Thread {
28     @Override
29     public void run() {
30         System.out.println(Thread.currentThread().getName() + "正在执行。。。");
31     }
32 }
复制代码
   后来发现ExecutorService的功能没有想像中的那么好,而且最多只是提供一个线程的容器而然,所以后来我用改用了java.lang.ThreadGroup,ThreadGroup有很多优势,最重要的一点就是它可以对线程进行遍历,知道那些线程已经运行完毕,还有那些线程在运行。关于ThreadGroup的使用代码如下:

 1 class MyThread extends Thread {
 2   boolean stopped;
 3 
 4   MyThread(ThreadGroup tg, String name) {
 5     super(tg, name);
 6     stopped = false;
 7   }
 8 
 9   public void run() {
10     System.out.println(Thread.currentThread().getName() + " starting.");
11     try {
12       for (int i = 1; i < 1000; i++) {
13         System.out.print(".");
14         Thread.sleep(250);
15         synchronized (this) {
16           if (stopped)
17             break;
18         }
19       }
20     } catch (Exception exc) {
21       System.out.println(Thread.currentThread().getName() + " interrupted.");
22     }
23     System.out.println(Thread.currentThread().getName() + " exiting.");
24   }
25 
26   synchronized void myStop() {
27     stopped = true;
28   }
29 }
30 
31 public class Main {
32   public static void main(String args[]) throws Exception {
33     ThreadGroup tg = new ThreadGroup("My Group");
34 
35     MyThread thrd = new MyThread(tg, "MyThread #1");
36     MyThread thrd2 = new MyThread(tg, "MyThread #2");
37     MyThread thrd3 = new MyThread(tg, "MyThread #3");
38 
39     thrd.start();
40     thrd2.start();
41     thrd3.start();
42 
43     Thread.sleep(1000);
44 
45     System.out.println(tg.activeCount() + " threads in thread group.");
46 
47     Thread thrds[] = new Thread[tg.activeCount()];
48     tg.enumerate(thrds);
49     for (Thread t : thrds)
50       System.out.println(t.getName());
51 
52     thrd.myStop();
53 
54     Thread.sleep(1000);
55     
56     System.out.println(tg.activeCount() + " threads in tg.");
57     tg.interrupt();
58   }
59 }
复制代码
  由以上的代码可以看出:ThreadGroup比ExecutorService多以下几个优势  
1.ThreadGroup可以遍历线程,知道那些线程已经运行完毕,那些还在运行  
2.可以通过ThreadGroup.activeCount知道有多少线程从而可以控制插入的线程数
转自: http://www.cnblogs.com/jimmy0756/archive/2011/04/18/2019439.html
JSONTokener,JSONObject,JSONArray,JSONStringer和JSONException
1.JSONTokener类
    这个类的功能就是将按照JSON(RFC 4627)格式的字符窜解析成与JSON规范一致的对象。大多数用户只是使用到这个类的构造方法和nextValue()方法。
    构造方法:
        JSONTokener(String in) ,参数是符合JSON(RFC 4627)规范的字符串。
    public method:
        Object nextValue( ) , Return the next value from the input。
    Example:
     String json = "{"
          + "  /"query/": /"Pizza/", "
          + "  /"locations/": [ 94043, 90210 ] "
          + "}";
     JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
     String query = object.getString("query");
     JSONArray locations = object.getJSONArray("locations");
  2.JSONObject类
    这是一个可变的键值对映射的集合,这个集合中键值对可增加、减少,值可以是JSONObjects,JSONArrays,Strings,Booleans,Integers,Longs,Double or NULL,总之值必须是对象的应用。
   常用方法介绍:
   构造方法:
       JSONObject( ), creates a JSONObject with no name/value mappings
       JSONObject(JSONTokener readForm), creates a new JSONObject with name/value mappings from the next object in the tokener
       JSONObject(String json), creates a new JSONObject with name/value mappings from the JSON String
  public methods:
       JSONObject accumulate(String name, Object value), Appends value to the array already mapped to name.
       Object get(String name), Returns the value mapped by name.
       JSONArray getJSONArray(String name), Returns the value mapped by name if it exists and is a JSONArray.
       JSONObject getJSONObject(String name), Return the value mapped by name if it exist and is a JSONOBject.
       String toString( ), Enables this object as a compact JSON String.
       String toString(int indentSpaces), Enables this object as a human readable JSON String for debugging.
       剩下几个比较重要的方法:getBoolean(), getDouble(), getInt(), getLong(), getString()。
   3.JSONArray类
     这是一个值的索引序列,值可以是JSONObjects,其他的JSONArray, Strings, Booleans, Integers, Longs, Doubles,null or NULL.
     构造方法:
         JSONArray(),creates a JSONArray with no values.
         JSONArray(JSONTokener readFrom), create a new JSONArray with values from the next array in the tokener. 
         JSONArray(String json), creates a new JSONArray with values from the JSON String.
     public methods:
         JSONArray getJSONArray(int index), Returns the value at index if it exists and is a JSONArray.
         JSONObject getJSONObject(int index), Returns the value at index if it exists and is a JSONObject.
         Object get(int index), Returns the value at index.
        剩下几个比较重要的方法,是对方法get(int index)的细化:getDouble(int index), getBoolean(int index), getInt(int index), getLong(int index), getString(int index).
    4.JSONStringer类
      这个类实现了toString() and toString()方法。大多数应用程序开发者直接使用自带的toString()方法,而不是用这个类的方法。
        5. JSONExceptionl类
      这个类不做介绍。
 总结:
    1.虽然 JSONTokener,JSONObject,JSONArray,JSONStringer这几个类都不是final类,但在使用这几个类的时候,最好不要试图去继承它们。
    2.JSONTokener,JSONObject,JSONArray,JSONStringer这几个类都不是线程安全的类。
    3.JSONObject和JSONArray,使用了两种相互冲突的方式表现null:一种是标准的Java null引用,另一种是包装值NULL。特别是,JSONObject在调用put(name, null)会清除掉name指定的实体对象,但调用put(name, JSONObject.NULL)会存储这个键值对,其value为JSONObject.NULL
Java基础
一、String,StringBuffer, StringBuilder 的区别是什么?String为什么是不可变的? 
二、VECTOR,ARRAYLIST, LINKEDLIST的区别是什么? 
三、HASHTABLE, HASGMAQ,TreeMap区别 
四、ConcurrentMap和HashMap的区别 
五、Tomcat,apache,jboss的区别 
六、GET POST区别 
七、SESSION, COOKIE区别 
八、Servlet的生命周期 
九、HTTP 报文包含内容 
十、Statement与PreparedStatement的区别,什么是SQL注入,如何防止SQL注入 
十一、redirect, foward区别 
十二、关于JAVA内存模型,一个对象(两个属性,四个方法)实例化100次,现在内存中的存储状态, 
几个对象,几个属性,几个方法。 
十三、谈谈Hibernate的理解,一级和二级缓存的作用,在项目中Hibernate都是怎么使用缓存的 
十四、反射讲一讲,主要是概念,都在哪需要反射机制,反射的性能,如何优化 
十五、谈谈Hibernate与Ibatis的区别,哪个性能会更高一些 
十六、对Spring的理解,项目中都用什么?怎么用的?对IOC、和AOP的理解及实现原理 
十七、线程同步,并发操作怎么控制 
十八、描述struts的工作流程。 
十九、Tomcat的session处理,如果让你实现一个tomcatserver,如何实现session机制 
二十、关于Cache(Ehcache,Memcached) 
二一、sql的优化相关问题 
二二、oracle中 rownum与rowid的理解,一千条记录我查200到300的记录怎么查? 
二三、如何分析ORACLE的执行计划? 
二四、 DB中索引原理,种类,使用索引的好处和问题是什么? 
二五、JVM垃圾回收实现原理。垃圾回收的线程优先级。 
二六、jvm 最大内存设置。设置的原理。结合垃圾回收讲讲。 
用多线程实现的Java爬虫程序
以下是一个Java爬虫程序,它能从指定主页开始,按照指定的深度抓取该站点域名下的网页并维护简单索引。
参数:private static int webDepth = 2;//爬虫深度。主页的深度为1,设置深度后超过该深度的网页不会抓取。
        private int intThreadNum = 10;//线程数。开启的线程数。
抓取时也会在程序源文件目录下生成一个report.txt文件记录爬虫的运行情况,并在抓取结束后生成一个fileindex.txt文件维护网页文件索引。
本程序用到了多线程(静态变量和同步),泛型,文件操作,URL类和连接,Hashtable类关联数组,正则表达式及其相关类。运行时需使用命令行参数,第一个参数应使用http://开头的有效URL字符串作为爬虫的主页,第二个参数(可选)应输入可转换为int型的字符串(用Integer.parseInt(String s)静态方法可以转换的字符串,如3)作为爬虫深度,如果没有,则默认深度为2。
本程序的不足之处是:只考虑了href= href=' href="后加绝对url的这三种情况(由于url地址在网页源文件中情况比较复杂,有时处理也会出现错误),还有相对url和window.open('的情况没有考虑。异常处理程序也只是简单处理。如果读者有改进办法可以把源代码帖出,不胜感激。
附上源代码如下(保存名为GetWeb.java):
import java.io.File;import java.io.BufferedReader;import java.io.FileOutputStream;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.URL;import java.net.URLConnection;import java.util.ArrayList;import java.util.regex.Matcher;import java.util.regex.Pattern;import java.util.Hashtable;
public class GetWeb {private int webDepth = 2;//爬虫深度private int intThreadNum = 10;//线程数private String strHomePage = "";//主页地址private String myDomain;//域名private String fPath = "web";//储存网页文件的目录名private ArrayList<String> arrUrls = new ArrayList<String>();//存储未处理URLprivate ArrayList<String> arrUrl = new ArrayList<String>();//存储所有URL供建立索引private Hashtable<String,Integer> allUrls = new Hashtable<String,Integer>();//存储所有URL的网页号private Hashtable<String,Integer> deepUrls = new Hashtable<String,Integer>();//存储所有URL深度private int intWebIndex = 0;//网页对应文件下标,从0开始private String charset = "GB2312";private String report = "";private long startTime;private int webSuccessed = 0;private int webFailed = 0;public GetWeb(String s){   this.strHomePage = s;}public GetWeb(String s,int i){   this.strHomePage = s;   this.webDepth = i;}public synchronized void addWebSuccessed(){   webSuccessed++;}public synchronized void addWebFailed(){   webFailed++;}public synchronized void addReport(String s){   try   {    report += s;    PrintWriter pwReport = new PrintWriter(new FileOutputStream("report.txt"));    pwReport.println(report);    pwReport.close();   }   catch(Exception e)   {    System.out.println("生成报告文件失败!");   }}public synchronized String getAUrl(){   String tmpAUrl = arrUrls.get(0);   arrUrls.remove(0);   return tmpAUrl;}public synchronized String getUrl(){   String tmpUrl = arrUrl.get(0);   arrUrl.remove(0);   return tmpUrl;}public synchronized Integer getIntWebIndex(){   intWebIndex++;   return intWebIndex;}/*** @param args*/public static void main(String[] args){   if (args.length == 0 || args[0].equals(""))   {    System.out.println("No input!");    System.exit(1);   }   else if(args.length == 1)   {    GetWeb gw = new GetWeb(args[0]);    gw.getWebByHomePage();   }   else    {    GetWeb gw = new GetWeb(args[0],Integer.parseInt(args[1]));    gw.getWebByHomePage();   }}public void getWebByHomePage(){   startTime = System.currentTimeMillis();   this.myDomain = getDomain();   if (myDomain == null)   {    System.out.println("Wrong input!");    //System.exit(1);    return;   }   System.out.println("Homepage = " + strHomePage);   addReport("Homepage = " + strHomePage + "!\n");   System.out.println("Domain = " + myDomain);   addReport("Domain = " + myDomain + "!\n");   arrUrls.add(strHomePage);   arrUrl.add(strHomePage);   allUrls.put(strHomePage,0);   deepUrls.put(strHomePage,1);   File fDir = new File(fPath);        if(!fDir.exists())        {        fDir.mkdir();        }   System.out.println("Start!");   this.addReport("Start!\n");   String tmp = getAUrl();   this.getWebByUrl(tmp,charset,allUrls.get(tmp)+"");   int i = 0;   for (i=0;i<intThreadNum;i++)   {    new Thread(new Processer(this)).start();   }   while (true)   {    if(arrUrls.isEmpty() && Thread.activeCount() == 1)    {     long finishTime = System.currentTimeMillis();     long costTime = finishTime-startTime;     System.out.println("\n\n\n\n\nFinished!");     addReport("\n\n\n\n\nFinished!\n");     System.out.println("Start time = " + startTime + "   " + "Finish time = " + finishTime + "   " + "Cost time = " + costTime + "ms");     addReport("Start time = " + startTime + "   " + "Finish time = " + finishTime + "   " + "Cost time = " + costTime + "ms" + "\n");     System.out.println("Total url number = " + (webSuccessed+webFailed) + "   Successed: " + webSuccessed + "   Failed: " + webFailed);     addReport("Total url number = " + (webSuccessed+webFailed) + "   Successed: " + webSuccessed + "   Failed: " + webFailed + "\n");         String strIndex = "";     String tmpUrl = "";     while (!arrUrl.isEmpty())     {      tmpUrl = getUrl();      strIndex += "Web depth:" + deepUrls.get(tmpUrl) + "   Filepath: " + fPath + "/web" + allUrls.get(tmpUrl) + ".htm" + "   url:" + tmpUrl + "\n\n";     }     System.out.println(strIndex);     try     {      PrintWriter pwIndex = new PrintWriter(new FileOutputStream("fileindex.txt"));      pwIndex.println(strIndex);      pwIndex.close();     }     catch(Exception e)     {      System.out.println("生成索引文件失败!");     }     break;    }   }}public void getWebByUrl(String strUrl,String charset,String fileIndex){   try   {    //if(charset==null||"".equals(charset))charset="utf-8";    System.out.println("Getting web by url: " + strUrl);    addReport("Getting web by url: " + strUrl + "\n");    URL url = new URL(strUrl);    URLConnection conn = url.openConnection();    conn.setDoOutput(true);    InputStream is = null;    is = url.openStream();       String filePath = fPath + "/web" + fileIndex + ".htm";    PrintWriter pw = null;    FileOutputStream fos = new FileOutputStream(filePath);    OutputStreamWriter writer = new OutputStreamWriter(fos);    pw = new PrintWriter(writer);    BufferedReader bReader = new BufferedReader(new InputStreamReader(is));    StringBuffer sb = new StringBuffer();    String rLine = null;    String tmp_rLine = null;    while ( (rLine = bReader.readLine()) != null)    {     tmp_rLine = rLine;     int str_len = tmp_rLine.length();     if (str_len > 0)     {      sb.append("\n" + tmp_rLine);      pw.println(tmp_rLine);      pw.flush();      if (deepUrls.get(strUrl) < webDepth)getUrlByString(tmp_rLine,strUrl);     }     tmp_rLine = null;    }    is.close();    pw.close();    System.out.println("Get web successfully! " + strUrl);    addReport("Get web successfully! " + strUrl + "\n");    addWebSuccessed();   }   catch (Exception e)   {    System.out.println("Get web failed!       " + strUrl);    addReport("Get web failed!       " + strUrl + "\n");    addWebFailed();   }}public String getDomain(){   String reg = "(?<=http\\://[a-zA-Z0-9]{0,100}[.]{0,1})[^.\\s]*?\\.(com|cn|net|org|biz|info|cc|tv)";   Pattern p = Pattern.compile(reg,Pattern.CASE_INSENSITIVE);   Matcher m = p.matcher(strHomePage);   boolean blnp = m.find();   if (blnp == true)   {    return m.group(0);   }   return null;}public void getUrlByString(String inputArgs,String strUrl){   String tmpStr = inputArgs;   String regUrl = "(?<=(href=)[\"]?[\']?)[http://][^\\s\"\'\\?]*(" + myDomain + ")[^\\s\"\'>]*";   Pattern p = Pattern.compile(regUrl,Pattern.CASE_INSENSITIVE);   Matcher m = p.matcher(tmpStr);   boolean blnp = m.find();   //int i = 0;   while (blnp == true)   {    if (!allUrls.containsKey(m.group(0)))    {     System.out.println("Find a new url,depth:" + (deepUrls.get(strUrl)+1) + " "+ m.group(0));     addReport("Find a new url,depth:" + (deepUrls.get(strUrl)+1) + " "+ m.group(0) + "\n");     arrUrls.add(m.group(0));     arrUrl.add(m.group(0));     allUrls.put(m.group(0),getIntWebIndex());     deepUrls.put(m.group(0),(deepUrls.get(strUrl)+1));    }    tmpStr = tmpStr.substring(m.end(),tmpStr.length());    m = p.matcher(tmpStr);    blnp = m.find();   }}class Processer implements Runnable{     GetWeb gw;     public Processer(GetWeb g)     {         this.gw = g;     }     public void run()     {       //Thread.sleep(5000);       while (!arrUrls.isEmpty())       {        String tmp = getAUrl();        getWebByUrl(tmp,charset,allUrls.get(tmp)+"");       }     }}}
ndroid开发者应该深入学习的10个开源应用项目
1.Android团队提供的示例项目

  如果不是从学习Android SDK中提供的那些样例代码开始,可能没有更好的方法来掌握在Android这个框架上开发。由Android的核心开发团队提供了15个优秀的示例项目,包含了游戏、图像处理、时间显示、开始菜单快捷方式等。

  地址:http://code.google.com/p/apps-for-android/

  2.Remote Droid

  RemoteDroid是一个Android应用,能够让用户使用自己的无线网络使用无线键盘、触摸屏操作手机。这个项目为开发者提供了如网络连接、触摸屏手指运动等很好的样例。

  地址:http://code.google.com/p/remotedroid/

  3.TorProxy和Shadow

  TorProxy应用实现了Android手机无线电电传通讯(TOR),和Shadow应用一起使用,可以使用手机匿名上网。从该项目源代码中,可以掌握socket连接、管理cookie等方法。

  地址:http://www.cl.cam.ac.uk/research/dtg/code/svn/android-tor/

  4、 Android SMSPopup

  SMSPopup可以截获短信内容显示在一个泡泡形状的窗口中。从这个项目中可以掌握到如何使用内置的短信SMS接口。

  地址:http://code.google.com/p/android-smspopup/

  5、 Standup Timer

  Standup Timer应用用于控制站立会议时间,类似秒表倒计时,可以提醒每个人的讲话时间已到,从而保证每个与会者使用时间一样。从该项目的代码中,可以学会如何使用时间函数。另外,这个项目的代码是采用视图view、模型model严格分离的设计思路。

  地址:http://github.com/jwood/standup-timer

  6、 Foursquare

  是Foursquare.com的一个客户端应用,该应用主要分为两个模块:API(com.joelapenna.foursquare)和界面前端(com.joelapenna.foursquared)两部分。从该项目代码中,可以学会如何同步、多线程、HTTP连接等技术。

  地址:http://code.google.com/p/foursquared/

  7、 Pedometer

  Pedometer应用用于记录你每天走路步数的。尽管记录不一定精准,但是从这个项目中,可以学习几个不同的技术:加速器交互、语音更新、后台运行服务等。

  地址:http://code.google.com/p/pedometer/

  8、 OpenSudoku-android

  OpenSudoku是一个简单的九宫格数独游戏。从代码中可以学习到如何在视图中显示表格数据,以及如何和一个网站交互等技术。

  地址:http://code.google.com/p/opensudoku-android/

  9、 ConnectBot

  ConnectBot是Android平台的一个客户端安全壳应用。从该项目代码中,可以学习到很多Android安全方面的内容,这些是你在开发应用时经常需要考虑的安全问题。

  地址:http://code.google.com/p/connectbot/

  10、 WordPress的Android应用

  当然在最后不能不提Wordpress的Android应用了,这是Wordpress官方开发团队提供的一个项目。从代码中可以学习到XMLRPC调用(当然还有更多的优秀内容)。

  地址:http://android.svn.wordpress.org/trunk/
Android获取其他包的Context实例,然后调用它的方法
Android中有Context的概念,想必大家都知道。Context可以做很多事情,打开activity、发送广播、打开本包下文件夹和数据库、获取classLoader、获取资源等等。如果我们得到了一个包的Context对象,那我们基本上可以做这个包自己能做的大部分事情。
         那我们能得到吗?很高兴的告诉你,能!
      Context有个createPackageContext方法,可以创建另外一个包的上下文,这个实例不同于它本身的Context实例,但是功能是一样的。

      这个方法有两个参数:
1。packageName  包名,要得到Context的包名
2。flags  标志位,有CONTEXT_INCLUDE_CODE和CONTEXT_IGNORE_SECURITY两个选项。CONTEXT_INCLUDE_CODE的意思是包括代码,也就是说可以执行这个包里面的代码。CONTEXT_IGNORE_SECURITY的意思是忽略安全警告,如果不加这个标志的话,有些功能是用不了的,会出现安全警告。

      下面给个小例子,执行另外一个包里面的某个类的方法。
      另外一个包的包名是chroya.demo,类名Main,方法名print,代码如下:
Java代码  收藏代码
package chroya.demo;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.util.Log;  
  
class Main extends Activity {  
      
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
    }  
      
    public void print(String msg) {  
        Log.d("Main", "msg:"+ msg);  
    }  
}  


本包的调用Main的print方法的代码块如下:
Java代码  收藏代码
Context c = createPackageContext("chroya.demo", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);  
//载入这个类  
Class clazz = c.getClassLoader().loadClass("chroya.demo.Main");  
//新建一个实例  
Object owner = clazz.newInstance();  
//获取print方法,传入参数并执行  
Object obj = clazz.getMethod("print", String.class).invoke(owner, "Hello");  

    ok,这样,我们就调用了chroya.demo包的Main类的print方法,执行结果,打印出了Hello。
      怎么样,这只是一个调用其他包的代码的例子,我们获取到Context,还可以做很多事情,当然,题目所说的坏事,还是不要做为好。


Android获取应用程序的大小  
2010-12-29 10:11:36|  分类: Android |字号 订阅

今天碰到个问题,想获取某个已安装的包的大小,没找到合适的方法。搜索了一下,发现PackageManager里面有个getPackageSizeInfo方法,可惜是hide的,而且它执行之后,会将结果回调给IPackageStatsObserver的onGetStatsCompleted方法。后来想直接计算/data/app和/system/app里面的apk大小,可是有时候会碰到权限问题,需要root才可以获取大小。        再后来,我想起系统的设置里面有一个应用程序管理,它里面列出了所有程序的占用空间大小、数据大小和缓存大小。恩,这个就是突破口。
       以前写过一篇获取其他包的Context ,这个东西是真有用,这个结合反射,可以做很多神奇的事情,比如今天的这个。
       上代码:
Java代码 
package chroya.demo;  
  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.InvocationTargetException;  
import java.util.concurrent.CountDownLatch;  
  
import android.app.Activity;  
import android.content.Context;  
import android.content.pm.PackageStats;  
import android.content.pm.PackageManager.NameNotFoundException;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.Message;  
import android.util.Log;  
  
public class Main extends Activity {  
    private PackageStats ps;  
      
    public void getPackageStats(String packageName) {  
        try {  
            //获取setting包的的Context  
            Context mmsCtx = createPackageContext("com.android.settings",  
                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);  
            //使用setting的classloader加载com.android.settings.ManageApplications类  
            Class<?> maClass = Class.forName("com.android.settings.ManageApplications", true, mmsCtx.getClassLoader());  
            //创建它的一个对象  
            Object maObject = maClass.newInstance();  
              
            /* 
             * 将私有域mPm赋值。因为mPm在SizeObserver的invokeGetSize中用到了, 
             * 却因为没有执行onCreate而没有初始化,所以要在此处初始化。 
             */  
            Field f_mPm = maClass.getDeclaredField("mPm");  
            f_mPm.setAccessible(true);              
            f_mPm.set(maObject, mmsCtx.getPackageManager());  
              
            /* 
             * 给mHandler赋值为重新定义的Handler,以便接收SizeObserver的 
             * onGetStatsCompleted回调方法中dispatch的消息,从中取PackageStats对象。 
             * */  
            Field f_mHandler = maClass.getDeclaredField("mHandler");  
            f_mHandler.setAccessible(true);  
            f_mHandler.set(maObject, new Handler() {  
                  public void handleMessage(Message msg) {  
                      if(msg.what == 1) {  
                          //此处获取到PackageStats对象  
                          ps = (PackageStats) msg.getData().getParcelable("ApplicationPackageStats");                           
                          Log.d("", ""+ps.codeSize);                            
                      }  
                  }  
            });  
              
            //加载内部类SizeObserver  
            Class<?> sizeObserverClass = Class.forName("com.android.settings.ManageApplications$SizeObserver", true, mmsCtx.getClassLoader());  
            Constructor sizeObserverConstructor = sizeObserverClass.getDeclaredConstructors()[0];  
            sizeObserverConstructor.setAccessible(true);  
            /* 
             * 创建SizeObserver对象,两个参数,第一个是外部类的对象, 
             * 也就是ManageApplications对象,第二个是msgId,也就是 
             * 分发消息的id,跟Handler接收的msgId一样。 
             * */  
            Object soObject = sizeObserverConstructor.newInstance(maObject, 1);  
            //执行invokeGetSize方法  
            sizeObserverClass.getMethod("invokeGetSize", String.class,  
                    CountDownLatch.class).invoke(soObject, packageName, new CountDownLatch(1));           
        } catch (NameNotFoundException e) {  
            e.printStackTrace();  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
        } catch (IllegalAccessException e) {  
            e.printStackTrace();  
        } catch (IllegalArgumentException e) {  
            e.printStackTrace();  
        } catch (SecurityException e) {  
            e.printStackTrace();  
        } catch (InvocationTargetException e) {  
            e.printStackTrace();  
        } catch (NoSuchMethodException e) {  
            e.printStackTrace();  
        } catch (InstantiationException e) {  
            e.printStackTrace();  
        } catch (NoSuchFieldException e) {  
            e.printStackTrace();  
        }  
    }  
      
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);    
        getPackageStats("chroya.demo");         
    }  
}  
 
       注释都在代码里面了,稍微理解一下应该都能懂的。
       获取到PackageStats对象,就可以从中获取到应用程序的占用空间大小、数据大小和缓存大小。
 
      另,这毕竟只是hack code,不可能通用。这段代码的局限性是,只有1.5能用,而且如果别人把setting包去掉了,也没法使用。要写出各版本SDK通用的代码,就必须查看每个版本的setting包,看代码有何变化,然后根据上面给出的思路为每个版本写一个方法,就ok了。
在android2.1下面,运行到下面一句会产生异常:
sizeObserverClass.getMethod("invokeGetSize", String.class,  
                    CountDownLatch.class).invoke(soObject, packageName, new CountDownLatch(1));     

因为缺少访问包的权限,所以在程序中引用“访问包”权限,即如下所示:
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>

这是可以程序可以正常运行,但是一直没有回调产生,也就是说下面这句一直没有调用
  public void handleMessage(Message msg) {  
                      if(msg.what == 1) {  
                          //此处获取到PackageStats对象  
                          ps = (PackageStats) msg.getData().getParcelable("ApplicationPackageStats");                           
                          Log.d("", ""+ps.codeSize);                            
                      }  
                  }  

经过查看代码后,通过下面的处理能得到包的大小的信息:

sizeObserverClass.getMethod("invokeGetSize", String.class, CountDownLatch.class)
                    .invoke(soObject, packageName, count);
      
Field f_mStats = sizeObserverClass.getDeclaredField("stats");
f_mStats.setAccessible(true);
ps= (PackageStats)f_mStats.get(soObject);
if(ps != null)
                Log.d("packagename", "code size:" + ps.codeSize +" cacheSize:" +ps.cacheSize +" dataSize:" + ps.dataSize);
Activity设置singleInstance后不能启用startActivityForResult()进行数据回调
一般情况下如果我们想从A跳到B,并希望B操作完毕后返回操作结果到A,我们第一时间就会想到运用startActivityForResult()进行处理 

    但是...

    如果A的LauncherMode设置成了singleTop或者singleInstance,这招就会失灵

查看startActivityForResult()的文档,是这样描述的:

                   Note that this method should only be used with Intent protocols that are defined to return a result. In other protocols (such asandroid.content.Intent.ACTION_MAIN or android.content.Intent.ACTION_VIEW), you may not get the result when you expect. For example, if the activity you are launching uses the singleTask launch mode, it will not run in your task and thus you will immediately receive a cancel result.


最后一句大概意思是如果你将A设置成了singleTask那么你启动B时不会等待返回,只会马上得到返回码为Activity.RESULT_CANCELED.是不会得到你想要的结果的。

    其实这个时候需要使用方法onNowIntent(Intent intent)主动获取,而不是等待startActivityForResult()的返回

详细如下:

    1, 在A启动B时,使用平常的startActivity()方法

    2,在B处理完毕准备返回结果时,直接通过startActivity()重新激活栈中的A,这时应该将你想返回的数据寄存到intent里面

[html] view plaincopy 
Intent intent = new Intent(this, A.class);  
intent.putExtra("your callback data label", "your callback data");  
startActivity(intent);  
    3,在A中的onNowIntent()实现获取返回参数的方法
[html] view plaincopy 
@Override  
protected void onNewIntent(Intent intent) { // ActManager设置了singleInstance,可以通过这个方法获取重新启动ActManager的intent数据  
    // TODO Auto-generated method stub  
    super.onNewIntent(intent);  
    backDetail = intent.getBooleanExtra("delete", true);  
}  
    4,在A中实现onResume()方法,通过这个生命周期的这一步,实现自动处理返回数据的效果
[html] view plaincopy 
public void onResume() {  
    super.onResume();  
    if (backDetail) {  
        // do somthing  
    }  
}  

    就这样可以实现singleInstance的参数回调
利用createPackageContext()方法遇到的问题
(1)Context configContext = context.createPackageContext(“com.newmine.settings”, Context.CONTEXT_IGNORE_SECURITY);
SharedPreferences configxml = configContext.getSharedPreferences(“config”, 3);
使用createPackageContext()读取其他程序的SharedPreferences 时即(2),通过其他程序的包名。获取该程序的上下文,实质获取想要读的SharedPreferences 的路径。不过需要其他程序的SharedPreferences在创建时,必须设置为全局可读,即权限为-r--rw-rw-。具体的代码:

(2)SharedPreferences  preferences = getSharedPreferences("config", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE);



但是使用createPackageContext() 通过代码(3)向其他程序的SharedPreferences写入数据 时,即使该SharedPreferences是全局可写的,即-rw--rw-rw-,也会出现问题,操作失败。问题在于在向SharedPreferences,在该SharedPreferences所在的包路径下会产生一个临时存储的以.bak结尾的缓存文件,写入操作时,如果该包的权限没有做过必要的修改,默认是不让其他程序操作的,同样就不能在该包内创建临时文件,所以进而导致写入失败失败。

解决方法:在代码(2)后加入代码(4),这样shared_prefs包的权限也就变为可读可写可执行的了,自然可以在该包内创建临时文件。

(4)File dir = new File("/data/data/" + getPackageName() + "/shared_prefs/");
if (!dir.exists()){
dir.mkdirs();
}
dir.setReadable(true, false);
dir.setWritable(true, false);
dir.setExecutable(true, false);

(3)Context configContext = context.createPackageContext(“com.newmine.settings, Context.CONTEXT_IGNORE_SECURITY);
SharedPreferences configxml = configContext.getSharedPreferences(“config”, 3);
Editor xmledt = configxml.edit();
SharedPreferences具体使用方法及createPackageContext方法(获取其他应用的共享文件)
很多时候我们开发的软件需要向用户提供软件参数设置功能,Android应用,我们最适合采用什么方式保存软件配置参数呢?在Android平台上,提供了一个SharedPreferences类。
顾名思义,这个Preferences是共享的,共享的范围据现在同一个Package中,这里面说所的Package和Java里面的那个Package不同,貌似这里面的Package是指在AndroidManifest.xml文件中:
Xml代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.test.demo"
android:versionCode="1"
android:versionName="1.0.0">
这里面的package。
SharedPreferences它是一个轻量级的存储类,特别适合用于保存软件配置参数。使用SharedPreferences保存数据,其背后是用xml文件存放数据,使用简易的键值对存储。文件存放在/data/data//shared_prefs目录下
下面我们给点代码,说下简单的使用: 
这样,我们就把信息存储到了/data/data/(package包名)/shared_prefs/demo.xml文件里面(可在File Explorer里根据以上路径找到该xml文件)
getSharedPreferences(name,mode)方法的第一个参数用于指定该文件的名称,最好定义为一个静态字符串,另外,名称如上面所示,不用带后缀名,后缀名会由系统自动加上。方法的第二个参数指定文件的操作模式,共有四种操作模式:
MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容。
Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
MODE_WORLD_READABLE,则该配置文件除了自己访问外还可以被其它应该程序读取。
MODE_WORLD_WRITEABLE,则该配置文件除了自己访问外还可以被其它应该程序读取和写入
 
所以,如果你希望SharedPreferences背后使用的xml文件能被其他应用读和写,可以指定Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE权限。
另外Activity还提供了另一个getPreferences(mode)方法操作SharedPreferences,这个方法默认使用当前类不带包名的类名作为文件的名称。

如果我们的模式设置为Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE权限,我们其他的应用是可以访问的,下面是其他应用访问的代码(假如上面代码的包名为cn.test.demo):
Context context = createPackageContext("cn.test.demo", Context.CONTEXT_IGNORE_SECURITY);
CreatePackageContext这个方法有两个参数:
1.packageName 包名,要得到Context的包名
2.flags 标志位,有CONTEXT_INCLUDE_CODE和CONTEXT_IGNORE_SECURITY两个选项。CONTEXT_INCLUDE_CODE的意思是包括代码,也就是说可以执行这个包里面的代码。CONTEXT_IGNORE_SECURITY的意思是忽略安全警告,如果不加这个标志的话,有些功能是用不了的,会出现安全警告。
CreatePackageContext方法在找不到包名的时候会报NameNotFoundException异常,所以我们要捕获它。
MK中的ANDROID:SHAREDUSERID和LOCAL_CERTIFICATE作用
Android中如何修改系统时间(应用程序获得系统权限) 
在 android 的API中有提供 SystemClock.setCurrentTimeMillis()函数来修改系统时间,可惜无论你怎么调用这个函数都是没用的,无论模拟器还是真 机,在logcat中总会得到"Unable to open alarm driver: Permission denied ".这个函数需要root权限或者运行与系统进程中才可以用。 
        本来以为就没有办法在应用程序这一层改系统时间了,后来在网上搜了好久,知道这个目的还是可以达到的。 
        第一个方法简单点,不过需要在Android系统源码的环境下用make来编译: 
        1. 在应用程序的AndroidManifest.xml中的manifest节点中加入android:sharedUserId="android.uid.system"这个属性。 
        2. 修改Android.mk文件,加入LOCAL_CERTIFICATE := platform这一行 
        3. 使用mm命令来编译,生成的apk就有修改系统时间的权限了。 
        第二个方法麻烦点,不过不用开虚拟机跑到源码环境下用make来编译: 
        1. 同上,加入android:sharedUserId="android.uid.system"这个属性。 
        2. 使用eclipse编译出apk文件,但是这个apk文件是不能用的。 
        3. 用压缩软件打开apk文件,删掉META-INF目录下的CERT.SF和CERT.RSA两个文件。 
        4. 使用目标系统的platform密钥来重新给apk文件签名。这步比较麻烦,首先找到密钥文件,在我的Android源码目录中的位置 是"build\target\product\security",下面的platform.pk8和platform.x509.pem两个文件。然 后用Android提供的Signapk工具来签名,signapk的源代码是在"build\tools\signapk"下,用法为"signapk platform.x509.pem platform.pk8 input.apk output.apk",文件名最好使用绝对路径防止找不到,也可以修改源代码直接使用。 
        这样最后得到的apk和第一个方法是一样的。 
        最后解释一下原理,首先加入android:sharedUserId="android.uid.system"这个属性。通过Shared User id,拥有同一个User id的多个APK可以配置成运行在同一个进程中。那么把程序的UID配成android.uid.system,也就是要让程序运行在系统进程中,这样就 有权限来修改系统时间了。 
        只是加入UID还不够,如果这时候安装APK的话发现无法安装,提示签名不符,原因是程序想要运行在系统进程中还要有目标系统的platform key,就是上面第二个方法提到的platform.pk8和platform.x509.pem两个文件。用这两个key签名后apk才真正可以放入系 统进程中。第一个方法中加入LOCAL_CERTIFICATE := platform其实就是用这两个key来签名。 
        这也有一个问题,就是这样生成的程序只有在原始的Android系统或者是自己编译的系统中才可以用,因为这样的系统才可以拿到platform.pk8 和platform.x509.pem两个文件。要是别家公司做的Android上连安装都安装不了。试试原始的Android中的key来签名,程序在 模拟器上运行OK,不过放到G3上安装直接提示"Package ... has no signatures that match those in shared user android.uid.system",这样也是保护了系统的安全。 
        最最后还说下,这个android:sharedUserId属性不只可以把apk放到系统进程中,也可以配置多个APK运行在一个进程中,这样可以共享数据,应该会很有用的。
--------------
安装在设备中的每一个apk文件,Android给每个APK进程分配一个单独的用户空间,其manifest中的userid就是对应一个Linux用户都会被分配到一个属于自己的统一的Linux用户ID,并且为它创建一个沙箱,以防止影响其他应用程序(或者其他应用程序影响它)。用户ID 在应用程序安装到设备中时被分配,并且在这个设备中保持它的永久性。
通过Shared User id,拥有同一个User id的多个APK可以配置成运行在同一个进程中.所以默认就是可以互相访问任意数据. 也可以配置成运行成不同的进程, 同时可以访问其他APK的数据目录下的数据库和文件.就像访问本程序的数据一样.
对于一个APK来说,如果要使用某个共享UID的话,必须做三步:
1、在Manifest节点中增加android:sharedUserId属性。
2、在Android.mk中增加LOCAL_CERTIFICATE的定义。
如果增加了上面的属性但没有定义与之对应的LOCAL_CERTIFICATE的话,APK是安装不上去的。提示错误是:Package com.test.MyTest has no signatures that match those in shared user android.uid.system; ignoring!也就是说,仅有相同签名和相同sharedUserID标签的两个应用程序签名都会被分配相同的用户ID。例如所有和media/download相关的APK都使用android.media作为sharedUserId的话,那么它们必须有相同的签名media。
3、把APK的源码放到packages/apps/目录下,用mm进行编译。
举例说明一下。
系统中所有使用android.uid.system作为共享UID的APK,都会首先在manifest节点中增加android:sharedUserId="android.uid.system",然后在Android.mk中增加LOCAL_CERTIFICATE := platform。可以参见Settings等
系统中所有使用android.uid.shared作为共享UID的APK,都会在manifest节点中增加android:sharedUserId="android.uid.shared",然后在Android.mk中增加LOCAL_CERTIFICATE := shared。可以参见Launcher等
系统中所有使用android.media作为共享UID的APK,都会在manifest节点中增加android:sharedUserId="android.media",然后在Android.mk中增加LOCAL_CERTIFICATE := media。可以参见Gallery等。
另外,应用创建的任何文件都会被赋予应用的用户标识,并且正常情况下不能被其他包访问。当通过getSharedPreferences(String,int)、openFileOutput(String、int)或者openOrCreate Database(String、int、SQLiteDatabase.CursorFactory)创建一个新文件时,开发者可以同时或分别使用MODE_WORLD_READABLE和MODE_WORLD_RITEABLE标志允许其他包读/写此文件。当设置了这些标志后,这个文件仍然属于自己的应用程序,但是它的全局读/写和读/写权限已经设置,所以其他任何应用程序可以看到它。
关于签名:
build/target/product/security目录中有四组默认签名供Android.mk在编译APK使用:
1、testkey:普通APK,默认情况下使用。
2、platform:该APK完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试,这种方式编译出来的APK所在进程的UID为system。
3、shared:该APK需要和home/contacts进程共享数据。
4、media:该APK是media/download系统中的一环。
应用程序的Android.mk中有一个LOCAL_CERTIFICATE字段,由它指定用哪个key签名,未指定的默认用testkey.
 
 
对于使用eclipse编译的apk,可以使用signapk.jar来手动进行签名,其源码在build/tools/signapk下,编译后在out/host/linux-x86/framework/signapk.jar,也可以从网上下载。使用方法,以platform为例:java -jar ./signapk platform.x509.pem platform.pk8 input.apk output.apk  (platform.x509.pem platform.pk8在build/target/product/security获取)
Android.os包中一些类的使用
Android.os.Build
Java代码
Build.BOARD // 主板  
Build.BRAND // android系统定制商  
Build.CPU_ABI // cpu指令集  
Build.DEVICE // 设备参数  
Build.DISPLAY // 显示屏参数  
Build.FINGERPRINT // 硬件名称  
Build.HOST  
Build.ID // 修订版本列表  
Build.MANUFACTURER // 硬件制造商  
Build.MODEL // 版本  
Build.PRODUCT // 手机制造商  
Build.TAGS // 描述build的标签  
Build.TIME  
Build.TYPE // builder类型  
Build.USER  
  
// 运行结果  
/* 
board: unknown 
brand: generic 
cpu abi: armeabi 
device: generic 
display: google_sdk-eng 2.1 ERD79 22607 test-keys 
finger print: generic/google_sdk/generic/:2.1/ERD79/22607:eng/test-keys 
host: genki.mtv.corp.google.com 
id: ERD79 
manufacturer: unknown 
model: google_sdk 
product: google_sdk 
tags: test-keys 
time: 1261185425000 
type: eng 
user: android-build 
*/  
 
Build.BOARD // 主板   
Build.BRAND // android系统定制商   
Build.CPU_ABI // cpu指令集   
Build.DEVICE // 设备参数   
Build.DISPLAY // 显示屏参数   
Build.FINGERPRINT // 硬件名称   
Build.HOST  
Build.ID // 修订版本列表   
Build.MANUFACTURER // 硬件制造商   
Build.MODEL // 版本   
Build.PRODUCT // 手机制造商   
Build.TAGS // 描述build的标签   
Build.TIME  
Build.TYPE // builder类型   
Build.USER  
  
// 运行结果   
/* 
board: unknown 
brand: generic 
cpu abi: armeabi 
device: generic 
display: google_sdk-eng 2.1 ERD79 22607 test-keys 
finger print: generic/google_sdk/generic/:2.1/ERD79/22607:eng/test-keys 
host: genki.mtv.corp.google.com 
id: ERD79 
manufacturer: unknown 
model: google_sdk 
product: google_sdk 
tags: test-keys 
time: 1261185425000 
type: eng 
user: android-build 
*/  
Build.VERSION
Java代码 
// 当前开发代号  
Build.VERSION.CODENAME  
// 源码控制版本号  
Build.VERSION.INCREMENTAL  
// 版本字符串  
Build.VERSION.RELEASE  
// 版本号  
Build.VERSION.SDK  
// 版本号  
Build.VERSION.SDK_INT  
  
// 结果  
/* 
REL 
22607 
2.1 
7 
7 
*/  
  
// Build.VERSION.SDK_INT可与switch搭配用  
switch (Build.VERSION.SDK_INT) {  
case Build.VERSION_CODES.BASE: // 1.0  
    break;  
      
case Build.VERSION_CODES.BASE_1_1: // 1.1  
    break;  
      
case Build.VERSION_CODES.CUPCAKE: // 1.5  
    break;  
      
case Build.VERSION_CODES.CUR_DEVELOPMENT: // current dev version  
    break;  
      
case Build.VERSION_CODES.DONUT: // 1.6  
    break;  
      
case Build.VERSION_CODES.ECLAIR: // 2.0  
    break;  
      
case Build.VERSION_CODES.ECLAIR_0_1: // 2.0.1  
    break;  
      
case Build.VERSION_CODES.ECLAIR_MR1: // 2.1  
    break;  
}  
 
// 当前开发代号   
Build.VERSION.CODENAME  
// 源码控制版本号   
Build.VERSION.INCREMENTAL  
// 版本字符串   
Build.VERSION.RELEASE  
// 版本号   
Build.VERSION.SDK  
// 版本号   
Build.VERSION.SDK_INT  
  
// 结果   
/* 
REL 
22607 
2.1 
7 
7 
*/  
  
// Build.VERSION.SDK_INT可与switch搭配用   
switch (Build.VERSION.SDK_INT) {  
case Build.VERSION_CODES.BASE: // 1.0   
    break;  
      
case Build.VERSION_CODES.BASE_1_1: // 1.1   
    break;  
      
case Build.VERSION_CODES.CUPCAKE: // 1.5   
    break;  
      
case Build.VERSION_CODES.CUR_DEVELOPMENT: // current dev version   
    break;  
      
case Build.VERSION_CODES.DONUT: // 1.6   
    break;  
      
case Build.VERSION_CODES.ECLAIR: // 2.0   
    break;  
      
case Build.VERSION_CODES.ECLAIR_0_1: // 2.0.1   
    break;  
      
case Build.VERSION_CODES.ECLAIR_MR1: // 2.1   
    break;  
}  
Android.os.SystemClock
文档中对System.currentTimeMillis()进行了相应的描述,就是说它不适合用在需要时间间隔的地方,如Thread.sleep, Object.wait等,因为可以通过System.setCurrentTimeMillis来改变它的值。

要用时间间隔,推荐使用SystemClock中的相关方法。
Java代码  
SystemClock.currentThreadTimeMillis(); // 在当前线程中已运行的时间  
SystemClock.elapsedRealtime(); // 从开机到现在的毫秒书(手机睡眠(sleep)的时间也包括在内)  
SystemClock.uptimeMillis(); // 从开机到现在的毫秒书(手机睡眠的时间不包括在内)  
SystemClock.sleep(100); // 类似Thread.sleep(100);但是该方法会忽略InterruptedException  
SystemClock.setCurrentTimeMillis(1000); // 设置时钟的时间,和System.setCurrentTimeMillis类似  
  
// 时间间隔  
long timeInterval = SystemClock.uptimeMillis() - lastTime;  
// do something with timeInterval  
 
SystemClock.currentThreadTimeMillis(); // 在当前线程中已运行的时间   
SystemClock.elapsedRealtime(); // 从开机到现在的毫秒书(手机睡眠(sleep)的时间也包括在内)   
SystemClock.uptimeMillis(); // 从开机到现在的毫秒书(手机睡眠的时间不包括在内)   
SystemClock.sleep(100); // 类似Thread.sleep(100);但是该方法会忽略InterruptedException   
SystemClock.setCurrentTimeMillis(1000); // 设置时钟的时间,和System.setCurrentTimeMillis类似   
  
// 时间间隔   
long timeInterval = SystemClock.uptimeMillis() - lastTime;  
// do something with timeInterval  
android.os.PowerManager
PowerManager的flag的意思:

Java代码
// PowerManager的一般用法,请求和释放唤醒锁  
PowerManager powerMgr = (PowerManager)  
        getSystemService(Context.POWER_SERVICE);  
int flags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;  
WakeLock wakeLock = powerMgr.newWakeLock(flags, "for debug purpose tag");  
wakeLock.acquire(); // 获取唤醒锁  
wakeLock.release(); // 释放唤醒锁  
// 在游戏中,会将acquire放在onResume中;将release放在onPause,这样在程序运行时就可以保持屏幕常量;在程序处在后台时,就恢复原来的设置。  
  
  
// PowerManager的其它api的使用  
powerMgr.goToSleep(SystemClock.uptimeMillis() + 100); // 100ms后进入睡眠  
powerMgr.isScreenOn(); // 屏幕是否亮着  
powerMgr.userActivity(SystemClock.uptimeMillis()+100, true); // 相当于按home键,会引起从睡眠激活  
  
  
// WakeLock的其它api  
wakeLock.acquire(1000); // 获取唤醒锁,并在1000ms后释放  
wakeLock.isHeld(); // 当前是否持有唤醒锁  
// 是否使用引用计数,默认是启用的。引用计数应该就是第一次请求为1,第二次加1,再一次再加1。  
// 在释放时,只有引用计数为0时才被视为完全释放(所以要多次调用release)  
wakeLock.setReferenceCounted(true);   
 
// PowerManager的一般用法,请求和释放唤醒锁   
PowerManager powerMgr = (PowerManager)  
        getSystemService(Context.POWER_SERVICE);  
int flags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;  
WakeLock wakeLock = powerMgr.newWakeLock(flags, "for debug purpose tag");  
wakeLock.acquire(); // 获取唤醒锁   
wakeLock.release(); // 释放唤醒锁   
// 在游戏中,会将acquire放在onResume中;将release放在onPause,这样在程序运行时就可以保持屏幕常量;在程序处在后台时,就恢复原来的设置。   
  
  
// PowerManager的其它api的使用   
powerMgr.goToSleep(SystemClock.uptimeMillis() + 100); // 100ms后进入睡眠   
powerMgr.isScreenOn(); // 屏幕是否亮着   
powerMgr.userActivity(SystemClock.uptimeMillis()+100, true); // 相当于按home键,会引起从睡眠激活   
  
  
// WakeLock的其它api   
wakeLock.acquire(1000); // 获取唤醒锁,并在1000ms后释放   
wakeLock.isHeld(); // 当前是否持有唤醒锁   
// 是否使用引用计数,默认是启用的。引用计数应该就是第一次请求为1,第二次加1,再一次再加1。   
// 在释放时,只有引用计数为0时才被视为完全释放(所以要多次调用release)   
wakeLock.setReferenceCounted(true); 
Android wifi锁浅谈
Android 对WIFI电源管理的代码主要在WifiService.Java中。如果应用程序想在屏幕被关掉后继续使用WiFi则可以调用 acquireWifiLock来锁住WiFi,该操作会阻止WiFi进入睡眠状态。当应用程序不再使用WiFi时需要调用 releaseWifiLock来释放WiFi。之后WiFi可以进入睡眠状态以节省电源。

  默认情况下当屏幕被关掉以后,如果没有应用程序在使用WiFi,WiFi会在2分钟后进入睡眠状态。这主要是为防止频繁地改变WiFi的电源模式。

       1、介绍
       WifiLock允许一个应用无线网络广播,以保持清醒。通常情况下,无线网络广播可以关掉时,该用户使用该设备在一段时间。将获得WifiLock收音机直到锁释放。WifiLocks多个应用程序可能持有,电台只允许在关掉时没有WifiLocks被保留在任何应用程序。
       在使用WifiLock,仔细考虑一下如果你的应用要求,或能提供无线上网服务的功能在一个移动网络,如果有的话。一个程序,需要下载大文件都应确保WifiLock下载将完成,但是程序或者是偶尔的网络应用不应持有一个WifiLock端口避免影响电池的寿命。
       注意,WifiLocks用户级”不能凌驾了无线保真技术使能”设置,也没有飞机模式。他们从简单的把收音机关掉无线保真技术已经在时但装置是无用的。

       2、API
       android.NET.wifi.WifiManager WifiLock api实现。

       2.1获得

       直到锁定无线网络电台的释放是打电话来了。
       如果这WifiLock,每个电话是reference-counted获得将增加叁考计数,电台将永远被困只要叁考计数是零度以上。
       如果这不是reference-counted WifiLock,第一个电话获取锁收音机,但是随后的电话将被忽略。只有一个叫释放将被要求,无论多少次,获得被称为。

       2.2释放
       开启了无线保真技术广播,允许它关掉时,该装置是无用的。
       如果这WifiLock,每个电话是reference-counted释放会递减叁考计数,电台将会开启只有当叁考计数达到零。如果叁考计数下面去了零(即,如果释放被称为一个更大的次数比获得),唯一的例外就是丢出。
       如果这不是reference-counted WifiLock,第一次叫牌后释放

java代码:

import android.os.PowerManager; 
import android.net.wifi.WifiManager; 


public class WifiLockExample{ 
private final Context mContext; 
private PowerManager.WakeLock mWakeLock = null; 
private WifiManager.WifiLock mWifiLock = null; 
public WifiLockExample(Context context){ 
mContext = context; 
PowerManager powerManager = (PowerManager) 
mContext.getSystemService(Context.POWER_SERVICE); 
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); 
mWakeLock.setReferenceCounted(true); 

WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 
mWifiLock = wifiManager.createWifiLock(WIFILOCK_KEY); 
mWifiLock.setReferenceCounted(true); 
} 


private void acquire(){ 
mWakeLock.acquire(); 
mWifiLock.acquire(); 
} 


private void release(){ 
mWifiLock.release(); 
mWakeLock.release(); 
} 
} 
AlarmManager.RTC和ELAPSED_REALTIME的区别
AlarmManager.RTC,硬件闹钟,不唤醒手机(也可能是其它设备)休眠;当手机休眠时不发射闹钟。
AlarmManager.RTC_WAKEUP,硬件闹钟,当闹钟发躰时唤醒手机休眠;
AlarmManager.ELAPSED_REALTIME,真实时间流逝闹钟,不唤醒手机休眠;当手机休眠时不发射闹钟。
AlarmManager.ELAPSED_REALTIME_WAKEUP,真实时间流逝闹钟,当闹钟发躰时唤醒手机休眠;
 
RTC闹钟和ELAPSED_REALTIME最大的差别就是前者可以通过修改手机时间触发闹钟事件,后者要通过真实时间的流逝,即使在休眠状态,时间也会被计算。
如何检查Android后台服务线程(Service类)是否正在运行
如何检查后台服务(Android的Service类)是否正在运行?我希望我的Activity能够显示Service的状态,然后我可以打开或者关闭它。

 

回答:

Android系统提供了一个函数ActivityManager.getRunningServices可以列出当前正在运行的后台服务线程

private boolean isServiceRunning() {
    ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
        if ("com.example.MyService".equals(service.service.getClassName())) {
            return true;
        }
    }
    return false;
}

这个方法是可靠的,因为这是由Android系统提供的服务查询办法。
所以以来于OnDestroy或者OnXXX方法,甚或是Binders以及静态变量的方法都是不可靠的,因为作为一个开发者,你永远不知道Android系统什么时候会杀掉你的进程来释放内存,那些回调函数很可能根本没机会被调用。

另外,关于如果希望手工检查所有后台服务的运行状态,见如何观察和控制正在运行的Android后台服务.
Android入门之增删改查通讯录
一、通讯录应用介绍



通讯录应用是Android自带的应用程序,我们看到此应用的时候,可能只认为这是一个应用,用数据库存储数据,但是实际上不是这样的。

通讯录是ContentProvider的应用,通讯录由两部分组成:

(1)com.android.providers.contacts的ContentProvider:真正存储数据的ContentProvider

(2)com.android.contacts:运用ContentResolver获取数据的图形用户界面;



二、获取ContactProvider的源代码

Android源代码: http://my.oschina.net/zhanglubing/blog/40623 用git获取;

如果要获取ContactProvider,则安装git,并打开git bash,输入

git clone https://android.googlesource.com/platform/packages/providers/ContactsProvider.git 即可;

目录结构如下:

为何要获取ContactProvider的源代码呢?

因为如果要访问ContentProvider,必须要了解URI的设置(authority,path等);只有查看源代码才能够知道;

AndroidManifest.xml为清单文件,列出了ContactProvider的authorities,以及要访问通讯录需要的权限;

 

[html] view plaincopyprint?
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />

主要的通讯录程序为ContactsProvider2.java,authorities为:contacts或com.android.contacts;


三、通讯录数据库结构介绍

表结构如下:











通讯录是存放在/data/data/com.android.providers.contacts/databases/contacts2.db,里面主要的表有:

(1)raw_contacts:存放联系人的ID,

_id属性为主键,声明为autoincrement,即不需要手动设置,其他属性也不需要手动设置就有默认值;

display_name属性为姓名;

(2)mimetypes:存放数据的类型,比如"vnd.android.cursor.item/name"表示“姓名”类型的数据,"vnd.android.cursor.item/phone_v2"表示“电话”类型的数据;

(3)data:存放具体的数据;

raw_contact_id属性用来连接raw_contacts表,每条记录表示一个具体数据;我们主要的数据(email、phone等)都存放在data表;

data1属性存放总数据;

data2属性:

-如果此记录存放姓名,则data2存放名;

-如果此记录存放电话,则data2存放类型,比如手机、家电;

-如果此记录存放组织,则data2存放类型,比如公司、其他;

-如果此记录存放地址,则data2存放类型,比如住宅,单位等;



四、对通信录做增删改查


简单的说:对通讯录操作就是对一个普通的ContentProvider操作;



1.Query

(1)根据电话号码查询姓名

[java] view plaincopyprint?
//根据电话号码查询姓名(在一个电话打过来时,如果此电话在通讯录中,则显示姓名)
public void testReadNameByPhone(){
String phone = "12345678";
//uri= content://com.android.contacts/data/phones/filter/#
Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/"+phone);
ContentResolver resolver = this.getContext().getContentResolver();
Cursor cursor = resolver.query(uri, new String[]{Data.DISPLAY_NAME}, null, null, null); //从raw_contact表中返回display_name
if(cursor.moveToFirst()){
Log.i("Contacts", "name="+cursor.getString(0));
}
}

(2)查询所有的联系人


[java] view plaincopyprint?
//读取通讯录的全部的联系人
//需要先在raw_contact表中遍历id,并根据id到data表中获取数据
public void testReadAll(){
//uri = content://com.android.contacts/contacts
Uri uri = Uri.parse("content://com.android.contacts/contacts"); //访问raw_contacts表
ContentResolver resolver = this.getContext().getContentResolver();
Cursor cursor = resolver.query(uri, new String[]{Data._ID}, null, null, null); //获得_id属性
while(cursor.moveToNext()){
StringBuilder buf = new StringBuilder();
int id = cursor.getInt(0);//获得id并且在data中寻找数据
buf.append("id="+id);
uri = Uri.parse("content://com.android.contacts/contacts/"+id+"/data"); //如果要获得data表中某个id对应的数据,则URI为content://com.android.contacts/contacts/#/data
Cursor cursor2 = resolver.query(uri, new String[]{Data.DATA1,Data.MIMETYPE}, null,null, null); //data1存储各个记录的总数据,mimetype存放记录的类型,如电话、email等
while(cursor2.moveToNext()){
String data = cursor2.getString(cursor2.getColumnIndex("data1"));
if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/name")){ //如果是名字
buf.append(",name="+data);
}
else if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/phone_v2")){ //如果是电话
buf.append(",phone="+data);
}
else if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/email_v2")){ //如果是email
buf.append(",email="+data);
}
else if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/postal-address_v2")){ //如果是地址
buf.append(",address="+data);
}
else if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/organization")){ //如果是组织
buf.append(",organization="+data);
}
}
String str = buf.toString();
Log.i("Contacts", str);
}
} 

2.Insert

(1)一步一步添加数据


[java] view plaincopyprint?
//一步一步添加数据
public void testAddContacts(){
//插入raw_contacts表,并获取_id属性
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
ContentResolver resolver = this.getContext().getContentResolver();
ContentValues values = new ContentValues();
long contact_id = ContentUris.parseId(resolver.insert(uri, values));
//插入data表
uri = Uri.parse("content://com.android.contacts/data");
//add Name
values.put("raw_contact_id", contact_id);
values.put(Data.MIMETYPE,"vnd.android.cursor.item/name");
values.put("data2", "zdong");
values.put("data1", "xzdong");
resolver.insert(uri, values);
values.clear();
//add Phone
values.put("raw_contact_id", contact_id);
values.put(Data.MIMETYPE,"vnd.android.cursor.item/phone_v2");
values.put("data2", "2"); //手机
values.put("data1", "87654321");
resolver.insert(uri, values);
values.clear();
//add email
values.put("raw_contact_id", contact_id);
values.put(Data.MIMETYPE,"vnd.android.cursor.item/email_v2");
values.put("data2", "2"); //单位
values.put("data1", "xzdong@xzdong.com");
resolver.insert(uri, values);
}



(2)批量添加数据

核心代码:
(1)ContentProviderOperation operation = ContentProviderOperation.newInsert(uri).withValue("key","value").build();
(2)resolver.applyBatch("authorities",operations);//批量提交



[java] view plaincopyprint?
<span style="font-size:18px;">public void testAddContactsInTransaction() throws Exception {
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
ContentResolver resolver = this.getContext().getContentResolver();
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
// 向raw_contact表添加一条记录
//此处.withValue("account_name", null)一定要加,不然会抛NullPointerException
ContentProviderOperation operation1 = ContentProviderOperation
.newInsert(uri).withValue("account_name", null).build();
operations.add(operation1);
// 向data添加数据
uri = Uri.parse("content://com.android.contacts/data");
//添加姓名
ContentProviderOperation operation2 = ContentProviderOperation
.newInsert(uri).withValueBackReference("raw_contact_id", 0)
//withValueBackReference的第二个参数表示引用operations[0]的操作的返回id作为此值
.withValue("mimetype", "vnd.android.cursor.item/name")
.withValue("data2", "xzdong").build();
operations.add(operation2);
//添加手机数据
ContentProviderOperation operation3 = ContentProviderOperation
.newInsert(uri).withValueBackReference("raw_contact_id", 0)
.withValue("mimetype", "vnd.android.cursor.item/phone_v2")
.withValue("data2", "2").withValue("data1", "0000000").build();
operations.add(operation3);
resolver.applyBatch("com.android.contacts", operations);
}</span>

3.Delete


核心思想:
(1)先在raw_contacts表根据姓名查出id;
(2)在data表中只要raw_contact_id匹配的都删除;

[java] view plaincopyprint?
public void testDelete()throws Exception{
String name = "xzdong";
//根据姓名求id
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
ContentResolver resolver = this.getContext().getContentResolver();
Cursor cursor = resolver.query(uri, new String[]{Data._ID},"display_name=?", new String[]{name}, null);
if(cursor.moveToFirst()){
int id = cursor.getInt(0);
//根据id删除data中的相应数据
resolver.delete(uri, "display_name=?", new String[]{name});
uri = Uri.parse("content://com.android.contacts/data");
resolver.delete(uri, "raw_contact_id=?", new String[]{id+""});
}
}


4.Update


核心思想:

(1)不需要更新raw_contacts,只需要更新data表;

(2)uri=content://com.android.contacts/data 表示对data表进行操作;


[java] view plaincopyprint?
public void testUpdate()throws Exception{
int id = 1;
String phone = "999999";
Uri uri = Uri.parse("content://com.android.contacts/data");//对data表的所有数据操作
ContentResolver resolver = this.getContext().getContentResolver();
ContentValues values = new ContentValues();
values.put("data1", phone);
resolver.update(uri, values, "mimetype=? and raw_contact_id=?", new String[]{"vnd.android.cursor.item/phone_v2",id+""})
}
Global site tag (gtag.js) - Google Analytics