Chinaunix首页 | 论坛 | 博客
  • 博客访问: 581501
  • 博文数量: 192
  • 博客积分: 3780
  • 博客等级: 中校
  • 技术积分: 1487
  • 用 户 组: 普通用户
  • 注册时间: 2010-08-26 10:11
文章存档

2012年(6)

2011年(160)

2010年(26)

分类: Java

2011-11-10 10:59:51

50. Android自定义View实例AnalogClock源码

针 对Android底层View的直接构造很多网友没有实战经验,本次Android开发网结合目前平台开源代码一起通过AnalogClock类来理解 View的直接继承。AnalogClock就是Home Screen上的那个带有两根指针的表盘类。它的实现我们直接从开源代码可以了解到:

  public class AnalogClock extends View {
    private Time mCalendar;

    private Drawable mHourHand; //时针
    private Drawable mMinuteHand; //分针
    private Drawable mDial; //表盘背景

    private int mDialWidth; //表盘宽度
    private int mDialHeight; //表盘高度

    private boolean mAttached; //附着状态

    private final Handler mHandler = new Handler(); //定一个Handler类实现更新时间
    private float mMinutes;
    private float mHour;
    private boolean mChanged; //时间是否改变

    public AnalogClock(Context context) {
        this(context, null);
    }

    public AnalogClock(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AnalogClock(Context context, AttributeSet attrs,
                       int defStyle) {
        super(context, attrs, defStyle);
        Resources r = mContext.getResources();
        TypedArray a =
                context.obtainStyledAttributes(
                        attrs, com.android.internal.R.styleable.AnalogClock, defStyle, 0);

        mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial); //加载表盘资源
        if (mDial == null) {
            mDial = r.getDrawable(com.android.internal.R.drawable.clock_dial);
        }

        mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour); //加载时针图片资源
        if (mHourHand == null) {
            mHourHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
        }

        mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute); //加载分针图片
        if (mMinuteHand == null) {
            mMinuteHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
        }

        mCalendar = new Time(); //获取当前系统时间

        mDialWidth = mDial.getIntrinsicWidth(); //获取表盘图片的宽度
        mDialHeight = mDial.getIntrinsicHeight(); //高度,同上
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        if (!mAttached) {
            mAttached = true;
            IntentFilter filter = new IntentFilter(); //注册一个消息过滤器,获取时间改变、时区改变的action

            filter.addAction(Intent.ACTION_TIME_TICK);
            filter.addAction(Intent.ACTION_TIME_CHANGED);
            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);

            getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
        }

          mCalendar = new Time();

        onTimeChanged();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mAttached) {
            getContext().unregisterReceiver(mIntentReceiver); //反注册消息过滤器
            mAttached = false;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize =  MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize =  MeasureSpec.getSize(heightMeasureSpec);

        float hScale = 1.0f;
        float vScale = 1.0f;

        if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {
            hScale = (float) widthSize / (float) mDialWidth;
        }

        if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {
            vScale = (float )heightSize / (float) mDialHeight;
        }

        float scale = Math.min(hScale, vScale);

        setMeasuredDimension(resolveSize((int) (mDialWidth * scale), widthMeasureSpec),
                resolveSize((int) (mDialHeight * scale), heightMeasureSpec));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mChanged = true;
    }

    主要的绘图重写View的onDraw方法,我们可以看到通过canvas实例直接屏幕

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        boolean changed = mChanged;
        if (changed) {
            mChanged = false;
        }

        int availableWidth = mRight - mLeft;
        int availableHeight = mBottom - mTop;

        int x = availableWidth / 2;
        int y = availableHeight / 2;

        final Drawable dial = mDial;
        int w = dial.getIntrinsicWidth();
        int h = dial.getIntrinsicHeight();

        boolean scaled = false;

        if (availableWidth < w || availableHeight < h) {
            scaled = true;
            float scale = Math.min((float) availableWidth / (float) w,
                                   (float) availableHeight / (float) h);
            canvas.save();
            canvas.scale(scale, scale, x, y);
        }

        if (changed) {
            dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
        }
        dial.draw(canvas);

        canvas.save();
        canvas.rotate(mHour / 12.0f * 360.0f, x, y); //计算时针旋转的角度,android123提示就是那个时针图片的旋转角度,直接反应的就是表盘上那个针的时间
        final Drawable hourHand = mHourHand;
        if (changed) {
            w = hourHand.getIntrinsicWidth();
            h = hourHand.getIntrinsicHeight();
            hourHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
        }
        hourHand.draw(canvas);
        canvas.restore();

        canvas.save();
        canvas.rotate(mMinutes / 60.0f * 360.0f, x, y); //同理,分针旋转的角度

        final Drawable minuteHand = mMinuteHand;
        if (changed) {
            w = minuteHand.getIntrinsicWidth();
            h = minuteHand.getIntrinsicHeight();
            minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
        }
        minuteHand.draw(canvas);
        canvas.restore();

        if (scaled) {
            canvas.restore();
        }
    }

    private void onTimeChanged() {  //获取时间改变,计算当前的时分秒
        mCalendar.setToNow();

        int hour = mCalendar.hour;
        int minute = mCalendar.minute;
        int second = mCalendar.second;

        mMinutes = minute + second / 60.0f;
        mHour = hour + mMinutes / 60.0f;
        mChanged = true;
    }

    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { //监听获取时间改变action
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
                String tz = intent.getStringExtra("time-zone");
                mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
            }

            onTimeChanged(); //获取新的时间
            invalidate(); //刷新屏幕,强制类调用onDraw方法实现分针时针的走动
        }
    }; 

  看了本例根据,Android开发很简单吧,感兴趣的网友可以为本程序加入一个秒针,不过Android123提醒网友的是可能对于电池,以及系统运行效率产生一定的影响,不过作为练习大家可以试一试。

