2018年8月

前言

这篇文章主要对优化ListView作简单的笔记,本文主要参考ListView的优化


ListView的RecycleBin机制

ListView被多次调用的方法就是适配器Adapter中的getView方法。getView方法需要返回一个View对象作为每个item的视图。简单来说,item的有多少个就会调用多少次getView方法。但并不是有多少个item就会创建多少个View,这样对于内存优化来说是很不友好的,如果View里面包含很多图片或者控件的话,估计手机都会卡死了。所以ListView里有一个RecycleBin机制,它会自动回收视图已经不可见的View对象,然后在滚动条向下滑动的时候将回收的View对象作为底部新出现的item来使用。所以getView方法的参数中有一个View convertView。具体可参考上述的链接,里面有详细的图片说明。


引入ViewHolder

  • 对布局的优化

按照上述的机制我们可以对convertView作一层判断,因为列表中item的布局一般都是使用同一个布局(当然也会出现多布局的情况,但是使用ListView不是特别方便)。根据上述的方案,在getView中就会有如下的写法:

if(convertView==null){  
  convertView=LayoutInflater.from(mContext).inflate(R.layout.activity_main,null);
}else{

}

首先判断convertView是否为null,如果是则将布局xml与其绑定。这里就遵循一个原则:item的布局是不变的,变的只是布局里面的内容。

  • 对控件的优化
    上述的方法只是对convertView这一布局做了优化而已,由于getView方法频繁被调用,这就意味着如果按照上述的写法,布局中的控件就会被频繁的创建,如:
TextView textView=convertView.findViewById(R.id.text);

这样也会增加内存的压力。所以我们可以引入一个ViewHolder的概念,这个其实在RecyclerView中已经是常规性操作了。

class ViewHolder{
    TextView textView;

    public ViewHolder(View itemView) {
        textView=itemView.findViewById(R.id.text);
    }
}

在Adapter中编写一个ViewHolder的内部类,我们可以将上述的TextView写成这个类的成员变量,然后在其构造方法中利用convertView来初始化TextView,然后将这个ViewHolder对象用convertView.setTag方法保存起来。

ViewHolder holder;
if(convertView==null){
   convertView=LayoutInflater.from(mContext).inflate(R.layout.activity_main,null);
   holder=new ViewHolder(convertView);
   convertView.setTag(holder);
}else{
   holder= (ViewHolder) convertView.getTag();
}

当convertView不是null的时候我们就可以从中取出这个ViewHolder,这样就避免了控件重复创建的问题了。


其它

关于ListView的优化其实还有很多,譬如如果item中包含图片的话还需注意资源回收的问题。还有就是static关键字的问题,譬如将Context对象定义成static的话就很容易造成内存泄漏。


总结

以下是对ListView的优化作一次总结:

  • Item布局,层级越少越好,使用hierarchyview工具查看优化。
  • 复用convertView
  • 使用ViewHolder
  • item中有图片时,异步加载
  • 快速滑动时,不加载图片
  • item中有图片时,应对图片进行适当压缩
  • 实现数据的分页加载

前言

今天来梳理一下有关Service的问题。


Service是否在main thread中执行,里面是否能执行耗时的操作?

默认情况,Service和Activity是运行在当前app所在进程的main thread(UI 主线程)里面的。service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件)。如果需要作耗时操作需要在service里另外开启线程。也可以在清单文件配置service执行所在的进程,让service在另外的进程中执行。此外还可以使用IntentService来实现多线程的耗时任务处理。


启动Service的方法和其生命周期

Service的生命周期:
20160523175053216.png

可以看出Service的生命周期分为两种形式,第一种为非绑定模式,另一种为绑定模式。

  • 非绑定模式:
    当第一次调用 startService 的时候执行的方法依次为 onCreate()、onStartCommand(),当 Service 关闭的时候调用 onDestory 方法。此方法适用于仅仅开启一个后台服务的情况。
  • 绑定模式:
    第一次 bindService()的时候,执行的方法为 onCreate()、 onBind()解除绑定的时候会执行 onUnbind()、onDestory()。此方法应用于Service与Activity需要进行数据交互的时候。

两种方法的区别是:如果仅使用一个模式时,绑定模式的生命周期随着开启它的组件,比如Activity被销毁后,该Service就会被销毁。而非绑定模式与开启它的组件的生命周期无关,它仅于调用它的进程相关,该进程被杀死后,Service才会被销毁。

