Android-下拉刷新库

前言

入职接近半个多月,有几天空闲,所以想着能不能自己实现一个库来练练手,因为之前一直想要实现下拉刷新的功能,因此就有了这样一个自制的下拉刷新库——RefreshWidgetLib.

关于下拉刷新

下拉刷新,作为一个几乎每个应用都会出现的一种控件,不言而喻,它对于提高用户体验有着很重要的作用,而且也已经成为了人们习惯的一种操作。说起下拉刷新这种设计,最早的引入者是在2008年上线的Tweetie,Tweetie引入了如今随处可见的“下拉刷新”设计,不仅有多达数百款App Store应用使用这种设计,就连苹果的Mail应用也采纳了这一元素。现在我们在各大平台的应用上都可以看见这一元素,而且样式丰富多彩,体验更是很不错。当然后期也出现一些质疑下拉刷新这种设计的不人性的看法,但是对于我来说,我认为这种设计不仅成为了用户习惯式的一种需求,而且它也有创造更多优质体验的可能。

关于实现思路

下拉刷新,从以前到现在,网上已经有很多开源的下拉刷新库,而且,google官方后期也推出一个下拉刷新的控件,所以可以说,我们可以使用的资源是很丰富的。那么还有什么意义去实现它呢,我觉得即使我们有很多轮子可以用,如果自己有机会,也需要学会去造轮子。

回到实现思路的话题,假如现在有一个ListView,我们想要让它具有下拉刷新的功能,那么大的方向有两个:

1)继承ListView,利用ListView本身自带的addHeaderView()方法,通过监听滚动事件,实现下拉刷新的效果。

2)自定义一个ViewGroup,依次添加HeaderView,ListView和FooterView,通过重写触摸事件onInterceptTouchEvent()方法拦截触摸事件,重写onTouchEvent()方法处理相关逻辑。
外部ViewGroup的实例不同,相对应的,下拉刷新控件的效果也不同,这里的ViewGroup可以是LinearLayout,也可以是FrameLayout.

关于本篇的实现思路

从上面我们可以看出实现思路是可以有很多选择的,本篇博客我的实现思路是,使用LinearLayout布局包裹HeaderView,ListView,FooterView,其他的实现思路我会在后面持续实现更新。

接下来是具体分析:

首先来看RefreshWidget的结构,外部布局为LinearLayout,排列方式为Vertical, 内部三个视图依次为HeaderView,ContentView,FooterView,这里的ContentView可以为ListView,也可以是GridView,也可以是SrcollView,取决于我们子类对于ContentView的实现方式。

这里以ContentView实例化为ListView作为例子,最初结构图如下:

这里写图片描述

可以看到,我们在LinearLayout中依次添加了HeaderView,ListView和FooterView,但是我们希望默认情况下,也就是没有上拉或者下拉的时候,HeaderView和FooterView隐藏起来。这时候,我们可以通过设置topMargin或者bottomMargin来使得HeaderView和FooterView被隐藏起来,如下:

这里写图片描述

然后,接下来就是重写触摸事件处理逻辑的部分,我们重写onInterceptTouchEvent()方法。因为LinearLayout内部的View仍然需要处理触摸事件,例如ListView仍然需要处理点击Item,处理ListView自身的滚动事件,所以我们不能完全拦截触摸事件,也不能只重写onTouchEvent()方法,要合理适当的拦截触摸事件。