51. ArrayList LinkedList Set HashMap介绍

  在Android开发中我们经常需要对数据进行分类和操作,对于轻量级的数据存储我们可能不需要动用SQLite或效率以及类库不完善的XML,由于 SharedPreferences不具备数据枚举方法,如果仅仅是一个String或Int数组可以通过一个标记分割设计外,我们还是主要来看看 Android或者说Java提供的基础数据类型辅助类ArrayList LinkedList Set HashMap的介绍,如果你熟悉C++的STL或Boost库可以略过本文。

   在Java中提供了Collection和Map接口。其中List和Set继承了Collection接口;同时用Vector、ArrayList、 LinkedList三个类实现List接口,HashSet、TreeSet实现Set接口。直接有HashTable、HashMap、 TreeMap实现Map接口。

    Vector基于Array的List,性能也就不可能超越Array,并且Vector是“sychronized”的,这个也是Vector和ArrayList的唯一的区别。

    ArrayList:同Vector一样是一个基于Array的,但是不同的是ArrayList不是同步的。所以在性能上要比Vector优越一些。Android123提示大家适用于顺序性的查找

    LinkedList:不同于前面两种List,它不是基于Array的,作为链表数据结构方式,所以不受Array性能的限制。当对 LinkedList做添加,删除动作的时候只要更改nextNode的相关信息就可以实现了所以它适合于进行频繁进行插入和删除操作。这就是 LinkedList的优势,当然对于元素的位置获取等方面就逊色很多。

    List:

        1. 所有的List中只能容纳单个不同类型的对象组成的表,而不是Key-Value键值对。例如:[ tom,1,c ];

        2. 所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ];

        3. 所有的List中可以有null元素,例如[ tom,null,1 ];

        4. 基于Array的List(Vector,ArrayList)适合查询,而LinkedList(链表)适合添加,删除操作。

虽然Set同List都实现了Collection接口,但是他们的实现方式却大不一样。List基本上都是以Array为基础。但是Set则是在HashMap的基础上来实现的,这个就是Set和List的根本区别。

     HashSet:HashSet的存储方式是把HashMap中的Key作为Set的对应存储项,HashMap的key是不能有重复的。HashSet能快速定位一个元素,但是放到HashSet中的对象需要实现hashCode()方法0。

    TreeSet:将放入其中的元素按序存放,这就要求你放入其中的对象是可排序的。TreeSet不同于HashSet的根本是TreeSet是有序的。它是通过SortedMap来实现的。

    Set总结: 1. Set实现的基础是Map(HashMap); 2. Set中的元素是不能重复的,如果使用add(Object obj)方法添加已经存在的对象,则会覆盖前面的对象,不能包含两个元素e1、e2(e1.equals(e2))。

    Map是一种把键对象和值对象进行关联的容器,Map有两种比较常用的实现: HashTable、HashMap和TreeMap。

    HashMap也用到了哈希码的算法,以便快速查找一个键,

    TreeMap则是对键按序存放,因此它有一些扩展的方法,比如firstKey(),lastKey()等。

    HashMap和Hashtable的区别。 HashMap允许空(null)键(key)或值(value),由于非线程安全,效率上可能高于Hashtable。 Hashtable不允许空(null)键(key)或值(value)。

   有关更多实用的Android开发技巧我们将在后面的文章中着重介绍。

52. ConditionVariable Android线程同步

ConditionVariable 类位于android.os.ConditionVariable,它可以帮助Android线程同步。在SDK上的介绍 ConditionVariable不同于标准Java位于java.lang.Object wait() 和 notify() ,这个类可以等待自己,这就意味着 open(), close() 和 block() 可能会假死 ,如果使用ConditionVariable类的open()在调用 block() 之前, block() 将不会阻塞,相反将会返回立即。

   该类一共有4个方法

   boolean  block(long timeout)
  阻止当前线程知道条件是open,或直到超时,这里参数long timeout为超时设置,Android123提示大家如果你们从事过Win32开发,这个方法类似DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds); 函数。

  void  block()
  阻止当前线程知道条件 open ,是上面的无超时等待重载版本。

  void  close()