注:Service 实例只会有一个,也就是说如果当前要启动的 Service 已经存 在了那么就不会再次创建该 Service 当然也不会调用 onCreate()方法。
一个 Service 可以被多个客户进行绑定,只有所有的绑定对象都执行了onBind()方法后该Service才会销毁,不过如果有一个客户执行了 onStart() 方法,那么这个时候如果所有的 bind 客户都执行了unBind()该Service也不会销毁。


IntentService

特点:

  • 会创建独立的 worker 线程来处理所有的 Intent 请求;
  • 会创建独立的 worker 线程来处理 onHandleIntent()方法实现的代码,无需处理多线程问题;
  • 所有请求处理完成后,IntentService 会自动停止,无需调用 stopSelf()方法停止Service;

值得一提的是,这里提到的worker线程其实是一个HandlerThread。在IntentService的方法中有一个叫onHandleIntent()方法,该方法就是运行在HandlerThread里的。我们可以在这方法里面做一些耗时的操作。具体可参考:IntentService


如何防止Service被系统自动杀死

具体方法可以参考:防止Service被系统自动杀死
其实上述方案只能做到降低Service被自动杀死的风险,而不能完全实现不被杀死这一流氓需求。

前言

intent在开发当中用得很多,有时候可能也会被封装好的工具类弄得并不能很好的理解intent的用法,今天来具体讲讲intent的用法。


显式intent

这里有一个关于intent七大属性的详细讲解,可以去看一下:关于Intent的七大属性
接下来就说一下显示的intent,这个其实很好理解。大体意思就是我有明确的目标去实现,就比如activity的跳转。

Intent intent=new Intent();
intent.setClassName(this,TextActivity.class);
startActivity(intent);

上述代码就是一个很简单的显式意图了,有明确的目标(TextActivity)跳转。还有一种写法是利用ComponentName来设置:

Intent intent=new Intent();
intent.setComponent(new ComponentName(this,TextActivity.class));
startActivity(intent);

当然我们还可以通过Extra来设置跳转时的数据传递。关于Extra后面会有讲述。


隐式intent

关于隐式调用设计到intent的Action,Category,Data,Type这几个属性,这里重点讲一下action和data,其他在上述的链接当中也有讲解。

  • Action——动作

    Intent intent=new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    startActivity(intent);

如上,我们在intent中设置一个action为Intent.ACTION_VIEW,代表浏览的意思。当程序执行到这里时,系统就会弹出所有满足这个action的应用让我们选择。TIM图片20180803165019.jpg

  • Data——数据条件(URI和数据类型)

我们还可以通过添加data实现数据的传递:

intent.setData(Uri.parse("http://www.baidu.com/"));

我们给data设置了一个url:http://www.baidu.com/,它的scheme为http,host为www.baidu.com,运行的时候首先会判断协议头http,然后通过http定义为我们需要打开一个浏览器去浏览www.baidu.com。如果您的手机有多个浏览器应用或者是登记了有该scheme为http的应用就会像上述一样通过弹窗提示选择应用打开了。

上面说了一种登记data的方法其实就是在项目的Androidmanifest.xml中预先设置好action和data,当运行上述代码时,就会在弹窗中多出自己的应用供用户选择。

<activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <data android:scheme="http" android:host="www.baidu.com"/>
        </intent-filter>
</activity>

注:category节点是必须设置的,至少像上述那样设置一个默认的。
这个scheme还有很多,譬如tel、sms,电话协议与短信协议。逻辑都是类似的。
当然我们还可以通过自定义一些协议头来作一些隐式的调起操作,这个可能要视具体情况来设计了。


Intent传递数据时,可以传递哪些类型数据?

这是我上次去面试做笔试时遇到的一道题,这个问题还挺常见的。
intent可以通过putExtra来设置需要传递的数据,还有一种方法就是上述的setData方法。

  • Extra:我们可以通过putExtra方法来将想要传递的数据设置到intent当中。
    这个方法支持java的基本数据类型、String类型、实现CharSequence接口的类型及数组(如:String, StringBuilder和StringBuffer)、实现了Serializable的对象及其数组、实现了Parcelable的对象及其数组、Bundle类型对象

有关Serializable接口可以参考此文章Serializable