所以正确的做法是,在onInterceptTouchEvent()方法中判断,当传递进来的是ACTION_MOVE事件,我们需要判断此时是否处于ListView的最顶部或者处于ListView的最底部,如果是最顶部或者最底部,则对触摸事件进行拦截,否则不做拦截,仍然把触摸事件交给子View(这里也就是ListView)去处理。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = ev.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                mMoveY = ev.getRawY();
                /* 如果不属于触摸滑动范围,则跳出 */
                if (Math.abs(mDownY - mMoveY) < mTouchSlop) return false;

                /* 如果处于顶部,且继续下拉,进入下拉刷新状态,同时拦截触摸事件 */
                if (mCurrentStatus == STATUS_NORMAL && isReachHeader() && mMoveY - mDownY > 0 && 
                        mRefreshEnabled) {
                    mCurrentStatus = STATUS_REFRESH;
                    mHeaderView.onRefresh(0);
                    return true;
                } else if (mCurrentStatus == STATUS_NORMAL && isReachFooter() && mMoveY - mDownY 
                        < 0 && mLoadMoreEnabled) {
                    /* 如果处于底部,且继续上拉,进入上拉加载更多状态,同时拦截触摸事件 */
                    mCurrentStatus = STATUS_LOAD_MORE;
                    mFooterView.onLoadMore(0);

                    /* 加上这一句,可以使得ContentView在上拉的过程中保持滑到最底部的状态 */
                    makeContentViewToFooter();
                    return true;
                }
                break;
        }
        /* 其他情况不拦截,默认返回false */
        return super.onInterceptTouchEvent(ev);
    }

接下来,假如我们已经判断出手指正在执行下拉刷新的操作,那么我们将触摸事件拦截,交由onTouchEvent()处理,通过设置topMargin和bottomMargin,使得HeaderView重新进入手机的可见区域

case MotionEvent.ACTION_MOVE:
                mMoveY = event.getRawY();
                /* 下拉刷新状态 且正在向下滑动 */
                if ((mCurrentStatus == STATUS_REFRESH || mCurrentStatus == STATUS_RELEASE_TO_REFRESH)
                        && mMoveY - mDownY >= 0 ) {
                    if (mMoveY - mDownY > mHeaderHeight * mHeaderPullProportion) {
                        mCurrentStatus = STATUS_RELEASE_TO_REFRESH;
                        mHeaderView.onReleaseToRefresh();
                        setHeaderTopMargin(0);
                        setHeaderBottomMargin((int) (mMoveY - mDownY - mHeaderHeight * mHeaderPullProportion));
                    } else {
                        mCurrentStatus = STATUS_REFRESH;
                        mHeaderView.onRefresh((mMoveY- mDownY) / ((float)mHeaderHeight * mHeaderPullProportion));
                        setHeaderTopMargin(-mHeaderHeight + (int) ((mMoveY - mDownY) / mHeaderPullProportion));
                        setHeaderBottomMargin(0);
                    }
                } 

当松手之后,我们还需要一个回弹的动作,这里我们用属性动画来完成

    /**
     * 下拉刷新任务
     */
    private void headerRefreshTask() {
        final int value = mHeaderLayoutParams.bottomMargin;
        mMainThreadHandler.post(new Runnable() {
            @Override
            public void run() {
                ValueAnimator valueAnimator = ValueAnimator.ofFloat(value,
                        0);
                valueAnimator.setInterpolator(new LinearInterpolator());
                valueAnimator.setDuration(HEADER_REFRESH_TIME);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float value = (float) animation.getAnimatedValue();
                        setHeaderBottomMargin((int) value);
                    }
                });
                valueAnimator.start();
            }
        });
    }

关于我的下拉刷新库-RefreshWidgetLib

github地址:https://github.com/82367825/RefreshWidget

上面介绍完基本原理之后,让我们来看看成果,先来看看效果:

这里写图片描述

这里写图片描述

工程库库的UML图,主要的逻辑设计都在基类BaseRefreshWidget中实现了,不同的下拉刷新控件都是继承自这个基类。

这里写图片描述

如何使用RefreshWidgetLib

举例RefreshListViewWidget的使用,就如同普通的ListView一样使用

在布局文件中添加

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <com.zero.refreshwidgetlib.widget.RefreshListViewWidget
        android:id="@+id/refresh_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </com.zero.refreshwidgetlib.widget.RefreshListViewWidget>
</LinearLayout>