重置条件为 close状态。

void  open()
Open条件,释放所有线程的阻塞.

  ConditionVariable在创建时还有一种构造方法是 public ConditionVariable (boolean state) ,如果为true,默认时为opened,如果为false则是closed. ,默认public ConditionVariable () 为close().


53.Android开发之Eclipse调试技巧

使 用Google提供的ADT插件可以在Eclipse上很轻松的调试Android程序,我们切换到DDMS标签,选择“Devices”标签,我们可以 看到会出现类似下面的Debug Process(调试进程)、Update Threads(更新线程)、Update Heap(更新堆)、Cause GC(引起垃圾回收)、Stop Process(停止进程)、Screen Capture(屏幕截图)、Reset adb(重启Android Debug Bridge)

  这里我们就可以很好的观察Android程序运行时的各种状态,比如进程信息、线程分析、堆内存的占用,结束一个进程,当然这些操作都是在DDMS框架下 进行的,日程开发的程序是无法执行调用的。如果遇到adb调试桥运行不稳定时可以选择reset adb来重新启动adb.exe进程,整个界面如图:

很 多网友对于一些常的Android规程序性能测试、文件管理或屏幕截图均使用Eclipse中的DDMS插件来查看,其实通过SDK中提供的Dalvik Debug Monitor可以很好的调试Android程序,这里可以更直观的现实设备的各种信息,除了Logcat、VM Heap堆查看、Thread线程状态外,在菜单的Device中可以找到Screen capture来截图、File Explorer进行文件同步操作,使用Show process status可以显示设备当前的进程状态,以及 快速的过滤Logcat信息,可以分析无线状态radio state、程序状态app state等等。这里支持模拟器和真机的显示,该工具可以再android-sdk-windows-1.5_r1\tools\ddms.bat找到, 目前我们测试环境为Windows平台,下次讲述下CentOS中的操作,如图:

Android性能与调试很重要

  用于手持的移动设备,Android软件性能上需要多加考虑。首先Java VM在资源占用上开销是很大的,很多垃圾GC处理机制直接影响到内存释放和整个平台运行的流畅度。

  1.节省电量

  手机软件必须考虑的问题是省电,如果需要大型处理尽量由服务器处理,直接把结果返回到手持设备上。多线程也是一种奢侈的使用,但是I/O存储必需这样才能 保证流畅度,线程的阻塞将会降低用户体验,但是线程间切换调度的开销一直是重点。Android在DDMS中加入了Thread查看。

  2.内存占用

  在Eclipse+ADT插件的开发方式中,我们在DDMS中可以看到Heap堆内存的显示,Android开发网提示的是Java内存分配方式的问题, 尽量产生少的对象,比如字符串操作如果连加比较多,可以使用StringBuilder代替String类。在游戏开发中经常用到的图片可以通过切片的方 式从一个大的png图片上截取,或者放在gif文件作为逐帧保存,这样可以共用文件头减小体积。

  3.调试工具

  Android调试工具主要是模拟器中的Dev Tools和DDMS中的Logcat查看。当然模拟器自带的Dev Tools在功能上还是很详细的,可以显示CPU占用率,内存使用量,在单元测试时需要多加分析。

Android开发工具Dev Tools介绍

ndroid提供了很多开发调试工具除了ADB、TraceView、Logcat外,今天这个名为Dev Tools的Android开发调试工具隐藏在中, 为我们提供了强大的调试支持。我们在功能表中找到Dev Tools,运行后可以看到有很多条目,比如Development Settings,用来开发设置,进入后我们看到了比如Show CPU Usage这样的实用功能显示CPU占用率,帮助Android开发人员分析当前软件性能情况,今天就分析下Development Settings中的选项:

Wait for debugger 等待调试器
Enable ADB 启用ADB(android调试桥)
Show running processs (显示运行中的进程)
Show screen updates (显示屏幕更新)

  下面是一些常规的调试选项,Android开发网友情提示开启这些选项后可能会影响运行效率,这些探测选项也是CPU敏感的。

Immediately destroy activites (立即销毁activities)
Show CPU usage (显示CPU占用率)
Show background (显示北京)
Show Sleep state on LED (在休眠状态下LED开启)
Keep screen on while plugged in (保持屏幕开启当插入后)
Show GTalk service connection status (显示GTalk服务连接状态)


http://hi.baidu.com/286177943/blog/item/67fd99ac8a445f134b36d6b4.html

阅读(1198) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~