Android系统架构
应用层(Application)
应用程序框架(Application Framework)
Android 内核
Android库(Libraries)
Android运行时(Android Runtime)
Linux 内核(Linux Kernel)
Android应用程序类型
前台应用程序
后台应用程序
间歇式应用程序
Widget和Live Wallpaper
ApplicationManifest 使用installLoction=""(preferExternal/auto)前者安装到外部存储器,后者系统决定
ApplicationManifest 运行时配置更改:向它的manifast节点中添加android:configChanges节点
mcc和mnc :检测到SIM,并且与之关联的国家或网络的代码发生变化
locale:用户改变设备的语言设置
keyboardHidden:显示或者隐藏了键盘
keyboard:对键盘的类型进行了更改
fontScale:用户修改了首选的字体大小
uiMode:整体UI模式发生了变化(夜间模式和白天模式)白天和夜间模式:在onResume()里判断是白天还是夜间,然后更改背景和字体颜色
orientation:屏幕在纵向和横向之间进行了旋转
screenLayout:屏幕布局发生变化。如果激活了另一个屏幕,就会出现这种情况
处理:在Activity中重写onConfigurationChanged(Configuration newConfg){
if(newConfig.xxxx=xxxx){
处理代码
}
}
尺寸
px(屏幕像素)
in(物理英寸)
pt(物理点)
mm(物理毫米)
dp(非密度制约的像素)
sp(缩放比例无关的像素)(字体)
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
动画 三种(补间动画,逐帧动画,属性动画 )
属性动画 能够让对象的属性动起来
补间动画
(res/anim)
alpha(淡入淡出)
scale(缩放)
translate(移动)
rotate(旋转)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
//需设置相应的属性
<alpha/>
<scale/>
<translate/>
<rotate/>
</set>
逐帧动画 可以用来创建Drawable的序列
<animation-list xml:xxx>
<item android:drawable="@drawable/xxxx" android:duration="500">
<item android:drawable="@drawable/xxxx" android:duration="500">
<item android:drawable="@drawable/xxxx" android:duration="500">
</animation-list>
也可以代码里写动画,new 出对象,如
new ScaleAnimation(0.0f, 1.4f, 0.0f, 1.4f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
使用xml动画
myAnimation= AnimationUtils.loadAnimation(this,R.anim.my_action);
//使用AnimationUtils类的静态方法loadAnimation()来加载XML中的动画XML文件
view.startAnimation(myAnimation);
//动画监听
myAnimation.setAnimationListener()
布局动画
1.写动画
<layoutAnimation
xxxx
android:delay="0.5"
android:animationOrder="ramdom"
android:anmition="@anim/xxx"/>
2.使用
在布局文件中加入
android:layoutAnimation="@anim/xxxx"
3.监听
aViewGroup.setLayoutAnimationListener()
Activity的启动模式及其特点
当应用运行起来后就会开启一条线程,线程中会运行一个任务栈,当Activity实例创建后就会放入任务栈中。Activity启动模式的设置在AndroidManifest.xml文件中,通过配置Activity的属性android:launchMode=""设置。
1. Standard模式(默认)
我们平时直接创建的Activity都是这种模式的Activity,这种模式的Activity的特点是:只要你创建了Activity实例,一旦激活该Activity,则会向任务栈中加入新创建的实例,退出Activity则会在任务栈中销毁该实例。
2. SingleTop模式
这种模式会考虑当前要激活的Activity实例在任务栈中是否正处于栈顶,如果处于栈顶则无需重新创建新的实例,会重用已存在的实例,否则会在任务栈中创建新的实例。
3. SingleTask模式
如果任务栈中存在该模式的Activity实例,则把栈中该实例以上的Activity实例全部移除,调用该实例的newInstance()方法重用该Activity,使该实例处於栈顶位置,否则就重新创建一个新的Activity实例。
4. SingleInstance模式
当该模式Activity实例在任务栈中创建后,只要该实例还在任务栈中,即只要激活的是该类型的Activity,都会通过调用实例的newInstance()方法重用该Activity,此时使用的都是同一个Activity实例,它都会处于任务栈的栈顶。此模式一般用于加载较慢的,比较耗性能且不需要每次都重新创建的Activity。
Activity
状态
活动状态
暂停状态(失去焦点)
停止状态(不可见)
非活动状态(终止)
生命周期
onCreate()->onRestoreInstanceState->onStart()->onResume()->onSaveInstanceState()->onPause()->onStop()->onDestroy()
onStop()->onRestart()->onStart()
onCreat()初始化Activity,填充用户界面,数据绑定到控件,启动Service和定时器
onDestroy()清除所有资源
onStop()用来暂停或停止动画、线程、传感器、GPS、定时器、Service或其它专门用来更新用户界面的进程
onStart()/onStop()注册和注销Broadcast Receiver
启动Activity,得到返回结果
startActivityForResult(intent);
onActivityResult(xx ,xxx,xx)请求码,结果码,intent
Boardcast Receiver
有序
无序
onReceive方法必须在10秒内完成,如果没有完成,则抛“Application No Response”当广播接收者onReceive方法需要执行很长时间时,最好将此耗时工作通过Intent发送给Service,由Service完成,并且不能使用子线程解决,因为BroadcastReceiver是接收到广播后才创建的,并且生命周期很短,因此子线程可能在没有执行完就已经被杀死了。
abortBroadcast(); 终止广播
Boardcast Receiver 10s内没有关闭将会出现ANR(Application Not Responding)
本地Boardcast Intent
ACTION_BOOT_COMPLETED 系统完成启动序列触发,需权限RECEIVE_BOOT_COMPLETED
ACTION_CAMERA_BUTTON 单击拍照键的时候触发
ACTION_DATE_CHANGED、ACTION_TIME_CHANGED 日期和时间手动被修改
ACTION_MEDIA_EJECT 弹出外部存储设备触发
ACTION_MEDIA_MOUNTED、ACTION_MEDIA_UNMOUNTED新的外部设备存储介质被成功的添加或移除触发
ACTION_NEW_OUTGOING_CALL 向外拨号 权限:PROCESS_OUTGOING_CALL
ACTION_SCREEN_OFF、ACTION_SCREEN_ON屏幕关闭或者打开触发
ACTION_TIMEZONE_CHANGED时区变化触发
监听电量变化
/*** 注册广播 ***/
IntentFilter intentFilter=new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batty=context.registerReceiver(null,intentFilter);
/*** Receiver接受广播后处理 ***/
int currLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); //当前电量
int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); //总电量
网络状态
android.net.conn.CONNECTIVITY_CHANGE
Context.CONNECTIVITY_SERVICE
android.permission.ACCESS_NETWORK_STATE
注册
静态注册
<receiver android:name=".BatteryChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
动态注册
MyReceiver receiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.MY_BROADCAST");
registerReceiver(receiver, filter);
unregisterReceiver(receiver);//取消注册
Content Provider
ContentResolver cr=getContentResolver();
(1)android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。
(2)只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。
(3)ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。
(4)开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。
(5)ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。
Service
(1)service用于在后台完成用户指定的操作。service分为两种:
(a)started(启动):当应用程序组件(如activity)调用startService()方法启动服务时,服务处于started状态。
(b)bound(绑定):当应用程序组件调用bindService()方法绑定到服务时,服务处于bound状态。
(2)startService()与bindService()区别:
(a)started service(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
(b)使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
(3)开发人员需要在应用程序配置文件中声明全部的service,使用<service></service>标签。
(4)Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。Service组件需要继承Service基类。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。
Intent 一种消息传递机制
用途:
使用类名显示启动一个特定的Service或Activity
特定的数据执行动作
广播某个时间已经发生//sendBroadcast(intent)
PendingIntent
Intent intent1 = new Intent(xx,xxxx);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent1, 0);
布局
FrameLayout 帧布局 (堆积)
LinearLayout 线性布局 orientation="xx"指明排列方式
RelativeLayout 相对布局
TableLayout表格布局
AbsoluteLayout,绝对位置布局
Fragment
生命周期
onAttach()获取对父Activity的引用
onCreate()初始创建
onCreateView()创建自己的界面
onActivityCreate()一旦父Activity和Fragment的UI已被创建,则调用该方法。完成Fragment的初始化,尤其是父Activity或者Fragment的View被完全填充后才能做的事情
onStart()
onResume()
onPause()
onStop()
onDestroyView()Fragment的View被分离时调用该方法,清楚相关资源的View
onDestroy()生命周期结束,清楚所有资源,结束线程和关闭数据库连接
onDetach()父Activity和Fragment分离
使用:
FragmentManager mFragmentManager = this.getFragmentManager();
FragmentTransaction fragmentTransaction = mFragmentManager
.beginTransaction();
fragmentTransaction.setCustomAnimations(
R.animator.fragment_slide_left_enter,
R.animator.fragment_slide_right_exit);//设置动画
fragmentTransaction.add(R.id.container, mTextFragmentOne);//添加
fragmentTransaction.remove(fragment);//删除
fragmentTransaction.replace(R.id.container,new MyFragment());
fragmentTransaction.commit();
Adapter 用来把数据绑定到扩展了AdapterView类的视图组,Adapter负责创建代表所绑定父视图中的底层数据的子视图。
Download Manager 作为一个Service来优化长时间下载操作的处理。
DownloadManager dg=(DowanloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request=new Request(uri);
long reference=dg.enqueue(request);
下载完后会发送ACTION_DOWNLOAD_COMPLETE广播,这个广播包含一个EXTRA_DOWNLOAD_ID的extra.(使用intent可取出)
默认情况DownloadManager管理的每一个下载显示一个持续的Notification,(也可自定义)
Shared Preference
创建键-值对的命名映射
//写
SharedPreference sp=getSharedPreferences("文件名","文件权限");
SharedPreference.Editor editor=sp.edit();
editor.putxxxx(xxx,xxx);
提交
editor.apply();异步
editor.commit();具有返回值
//读
sp.getxxxx(xxxx);
Dialog
//1
Dialog dialog=new Dialog(context);
dialog.setTitle(xxx);
dialog.setContentView(xxxxx);
View v=dialog.findViewById(xxxxx);
dialog.show();
dialog.dismiss();
//2
AlertDialog.Builder ad=new AlertDialog.Builder(context);
ad.setTitle(xx);
ad.setMessage(xxx);
ad.setPositiveButton(按钮内容,按钮监听);
ad.setNegativeButton(xxxx,xxx);
Dialog dialog=ad.create();
dialog.show();
ad.setCancelable(false);//设置对话框不可以被取消
Toast
toast.setGravity(xxxxxx);
toast.setView(xxxx);
Notification
显示状态栏图标
灯光/LED闪烁
手机震动
发出声音
在通知托盘中显示额外的信息
在通知托盘中使用交互式操作来广播intent
Notification Manager 是用来处理Notification的系统Service
NotificationManager nm=getSystemService(Context.NOTIFICATION_SERVICE);
创建
Notification nf=new Notification(图标,显示文本,System.currentTimeMills());//展开的状态栏按时间顺序排序通知
Notification.DEFAULT_LIGHTS//闪灯
Notification.DEFAULT_SOUND//声音
Notification.DEFAULT_VIBRATE//振动 android.permission.VIBRATE
notification.defaults=Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE;
//发出声音
notification.sound=miuseuri;
//发出
notificationManager.notify(NOTIFICATION_REF,notification);
//取消
notificationManager.cancle(NOTIFICATION_REF);
Notification.Builder 简化配置Notification的标志、选项、内容和布局过程
自定义
notifition.contentView=new RemoteViews(this.getPackageName(),R.layout.xxx);
notification.contentIntent=pendingIntent;
//设置contentView必须设置contentIntent,否则触发Notification时会抛异常 notifition.contentView.setImageViewResouce(viewid,drawable);
notifition.contentView.setOnClickPendingIntent(viewid,pendingIntent);
配置持续或者连续
builder.setOngoing(true);或者notification.flags=Notification.FLAG_ONGOING_EVENT //前台服务必须具有持续的Notification
notification.flags=Notification.FLAG_INSISTENT
//连续,重复
内存溢出和内存泄露
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
Android应用内存泄漏的的原因有以下几个:
1查询数据库后没有关闭游标cursor
2 构造Adapter时,没有使用 convertView 重用
3 Bitmap对象不在使用时调用recycle()释放内存
4 对象被生命周期长的对象引用,如activity被静态集合引用导致activity不能释放
内存溢出
加载的图片太多或图片过大
// 解决加载图片 内存溢出的问题
// Options 只保存图片尺寸大小,不保存图片到内存
BitmapFactory.Options opts = new BitmapFactory.Options();
// 缩放的比例,缩放是很难按准备的比例进行缩放的,其值表明缩放的倍数,SDK中建议其值是2的指数值,值越大会导致图片不清晰
opts.inSampleSize = 4;
Bitmap bmp = null;
bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);
// 回收
bmp.recycle();
ANR
1.在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)
2.BroadcastReceiver在10秒内没有执行完毕
1、运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)
2、应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver,关于原因后续会有介绍,此处不是本文重点)
3、避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
Android 消息机制
1. Message
消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。
2. Message Queue
消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
3. Handler
Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
4. Looper
循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Message Queue里面的Message,并交付给相应的Handler进行处理
5. 线程
UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。
运行机理:
每个线程都可以并仅可以拥有一个Looper实例,消息队列MessageQueue在Looper的构造函数中被创建并且作为成员变量被保存,也就是说MessageQueue相对于线程也是唯一的。Android应用在启动的时候会默认会为主线程创建一个Looper实例,并借助相关的Handler和Looper里面的MessageQueue完成对Activities、Services、Broadcase Receivers等的管理。而在子线程中,Looper需要通过显式调用Looper. Prepare()方法进行创建。Prepare方法通过ThreadLocal来保证Looper在线程内的唯一性,如果Looper在线程内已经被创建并且尝试再度创建"Only one Looper may be created per thread"异常将被抛出。
Handler在创建的时候可以指定Looper,这样通过Handler的sendMessage()方法发送出去的消息就会添加到指定Looper里面的MessageQueue里面去。在不指定Looper的情况下,Handler绑定的是创建它的线程的Looper。如果这个线程的Looper不存在,程序将抛出"Can't create handler inside thread that has not called Looper.prepare()"。
整个消息处理的大概流程是:1. 包装Message对象(指定Handler、回调函数和携带数据等);2. 通过Handler的sendMessage()等类似方法将Message发送出去;3. 在Handler的处理方法里面将Message添加到Handler绑定的Looper的MessageQueue;4. Looper的loop()方法通过循环不断从MessageQueue里面提取Message进行处理,并移除处理完毕的Message;5. 通过调用Message绑定的Handler对象的dispatchMessage()方法完成对消息的处理。
在dispatchMessage()方法里,如何处理Message则由用户指定,三个判断,优先级从高到低:1. Message里面的Callback,一个实现了Runnable接口的对象,其中run函数做处理工作;2. Handler里面mCallback指向的一个实现了Callback接口的对象,由其handleMessage进行处理;3. 处理消息Handler对象对应的类继承并实现了其中handleMessage函数,通过这个实现的handleMessage函数处理消息。
ListView 优化
1.复用View
2.使用viewholder 并且最好是静态内部类。静态内部类,不持有外部类的引用,避免内存泄露…
3.listview高度设置成match-parent (当我们固定listview的高度时(fill_parent或直接固定高度),那么listview很容易就能计算出容器内可以显示多少行。但如果我们使用了“wrap_content”,只有在屏幕内控件完全加载后才知道到底能显示多少行数据时,ListView自身便会做一些尝试性计算)
4.涉及图片等的时候使用异步加载,异步加载的时候引入线程池和线程队列(如果每当一个请求到达就创建一个新线程,开销是相当大的)
5.listview分页加载(加载更多)
6.对图片进行内存优化(当程序加载图片的时候,图片的尺寸可能很大,但是在手机上显示的时候,实际在程序中显示的时候,imageview会将大图缩小显示,其实图片中很多细节不会显示出来。但是当我们使用BitmapFactory解析图片的时候,BitmapFactory会将完整的图片解析出来,按照图片的分辨率来创建内存,转换成Bitmap,会占用非常大的内存)
InputStream is = this.getResources().openRawResource(R.drawable.pic1);
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 10; //width,hight设为原来的十分一
Bitmap btp =BitmapFactory.decodeStream(is,null,options);
if(!btp.isRecycle() ){
btp.recycle() //回收图片所占的内存
system.gc() //提醒系统及时回收
}
/**
* 以最省内存的方式读取本地资源的图片
* @param context
* @param resId
* @return
*/
public static Bitmap readBitMap(Context context, int resId){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
//获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is,null,opt);
}
7.图片异步加载引起的错位问题的解决
因为listview的复用,导致在快速移动的时候,大量调用getview方法,因为复用,导致每一个imageview都可以同时有多个不同网址的异步任务进行加载,这样导致了图片的多次错乱,需要给每一次加载view时的imageview设置tag,这个tag就是网址,在异步任务设置图片的时候,要检查一下,是否当前网址和imageview的tag一致,一致才设置图片。
因为采用了复用,当listview滚动的时候,实际上显示的是之前加载过的item,之前的item中imageview的内容,就可能是之前其他条目加载的图片信息,这个时候,操作先显示其他条目的图片,之后开启了网络加载后,才会刷新为自身正确的数据。解决方式:在每次getview的时候,都将imageview显示的内容设置为“加载中”图片。覆盖原有旧的数据。
8.图片的加载引入三级缓存机制
9.滑动不加载图片停下加载图片
list.setOnScrollListener
getView中判断是否加载图片,停止时加载,滑动事时改变判断条件
10.RecyclerView替代listview的原因
RecyclerView封装了viewholder的回收复用。
RecyclerView使用布局管理器管理子view的位置(目前尚只提供了LinearLayoutManager),也就是说你再不用拘泥于ListView的线性展示方式(即竖列的展示方式),如果之后提供其他custom LayoutManager的支持,你能够使用复杂的布局来展示一个动态组件。
自带了ItemAnimation,可以设置加载和移除时的动画,方便做出各种动态浏览的效果。
Android平台进行数据存储的五大方式
1.SharedPreferences XML,键值对的形式,MAP
2.内部存储空间
3.外部存储空间
4.SQLite Database数据库
5.Internet网络
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
简单的:ExecuteService pool = Executors.newFixedThreadPool(poolSize); 创建一个可重用固定线程数的线程池
定制高强度使用下面这种
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
//executor.execute(myTask1);
//.....
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
自定义View 完全自定义 组合控件 继承控件
LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的
在onLayout()过程结束后,我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了。说到这里,我相信很多朋友长久以来都会有一个疑问,getWidth()方法和getMeasureWidth()方法到底有什么区别呢?它们的值好像永远都是相同的。其实它们的值之所以会相同基本都是因为布局设计者的编码习惯非常好,实际上它们之间的差别还是挺大的。
首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
onMeasure()
measure是测量的意思,那么onMeasure()方法顾名思义就是用于测量视图的大小的
1. EXACTLY表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。
onLayout()
measure过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。正如其名字所描述的一样,这个方法是用于给视图进行布局的,也就是确定视图的位置。
layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。
onDraw()
measure和layout的过程都结束后,接下来就进入到draw的过程了。同样,根据名字你就能够判断出,在这里才真正地开始对视图进行绘制。
视图状态
视图状态的种类非常多,一共有十几种类型,不过多数情况下我们只会使用到其中的几种
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表示未按下。通常情况下这个状态都是由系统自动赋值的,但开发者也可以自己调用这个方法来进行改变。
视图重绘
调用视图的setVisibility()、setEnabled()、setSelected()等方法时都会导致视图重绘,而如果我们想要手动地强制让视图进行重绘,可以调用invalidate()方法来实现
自定义属性
1./res/values目录下新建一个名为 attrs.xml的文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NewMyElement">
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" />
<attr name="textValue" format="string" />
</declare-styleable>
</resources>
2.申明
xmlns:my="http://schemas.android.com/apk/res/com.ixgsoft.space"
3.使用
my:textValue="草了1"
4.代码中获得
public NewMyElement(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
init()方法中
String textValue = t.getString(R.styleable.NewMyElement_textValue);
事件处理
重写事件方法及处理
socket通信
桌面悬浮窗
http://blog.csdn.net/guolin_blog/article/details/16919859
WindowManager windowManager = getWindowManager(context);
WindowManager桌面管理类
图片缓冲 内存 本地 网络 http://blog.csdn.net/skytea_/article/details/50068399
1.在sdcard上开辟一定的空间,需要先判断sdcard上剩余空间是否足够,如果足够的话就可以开辟一些空间,比如10M
2.当需要获取图片时,就先从sdcard上的目录中去找,如果找到的话,使用该图片,并更新图片最后被使用的时间。如果找不到,通过URL去download
3.去服务器端下载图片,如果下载成功了,放入到sdcard上,并使用,如果失败了,应该有重试机制。比如3次。
4.下载成功后保存到sdcard上,需要先判断10M空间是否已经用完,如果没有用完就保存,如果空间不足就根据LRU规则删除一些最近没有被用户的资源。
应用层(Application)
应用程序框架(Application Framework)
Android 内核
Android库(Libraries)
Android运行时(Android Runtime)
Linux 内核(Linux Kernel)
Android应用程序类型
前台应用程序
后台应用程序
间歇式应用程序
Widget和Live Wallpaper
ApplicationManifest 使用installLoction=""(preferExternal/auto)前者安装到外部存储器,后者系统决定
ApplicationManifest 运行时配置更改:向它的manifast节点中添加android:configChanges节点
mcc和mnc :检测到SIM,并且与之关联的国家或网络的代码发生变化
locale:用户改变设备的语言设置
keyboardHidden:显示或者隐藏了键盘
keyboard:对键盘的类型进行了更改
fontScale:用户修改了首选的字体大小
uiMode:整体UI模式发生了变化(夜间模式和白天模式)白天和夜间模式:在onResume()里判断是白天还是夜间,然后更改背景和字体颜色
orientation:屏幕在纵向和横向之间进行了旋转
screenLayout:屏幕布局发生变化。如果激活了另一个屏幕,就会出现这种情况
处理:在Activity中重写onConfigurationChanged(Configuration newConfg){
if(newConfig.xxxx=xxxx){
处理代码
}
}
尺寸
px(屏幕像素)
in(物理英寸)
pt(物理点)
mm(物理毫米)
dp(非密度制约的像素)
sp(缩放比例无关的像素)(字体)
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
动画 三种(补间动画,逐帧动画,属性动画 )
属性动画 能够让对象的属性动起来
补间动画
(res/anim)
alpha(淡入淡出)
scale(缩放)
translate(移动)
rotate(旋转)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
//需设置相应的属性
<alpha/>
<scale/>
<translate/>
<rotate/>
</set>
逐帧动画 可以用来创建Drawable的序列
<animation-list xml:xxx>
<item android:drawable="@drawable/xxxx" android:duration="500">
<item android:drawable="@drawable/xxxx" android:duration="500">
<item android:drawable="@drawable/xxxx" android:duration="500">
</animation-list>
也可以代码里写动画,new 出对象,如
new ScaleAnimation(0.0f, 1.4f, 0.0f, 1.4f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
使用xml动画
myAnimation= AnimationUtils.loadAnimation(this,R.anim.my_action);
//使用AnimationUtils类的静态方法loadAnimation()来加载XML中的动画XML文件
view.startAnimation(myAnimation);
//动画监听
myAnimation.setAnimationListener()
布局动画
1.写动画
<layoutAnimation
xxxx
android:delay="0.5"
android:animationOrder="ramdom"
android:anmition="@anim/xxx"/>
2.使用
在布局文件中加入
android:layoutAnimation="@anim/xxxx"
3.监听
aViewGroup.setLayoutAnimationListener()
Activity的启动模式及其特点
当应用运行起来后就会开启一条线程,线程中会运行一个任务栈,当Activity实例创建后就会放入任务栈中。Activity启动模式的设置在AndroidManifest.xml文件中,通过配置Activity的属性android:launchMode=""设置。
1. Standard模式(默认)
我们平时直接创建的Activity都是这种模式的Activity,这种模式的Activity的特点是:只要你创建了Activity实例,一旦激活该Activity,则会向任务栈中加入新创建的实例,退出Activity则会在任务栈中销毁该实例。
2. SingleTop模式
这种模式会考虑当前要激活的Activity实例在任务栈中是否正处于栈顶,如果处于栈顶则无需重新创建新的实例,会重用已存在的实例,否则会在任务栈中创建新的实例。
3. SingleTask模式
如果任务栈中存在该模式的Activity实例,则把栈中该实例以上的Activity实例全部移除,调用该实例的newInstance()方法重用该Activity,使该实例处於栈顶位置,否则就重新创建一个新的Activity实例。
4. SingleInstance模式
当该模式Activity实例在任务栈中创建后,只要该实例还在任务栈中,即只要激活的是该类型的Activity,都会通过调用实例的newInstance()方法重用该Activity,此时使用的都是同一个Activity实例,它都会处于任务栈的栈顶。此模式一般用于加载较慢的,比较耗性能且不需要每次都重新创建的Activity。
Activity
状态
活动状态
暂停状态(失去焦点)
停止状态(不可见)
非活动状态(终止)
生命周期
onCreate()->onRestoreInstanceState->onStart()->onResume()->onSaveInstanceState()->onPause()->onStop()->onDestroy()
onStop()->onRestart()->onStart()
onCreat()初始化Activity,填充用户界面,数据绑定到控件,启动Service和定时器
onDestroy()清除所有资源
onStop()用来暂停或停止动画、线程、传感器、GPS、定时器、Service或其它专门用来更新用户界面的进程
onStart()/onStop()注册和注销Broadcast Receiver
启动Activity,得到返回结果
startActivityForResult(intent);
onActivityResult(xx ,xxx,xx)请求码,结果码,intent
Boardcast Receiver
有序
无序
onReceive方法必须在10秒内完成,如果没有完成,则抛“Application No Response”当广播接收者onReceive方法需要执行很长时间时,最好将此耗时工作通过Intent发送给Service,由Service完成,并且不能使用子线程解决,因为BroadcastReceiver是接收到广播后才创建的,并且生命周期很短,因此子线程可能在没有执行完就已经被杀死了。
abortBroadcast(); 终止广播
Boardcast Receiver 10s内没有关闭将会出现ANR(Application Not Responding)
本地Boardcast Intent
ACTION_BOOT_COMPLETED 系统完成启动序列触发,需权限RECEIVE_BOOT_COMPLETED
ACTION_CAMERA_BUTTON 单击拍照键的时候触发
ACTION_DATE_CHANGED、ACTION_TIME_CHANGED 日期和时间手动被修改
ACTION_MEDIA_EJECT 弹出外部存储设备触发
ACTION_MEDIA_MOUNTED、ACTION_MEDIA_UNMOUNTED新的外部设备存储介质被成功的添加或移除触发
ACTION_NEW_OUTGOING_CALL 向外拨号 权限:PROCESS_OUTGOING_CALL
ACTION_SCREEN_OFF、ACTION_SCREEN_ON屏幕关闭或者打开触发
ACTION_TIMEZONE_CHANGED时区变化触发
监听电量变化
/*** 注册广播 ***/
IntentFilter intentFilter=new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batty=context.registerReceiver(null,intentFilter);
/*** Receiver接受广播后处理 ***/
int currLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); //当前电量
int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); //总电量
网络状态
android.net.conn.CONNECTIVITY_CHANGE
Context.CONNECTIVITY_SERVICE
android.permission.ACCESS_NETWORK_STATE
注册
静态注册
<receiver android:name=".BatteryChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
动态注册
MyReceiver receiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.MY_BROADCAST");
registerReceiver(receiver, filter);
unregisterReceiver(receiver);//取消注册
Content Provider
ContentResolver cr=getContentResolver();
(1)android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。
(2)只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。
(3)ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。
(4)开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。
(5)ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。
Service
(1)service用于在后台完成用户指定的操作。service分为两种:
(a)started(启动):当应用程序组件(如activity)调用startService()方法启动服务时,服务处于started状态。
(b)bound(绑定):当应用程序组件调用bindService()方法绑定到服务时,服务处于bound状态。
(2)startService()与bindService()区别:
(a)started service(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
(b)使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
(3)开发人员需要在应用程序配置文件中声明全部的service,使用<service></service>标签。
(4)Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。Service组件需要继承Service基类。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。
Intent 一种消息传递机制
用途:
使用类名显示启动一个特定的Service或Activity
特定的数据执行动作
广播某个时间已经发生//sendBroadcast(intent)
PendingIntent
Intent intent1 = new Intent(xx,xxxx);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent1, 0);
布局
FrameLayout 帧布局 (堆积)
LinearLayout 线性布局 orientation="xx"指明排列方式
RelativeLayout 相对布局
TableLayout表格布局
AbsoluteLayout,绝对位置布局
Fragment
生命周期
onAttach()获取对父Activity的引用
onCreate()初始创建
onCreateView()创建自己的界面
onActivityCreate()一旦父Activity和Fragment的UI已被创建,则调用该方法。完成Fragment的初始化,尤其是父Activity或者Fragment的View被完全填充后才能做的事情
onStart()
onResume()
onPause()
onStop()
onDestroyView()Fragment的View被分离时调用该方法,清楚相关资源的View
onDestroy()生命周期结束,清楚所有资源,结束线程和关闭数据库连接
onDetach()父Activity和Fragment分离
使用:
FragmentManager mFragmentManager = this.getFragmentManager();
FragmentTransaction fragmentTransaction = mFragmentManager
.beginTransaction();
fragmentTransaction.setCustomAnimations(
R.animator.fragment_slide_left_enter,
R.animator.fragment_slide_right_exit);//设置动画
fragmentTransaction.add(R.id.container, mTextFragmentOne);//添加
fragmentTransaction.remove(fragment);//删除
fragmentTransaction.replace(R.id.container,new MyFragment());
fragmentTransaction.commit();
Adapter 用来把数据绑定到扩展了AdapterView类的视图组,Adapter负责创建代表所绑定父视图中的底层数据的子视图。
Download Manager 作为一个Service来优化长时间下载操作的处理。
DownloadManager dg=(DowanloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request=new Request(uri);
long reference=dg.enqueue(request);
下载完后会发送ACTION_DOWNLOAD_COMPLETE广播,这个广播包含一个EXTRA_DOWNLOAD_ID的extra.(使用intent可取出)
默认情况DownloadManager管理的每一个下载显示一个持续的Notification,(也可自定义)
Shared Preference
创建键-值对的命名映射
//写
SharedPreference sp=getSharedPreferences("文件名","文件权限");
SharedPreference.Editor editor=sp.edit();
editor.putxxxx(xxx,xxx);
提交
editor.apply();异步
editor.commit();具有返回值
//读
sp.getxxxx(xxxx);
Dialog
//1
Dialog dialog=new Dialog(context);
dialog.setTitle(xxx);
dialog.setContentView(xxxxx);
View v=dialog.findViewById(xxxxx);
dialog.show();
dialog.dismiss();
//2
AlertDialog.Builder ad=new AlertDialog.Builder(context);
ad.setTitle(xx);
ad.setMessage(xxx);
ad.setPositiveButton(按钮内容,按钮监听);
ad.setNegativeButton(xxxx,xxx);
Dialog dialog=ad.create();
dialog.show();
ad.setCancelable(false);//设置对话框不可以被取消
Toast
toast.setGravity(xxxxxx);
toast.setView(xxxx);
Notification
显示状态栏图标
灯光/LED闪烁
手机震动
发出声音
在通知托盘中显示额外的信息
在通知托盘中使用交互式操作来广播intent
Notification Manager 是用来处理Notification的系统Service
NotificationManager nm=getSystemService(Context.NOTIFICATION_SERVICE);
创建
Notification nf=new Notification(图标,显示文本,System.currentTimeMills());//展开的状态栏按时间顺序排序通知
Notification.DEFAULT_LIGHTS//闪灯
Notification.DEFAULT_SOUND//声音
Notification.DEFAULT_VIBRATE//振动 android.permission.VIBRATE
notification.defaults=Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE;
//发出声音
notification.sound=miuseuri;
//发出
notificationManager.notify(NOTIFICATION_REF,notification);
//取消
notificationManager.cancle(NOTIFICATION_REF);
Notification.Builder 简化配置Notification的标志、选项、内容和布局过程
自定义
notifition.contentView=new RemoteViews(this.getPackageName(),R.layout.xxx);
notification.contentIntent=pendingIntent;
//设置contentView必须设置contentIntent,否则触发Notification时会抛异常 notifition.contentView.setImageViewResouce(viewid,drawable);
notifition.contentView.setOnClickPendingIntent(viewid,pendingIntent);
配置持续或者连续
builder.setOngoing(true);或者notification.flags=Notification.FLAG_ONGOING_EVENT //前台服务必须具有持续的Notification
notification.flags=Notification.FLAG_INSISTENT
//连续,重复
内存溢出和内存泄露
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
Android应用内存泄漏的的原因有以下几个:
1查询数据库后没有关闭游标cursor
2 构造Adapter时,没有使用 convertView 重用
3 Bitmap对象不在使用时调用recycle()释放内存
4 对象被生命周期长的对象引用,如activity被静态集合引用导致activity不能释放
内存溢出
加载的图片太多或图片过大
// 解决加载图片 内存溢出的问题
// Options 只保存图片尺寸大小,不保存图片到内存
BitmapFactory.Options opts = new BitmapFactory.Options();
// 缩放的比例,缩放是很难按准备的比例进行缩放的,其值表明缩放的倍数,SDK中建议其值是2的指数值,值越大会导致图片不清晰
opts.inSampleSize = 4;
Bitmap bmp = null;
bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);
// 回收
bmp.recycle();
ANR
1.在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)
2.BroadcastReceiver在10秒内没有执行完毕
1、运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)
2、应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver,关于原因后续会有介绍,此处不是本文重点)
3、避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
Android 消息机制
1. Message
消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。
2. Message Queue
消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
3. Handler
Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
4. Looper
循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Message Queue里面的Message,并交付给相应的Handler进行处理
5. 线程
UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。
运行机理:
每个线程都可以并仅可以拥有一个Looper实例,消息队列MessageQueue在Looper的构造函数中被创建并且作为成员变量被保存,也就是说MessageQueue相对于线程也是唯一的。Android应用在启动的时候会默认会为主线程创建一个Looper实例,并借助相关的Handler和Looper里面的MessageQueue完成对Activities、Services、Broadcase Receivers等的管理。而在子线程中,Looper需要通过显式调用Looper. Prepare()方法进行创建。Prepare方法通过ThreadLocal来保证Looper在线程内的唯一性,如果Looper在线程内已经被创建并且尝试再度创建"Only one Looper may be created per thread"异常将被抛出。
Handler在创建的时候可以指定Looper,这样通过Handler的sendMessage()方法发送出去的消息就会添加到指定Looper里面的MessageQueue里面去。在不指定Looper的情况下,Handler绑定的是创建它的线程的Looper。如果这个线程的Looper不存在,程序将抛出"Can't create handler inside thread that has not called Looper.prepare()"。
整个消息处理的大概流程是:1. 包装Message对象(指定Handler、回调函数和携带数据等);2. 通过Handler的sendMessage()等类似方法将Message发送出去;3. 在Handler的处理方法里面将Message添加到Handler绑定的Looper的MessageQueue;4. Looper的loop()方法通过循环不断从MessageQueue里面提取Message进行处理,并移除处理完毕的Message;5. 通过调用Message绑定的Handler对象的dispatchMessage()方法完成对消息的处理。
在dispatchMessage()方法里,如何处理Message则由用户指定,三个判断,优先级从高到低:1. Message里面的Callback,一个实现了Runnable接口的对象,其中run函数做处理工作;2. Handler里面mCallback指向的一个实现了Callback接口的对象,由其handleMessage进行处理;3. 处理消息Handler对象对应的类继承并实现了其中handleMessage函数,通过这个实现的handleMessage函数处理消息。
ListView 优化
1.复用View
2.使用viewholder 并且最好是静态内部类。静态内部类,不持有外部类的引用,避免内存泄露…
3.listview高度设置成match-parent (当我们固定listview的高度时(fill_parent或直接固定高度),那么listview很容易就能计算出容器内可以显示多少行。但如果我们使用了“wrap_content”,只有在屏幕内控件完全加载后才知道到底能显示多少行数据时,ListView自身便会做一些尝试性计算)
4.涉及图片等的时候使用异步加载,异步加载的时候引入线程池和线程队列(如果每当一个请求到达就创建一个新线程,开销是相当大的)
5.listview分页加载(加载更多)
6.对图片进行内存优化(当程序加载图片的时候,图片的尺寸可能很大,但是在手机上显示的时候,实际在程序中显示的时候,imageview会将大图缩小显示,其实图片中很多细节不会显示出来。但是当我们使用BitmapFactory解析图片的时候,BitmapFactory会将完整的图片解析出来,按照图片的分辨率来创建内存,转换成Bitmap,会占用非常大的内存)
InputStream is = this.getResources().openRawResource(R.drawable.pic1);
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 10; //width,hight设为原来的十分一
Bitmap btp =BitmapFactory.decodeStream(is,null,options);
if(!btp.isRecycle() ){
btp.recycle() //回收图片所占的内存
system.gc() //提醒系统及时回收
}
/**
* 以最省内存的方式读取本地资源的图片
* @param context
* @param resId
* @return
*/
public static Bitmap readBitMap(Context context, int resId){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
//获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is,null,opt);
}
7.图片异步加载引起的错位问题的解决
因为listview的复用,导致在快速移动的时候,大量调用getview方法,因为复用,导致每一个imageview都可以同时有多个不同网址的异步任务进行加载,这样导致了图片的多次错乱,需要给每一次加载view时的imageview设置tag,这个tag就是网址,在异步任务设置图片的时候,要检查一下,是否当前网址和imageview的tag一致,一致才设置图片。
因为采用了复用,当listview滚动的时候,实际上显示的是之前加载过的item,之前的item中imageview的内容,就可能是之前其他条目加载的图片信息,这个时候,操作先显示其他条目的图片,之后开启了网络加载后,才会刷新为自身正确的数据。解决方式:在每次getview的时候,都将imageview显示的内容设置为“加载中”图片。覆盖原有旧的数据。
8.图片的加载引入三级缓存机制
9.滑动不加载图片停下加载图片
list.setOnScrollListener
getView中判断是否加载图片,停止时加载,滑动事时改变判断条件
10.RecyclerView替代listview的原因
RecyclerView封装了viewholder的回收复用。
RecyclerView使用布局管理器管理子view的位置(目前尚只提供了LinearLayoutManager),也就是说你再不用拘泥于ListView的线性展示方式(即竖列的展示方式),如果之后提供其他custom LayoutManager的支持,你能够使用复杂的布局来展示一个动态组件。
自带了ItemAnimation,可以设置加载和移除时的动画,方便做出各种动态浏览的效果。
Android平台进行数据存储的五大方式
1.SharedPreferences XML,键值对的形式,MAP
2.内部存储空间
3.外部存储空间
4.SQLite Database数据库
5.Internet网络
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
简单的:ExecuteService pool = Executors.newFixedThreadPool(poolSize); 创建一个可重用固定线程数的线程池
定制高强度使用下面这种
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
//executor.execute(myTask1);
//.....
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
自定义View 完全自定义 组合控件 继承控件
LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的
在onLayout()过程结束后,我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了。说到这里,我相信很多朋友长久以来都会有一个疑问,getWidth()方法和getMeasureWidth()方法到底有什么区别呢?它们的值好像永远都是相同的。其实它们的值之所以会相同基本都是因为布局设计者的编码习惯非常好,实际上它们之间的差别还是挺大的。
首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
onMeasure()
measure是测量的意思,那么onMeasure()方法顾名思义就是用于测量视图的大小的
1. EXACTLY表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。
onLayout()
measure过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。正如其名字所描述的一样,这个方法是用于给视图进行布局的,也就是确定视图的位置。
layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。
onDraw()
measure和layout的过程都结束后,接下来就进入到draw的过程了。同样,根据名字你就能够判断出,在这里才真正地开始对视图进行绘制。
视图状态
视图状态的种类非常多,一共有十几种类型,不过多数情况下我们只会使用到其中的几种
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表示未按下。通常情况下这个状态都是由系统自动赋值的,但开发者也可以自己调用这个方法来进行改变。
视图重绘
调用视图的setVisibility()、setEnabled()、setSelected()等方法时都会导致视图重绘,而如果我们想要手动地强制让视图进行重绘,可以调用invalidate()方法来实现
自定义属性
1./res/values目录下新建一个名为 attrs.xml的文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NewMyElement">
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" />
<attr name="textValue" format="string" />
</declare-styleable>
</resources>
2.申明
xmlns:my="http://schemas.android.com/apk/res/com.ixgsoft.space"
3.使用
my:textValue="草了1"
4.代码中获得
public NewMyElement(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
init()方法中
String textValue = t.getString(R.styleable.NewMyElement_textValue);
事件处理
重写事件方法及处理
socket通信
桌面悬浮窗
http://blog.csdn.net/guolin_blog/article/details/16919859
WindowManager windowManager = getWindowManager(context);
WindowManager桌面管理类
图片缓冲 内存 本地 网络 http://blog.csdn.net/skytea_/article/details/50068399
1.在sdcard上开辟一定的空间,需要先判断sdcard上剩余空间是否足够,如果足够的话就可以开辟一些空间,比如10M
2.当需要获取图片时,就先从sdcard上的目录中去找,如果找到的话,使用该图片,并更新图片最后被使用的时间。如果找不到,通过URL去download
3.去服务器端下载图片,如果下载成功了,放入到sdcard上,并使用,如果失败了,应该有重试机制。比如3次。
4.下载成功后保存到sdcard上,需要先判断10M空间是否已经用完,如果没有用完就保存,如果空间不足就根据LRU规则删除一些最近没有被用户的资源。