然后在Activity中添加数据

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_listview);
        initData();
        mRefreshListViewWidget = (RefreshListViewWidget) findViewById(R.id.refresh_list);
        mRefreshListViewWidget.setAdapter(new ListViewAdapter());
        mRefreshListViewWidget.setRefreshListener(new RefreshListener() {
            @Override
            public void onRefresh() {
                refreshData();
            }

            @Override
            public void onLoadMore() {
                loadMoreData();
            }
        });
        mRefreshListViewWidget.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(ListViewDemoActivity.this, "click " + position + " item", 
                        Toast.LENGTH_SHORT).show();
            }
        });
    }

当数据加载或者刷新完成,我们可以调用

mRefreshListViewWidget.completeRefresh();

我们也可以禁用上拉或者下拉

    /**
     * 设置下拉刷新功能是否可用
     * @param enabled
     */
    void setRefreshEnabled(boolean enabled);

    /**
     * 设置上拉加载功能是否可用
     * @param enabled
     */
    void setLoadMoreEnabled(boolean enabled);

当然,这些用法都是和系统的刷新控件以及其他第三方下拉刷新控件很类似的,可能看起来就没什么新意,但是还是有一些补充的地方,就是HeaderView和FooterView的拓展实现。

实现自己的HeaderView和FooterView

默认情况下,如果不添加自定义的HeaderView或者FooterView,RefreshWidgetLib会默认帮你生成一个只显示文字效果的HeaderView或者FooterView.

假如我们想要实现自己的HeaderView,当下拉的时候,加入一些自己的效果,让控件更加好看,也是很容易的。我们只需要继承BaseHeader,然后实现以下三个已经嵌入代码框架里的方法就可以了。

onRefresh()是当RefreshListViewWidget正在下拉操作的时候被调用;
onReleaseToRefresh()是当RefreshListViewWidget已经下拉超过指定范围被调用;
onRefreshIng()则是当RefreshListViewWidget松手之后被调用。

percent是一个关键的参数,它表示下拉的比例大小,通常情况下为:下拉距离 / HeaderView高度

    /**
     * 正在下拉刷新
     * @param percent 下拉完成比例
     */
    void onRefresh(float percent);

    /**
     * 松手刷新
     */
    void onReleaseToRefresh();

    /**
     * 正在刷新
     */
    void onRefreshIng();

举个例子,比如我想实现上面gif图里的波浪下拉HeaderView,那么就像上面说的,继承BaseHeader,实现三个方法。


package com.zero.refreshwidgetlib.header;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;

import com.zero.refreshwidgetlib.utils.DrawUtils;

/**
 * Anim HeaderView
 * @author linzewu
 * @date 16-6-29
 */
public class HeaderAnimView extends BaseHeader{

    private static final String TAG = "HeaderAnimView";

    private static final int STATUS_NORMAL = 0;
    private static final int STATUS_START_TO_REFRESH = 1;
    private static final int STATUS_RELEASE_TO_REFRESH = 2;
    private static final int STATUS_REFRESH_ING = 3;

    private int mCurrentStatus = STATUS_NORMAL;

    private static final float MAX_HEIGHT = 100;
    private static final int DEFAULT_COLOR = 0x4400AAFF;

    private int mColor = DEFAULT_COLOR;

    private Paint mPaint;
    private Path mPath;

    protected float mWidth;
    protected float mHeight;

    protected boolean mHasInit = false;

    private Handler mMainThreadHandler = new Handler();

    /**
     * 正在刷新TextView
     */
    private TextView mRefreshingView;

    /**
     * 下拉曲线的变量点
     */
    private PointF mLeftPoint,mRightPoint,mControlPoint;

    /**
     * 刷新波浪的变量点
     */
    private PointF mPointF1,mPointF2,mPointF3,mPointF4,mPointF5;

    public HeaderAnimView(Context context) {
        super(context);
        init();
    }