有关Parcelable接口可以参考此文章Parcelable

  • Data:根据上述所属,data是通过url的形式传输的,一般传输http、ftp协议的网络地址,或者根据某种协议传递字符串的数据。

Intent 和 Intent Filter

  • Intent:Activity,Service和broadcast receiver之间相互激活的手段。它的作用分为显式意图和隐式意图,就是上述所讲的内容。
  • Intent Filter:意图过滤,一般出现在android Manifest文件中,就像上面所述。有一种特殊情况就是在动态注册广播监听的时候会出现在代码当中:

    IntentFilter intentFilter=new IntentFilter();
    intentFilter.addAction("aaaa");
    registerReceiver(receiver,intentFilter);

上述的addAction方法对应就是android Manifest中的action结点了,当需要响应对应的广播时在intent中添加"aaaa"的action就会在对应的广播监听中接收到。

前言

今天看一些面试题总结了一些有关Activity的知识。


什么是Activity

Activity是Android四大组件之一,setContentView方法可设置该页面显示的布局。
Activity是Context的子类,同时也实现了window.callback、keyevent.callback的方法,实现处理与窗体用户的交互事件。
通常在项目中会自定义一个BaseActivity来规定一些Activity的共同特点与功能


有关Activity的生命周期

Activity的生命周期为onCreate->onStart->onResume->onPause->onStop->onDestory。分别对应从创建到销毁的不同过程。
其中生命周期的方法是两两对应的:

  • onCreate创建与onDestory销毁
  • onStart可见与onStop不可见
  • onResume可编辑(获取焦点)与onPause

除此之外还有onRestart方法,这个方法在Activity调用onStop之后,但没有调用onDestory时,再次启动该Activity时就会先调用onRestart->onStart->onResume。注意:再次启动时不会再调用onCreate方法,只有调用onDestory方法后才会再次调用onCreate。

通常在项目中,如果需要在Activity每次被启动时(包括调用onRestart方法返回)都需要重新加载新数据,可以将刷新数据的方法在onStart方法调用。

两个Activity之间跳转时,由A跳转到B,首先A会响应onPause方法,然后B依次响应onCreate、onStart、onResume,如果B覆盖了窗体,则A会响应onStop方法。如果B是透明或者是类似对话框的样式则A不会响应onStop方法。
注:可将Activity的主题设置成android:theme="@android:style/Theme.Dialog"使其变成一个窗口样式。


有关Activity横竖屏切换问题

  • 默认情况下,Activity进行横竖屏切换的时候会重新调用各个生命周期,默认是先销毁当前的activity重新创建。
  • 如果在Anroidmainfest.xml中对对应的activity设置android:configChanges="orientation|keyboardHidden|screenSize"在切换时将不会重新调用各个生命周期,只会执行onConfigurationChanged方法。

有关后台Activity被系统回收的问题

一般来说除了在栈顶的activity,其他activity都有可能在内存不足的时候被系统回收,也就是说一个activity越处于栈底,其被回收的概率就越大。
这时我们会使用Activity中的onSaveInstanceState(Bundle outState)方法来进行数据存储,将数据存储到Bundle类型的对象当中。
在onCreate(Bundle saveInstanceState)时可以先判断该Bundle对象是否为null,若不是就可以从中取出之前保存的数据。
onSaveInstanceState在Activity的生命周期为onPause -> onSaveInstanceState -> onStop


通过intent的flag安全关闭一个已打开多个activity的application

关闭一个已打开多个activity的application有很多,比如在application中专门设置一个存放每个activity对象的集合,遍历关闭;使用广播对每个在栈的activity传递关闭消息;或者使用startActivityForResult递归关闭等。
其实还有一个方法比较简单,就是给intent添加一个flag:Intent.FLAG_ACTIVITY_CLEAR_TOP,
激活一个新的activity,然后在activity中finish。
具体做法可参考:Android 关闭多个视图Intent.FLAG_ACTIVITY_CLEAR_TOP用法


同一个程序中,不同的activity存放在不同的栈中

  • 可以在Androidmainfest.xml中设置对应activity为Signalinstance,该标志可以使设置后的activity在启动时默认在另外一个单独的任务栈中创建。
  • 在activity跳转时,可以给intent添加flag:Intent.FLAG_ACTIVITY_NEW_TASK,使其可以在另外一个单独的任务栈中创建。