    public HeaderAnimView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public HeaderAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec, DrawUtils.dip2px(getContext(), MAX_HEIGHT));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
        super.onSizeChanged(w, h, oldW, oldH);
        if (!mHasInit) {
            mWidth = w;
            mHeight = h;
        }
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mColor);

        mRefreshingView = new TextView(getContext());
        mRefreshingView.setGravity(Gravity.CENTER);

        mPath = new Path();
        mLeftPoint = new PointF();
        mRightPoint = new PointF();
        mControlPoint = new PointF();

        mPointF1 = new PointF();
        mPointF2 = new PointF();
        mPointF3 = new PointF();
        mPointF4 = new PointF();
        mPointF5 = new PointF();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        mPath.reset();
        if (mCurrentStatus == STATUS_START_TO_REFRESH || mCurrentStatus ==
                STATUS_RELEASE_TO_REFRESH) {
            mLeftPoint.x = 0;
            mLeftPoint.y = mHeight - mHeight * mPercent;
            mRightPoint.x = mWidth;
            mRightPoint.y = mHeight - mHeight * mPercent;
            mControlPoint.x = mWidth / 2;
            mControlPoint.y = mHeight + mHeight * mPercent * 0.9f;
            mPath.moveTo(mLeftPoint.x, mLeftPoint.y);
            mPath.quadTo(mControlPoint.x, mControlPoint.y, mRightPoint.x, mRightPoint.y);
            mPath.moveTo(mLeftPoint.x, mLeftPoint.y);
            canvas.drawPath(mPath, mPaint);
        }  else if (mCurrentStatus == STATUS_REFRESH_ING) {
            /* 绘制波浪 */
            mPath.moveTo(mPointF1.x, mPointF1.y);
            mPath.quadTo((mPointF1.x + mPointF2.x) / 2, mPointF2.y + 20, mPointF2.x, mPointF2.y);
            mPath.quadTo((mPointF2.x + mPointF3.x) / 2, mPointF3.y - 20, mPointF3.x, mPointF3.y);
            mPath.quadTo((mPointF3.x + mPointF4.x) / 2, mPointF4.y + 20, mPointF4.x, mPointF4.y);
            mPath.quadTo((mPointF4.x + mPointF5.x) / 2, mPointF5.y - 20, mPointF5.x, mPointF5.y);
            mPath.lineTo(mPointF5.x, 0);
            mPath.lineTo(mPointF1.x, 0);
            mPath.lineTo(mPointF1.x, mPointF1.y);
            canvas.drawPath(mPath, mPaint);
        }
    }

    @Override
    public void onRefresh(float percent) {
        super.onRefresh(percent);
        this.mCurrentStatus = STATUS_START_TO_REFRESH;
        this.mPercent = percent;
        invalidate();
    }

    @Override
    public void onReleaseToRefresh() {
        super.onReleaseToRefresh();
        this.mCurrentStatus = STATUS_RELEASE_TO_REFRESH;
        this.mPercent = 1;
        invalidate();
    }

    @Override
    public void onRefreshIng() {
        super.onRefreshIng();
        this.mCurrentStatus = STATUS_REFRESH_ING;
        drawWave();
    }

    /**
     * draw the wave
     */
    private void drawWave() {
        mPointF1.x = -mWidth;
        mPointF1.y = 0.3f * mHeight;
        mMainThreadHandler.post(new Runnable() {
            @Override
            public void run() {
                ValueAnimator valueAnimator = ValueAnimator.ofFloat(-mWidth, 0);
                valueAnimator.setInterpolator(new LinearInterpolator());
                valueAnimator.setDuration(1500);
                valueAnimator.setRepeatMode(ValueAnimator.RESTART);
                valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        mPointF1.x = (float) animation.getAnimatedValue();
                        mPointF1.y = 0.3f * mHeight;
                        mPointF2.x = mPointF1.x + mWidth * 0.5f;
                        mPointF2.y = mPointF1.y;
                        mPointF3.x = mPointF1.x + mWidth;
                        mPointF3.y = mPointF1.y;
                        mPointF4.x = mPointF1.x + mWidth * 1.5f;
                        mPointF4.y = mPointF1.y;
                        mPointF5.x = mPointF1.x + mWidth * 2;
                        mPointF5.y = mPointF1.y;
                        invalidate();
                    }
                });
                valueAnimator.start();
            }
        });
    }
}

通过继承BaseHeader,我们实现了自己的HeaderView,然后我们再调用添加,同样的,FooterView也一样。

mRefreshListViewWidget.addHeaderView(new HeaderAnimView(ListViewDemoActivity.this));
mRefreshListViewWidget.addFooterView(new FooterAnimView(ListViewDemoActivity.this));

其他下拉刷新控件

通过这个库,我们可以轻松自定义自己想要的效果的headerView和footerView,也可以实现让自己希望的控件具备下拉刷新的功能。上面提到RefreshListViewWidget使得ListView具备了下拉刷新的功能,当然也可以让GridView具备下拉刷新的效果,还可以让ScrollView具备下拉刷新的效果。

哈哈,当然前提是,目标View本身像ListView、GridView等具有滚动的能力。

怎么做呢,继承基类BaseRefreshWidget,然后实现几个关键的方法,以ScrollView为例子:


    /**
     * 滑动到头部
     * @return
     */
    protected boolean isReachHeader(){
        return mContentView.getScrollY() == 0;
    }

    /**
     * 滑动到底部
     * @return
     */
    protected boolean isReachFooter(){
        View contentView = ((ScrollView)mContentView).getChildAt(0);
        return contentView.getMeasuredHeight() <= mContentView.getScrollY() + mContentView.getHeight();
    }


    @Override
    protected View getContentView() {
        return new ScrollView(getContext());
    }


    /**
     * 使得ContentView保持滚到底部
     */
    @Override
    protected void makeContentViewToFooter() {
        View contentView = ((ScrollView)mContentView).getChildAt(0);
        int realHeight = contentView.getMeasuredHeight();
        mContentView.scrollTo(0, realHeight - mContentView.getHeight());
    }

    /**
     * 使得ContentView不再保持滚到底部
     */ 
    @Override
    protected void makeContentViewRestore() {
    }

具体更多的代码细节可以看我的源码,也是上面提到的我的工程源码:

https://github.com/82367825/RefreshWidget

这个库算是我第一个完全靠自己思考和编码的一个工程库,可能会有很多不如人意或者错漏的地方,以后我也会不断地更新和维护这个工程,希望做得更好。

如有疑问,欢迎指正~

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

从AIDL看Android跨进程通信 - 2016-07-24 14:07:38

AIDL是Android实现IPC的一种重要的方式,理解它的原理对理解Android进程间通信有很大的帮助。AIDL的定义,已经有很多介绍的文章了,这里就不做详解了。我们直接从实例入手来分析AIDL实现原理。 AIDL的使用 首先需要定义AIDL接口IMyService.aidl: // IMyService.aidl package com.chuck.aidldemo; // Declare any non-default types here with import statements inter
OC与Swift两种实现方式基本上区别不大,主要是在一些对象或方法的调用方式不同 OC代码样式: self.view.backgroundColor = [UIColor blackColor];          //加载颗粒状的火花图片     CAEmitterLayer *emitterLa = [CAEmitterLayer layer];     emitterLa.emitterPosition = CGPointMake(self.view.bounds.size.width/2, sel
前言 相信很多朋友在开发中都会遇到图片上传的情况,尤其是多图上传,最 经典的莫过于微信的图片选择了。所有很多情况下会使用到多图选择。 所以就有了这篇文章,今天抽点时间写了个控件。 支持自定义选择图片的样式 支持设置图片选择数量 支持图片预览,删除 支持图片拍照 先来看看效果 实现分析 假如不定义控件,我们要实现这样一个功能,无非是写个GridView在item点击的时候去显示图片进行选择,在返回界面的时候进行GridView的数据刷新。我们把这些逻辑写在我们自定义的GridView中,就成了一个新的控件。
在360对DroidPlugin的特点介绍中有云: 插件的四大组件完全不需要在Host程序中注册,支持Service、Activity、BroadcastReceiver、ContentProvider四大组件。 实现了进程管理,插件的空进程会被及时回收,占用内存低。 之所以支持Service,Activity,ContentProvider三大组件,是因为DroidPlugin在AndroidManifest文件中预先注册了8个运行插件的进程,每个进程预注册Service一个, ContentProvi
本篇介绍ListView控件,这是Android中比较重要也比较复杂的控件,这里只谈到使用ViewHolder机制优化即可。 一、ListView简介 ListView是Android系统中显示列表的控件,每个ListView都可以包含很多个列表项。 二、ListView的使用 概念不多说,直接来介绍使用方法。 ListView中比较复杂的是数据适配器,其作用是把复杂的数据(数组、链表、数据库、集合等)填充在指定视图界面,是连接数据源和视图界面的桥梁。常见的Android原生的适配器有ArrayAdapt
欢迎转载,转载请注明出处: http://blog.csdn.net/dmk877/article/details/51912104   相信不管做了多长时间开发的人都用过Tween动画,从刚开始工作到现在我也是用了N次Tween动画,但是每一次使用总感觉掌握的不够全面,所以花了点时间详细的总结了下Tween动画,其实在android中熟练掌握动画,能够帮助我们实现一些非常酷炫的效果从而使我们的app在交互或者用户体验上有一个更好的体验,鉴于此详细的学习动画还是很有必要的,相信通过本篇的学习大家会对Twe

Java(Android)回调函数详解 - 2016-07-23 17:07:29

一、前言 本周有位入行开发不久的朋友问我回调究竟是个什么概念,在网上看了很多的回调函数解释,但是越看越乱。虽然回调函数这个梗已经不新鲜了,这里还是用书面的形式记录下。 如果有了解的,就无需再看。 二、概念 概念上,这里引用百度百科的解释,如下: 回调函数就是一个通过 函数指针 调用的函数。如果你把函数的 指针 (地址)作为 参数传递 给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或

Android_聊天_表情 - 2016-07-23 17:07:26

接下来就进入聊天界面了,我的界面效果如下几个图所示: 这其中包括两个点:仿微信按住说话功能,表情管理 第一个,按住说话 按钮的功能,通过重写Button完成, /** * 控制录音Button * 1、重写onTouchEvent;(changeState方法、wantToCancel方法、reset方法); * 2、编写AudioDialogManage、并与该类AudioRecorderButton进行整合; * 3、编写AudioManage、并与该类AudioRecorderButton进行整合;

JAVA 面向对象 隐藏和封装 - 2016-07-23 17:07:20

本页面更新日期: 2016年07月22日 前言 在前面程序中,经常出现通过 某个对象直接访问其成员变量的情况. 这可能引起一些潜在问题,比如将某个 Person 的 age 成员变量直接设为 1000. 这在语法上没有任何问题, 但显然违背了当前的自然规律. 人怎么可能活到 1000岁 - - . (就现在的科学来讲) Java也考虑到了这种情况, 为你提供了 类和对象的成员变量 进行封装的方法,来保护成员变量不被恶意修改. 理解封装 封装(Encapsulation) 是面向对象的三大特征之一.(另外两
上一篇文章分析过DroidPlugin对Activity的处理过程,不得不为对DroidPlugin的工程师们钦佩不已,那么是不是Service可以像Activity的处理过程一样来处理呢?前面讲过每一个代理进程只是预定义了一个Service,如果某一个插件中有多个Service,那岂不是某一个时刻只能有一个Service运行呢?由此可以判定可能Service的处理和Activity不一样。 一方面:平时使用Activity主要是用于展示界面和用户交互,Activity的生命周期可能受用户控制,当用户操作