View
自定义View中在onDraw()方法中可以设置padding吗?
答案是不能,设置padding后,View的布局改变,会重新进行measure,layout流程,然后draw,从而陷入死循环,导致内存溢出或泄漏;
自定义View
Android缓存策略
Android 之 Bitmap
一、加载Bitmap
BitmapFactory类提供了四类方法用来加载Bitmap:
1、
decodeFile(...)
通过图片路径加载,同时可以选择是否设置options,不设置则采用默认options。
例子:
Bitmap bm = BitmapFactory.decodeFile(sd_path)
采用默认options
Bitmap bm = BitmapFactory.decodeFile(sd_path,options)
2、
decodeResource(...)
通过传入Resource对象和R.drawable.xxx
形式加载。
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.aaa);
默认options
3、
decodeStream(...)
通过输入流加载
Bitmap bm = BitmapFactory.decodeStream(stream)
,这是一个耗时操作,要在子线程中执行
4、
decodeByteArray(...)
从字节数组中加载。通过讲输入流inputstream转换成byte[]字节数组加载。
Bitmap bm = BitmapFactory.decodeByteArray(myByte,0,myByte.length);
** 注:**decodeFile和decodeResource间接调用decodeStream方法。
二、高效加载bitmap
如果图片过大,直接通过BitmapFactory
加载,容易出现内存溢出。这样就需要采取一定策略来加载所需的图片。主要就是通过BitmapFactory
内部的一个内部类Options
来实现。
尺寸压缩 是压缩图片的像素,一张图片所占内存的大小 图片类型*宽*高,通过改变三个值减小图片所占的内存,防止OOM,当然这种方式可能会使图片失真 。这是必然的取舍。
设置Options,主要是设置图片色彩模式,采样率来实现。
** Android图片色彩模式分类:**
**Bitmap.Config.ALPHA_8*
:**每个像素占用1byte内存。颜色信息只由透明度组成,占8位。
Bitmap.Config.ARGB_4444
:每个像素占用2byte内存。颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占4位,总共占16位。
Bitmap.Config.ARGB_8888
:每个像素占用4byte内存。颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置。
Bitmap.Config.RGB_565
:每个像素占用2byte内存。颜色信息由R(Red),G(Green),B(Blue)三部分组成,R占5位,G占6位,B占5位,总共占16位。Android默认的色彩模式为ARGB_8888,这个色彩模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。
BitmapFactory.Options
的inPreferredConfig
参数可以 指定decode到内存中,手机中所采用的编码,可选值定义在Bitmap.Config
中。缺省值是ARGB_8888。
采样率:inSampleSize的值必须大于1时才会有效果,且采样率同时作用于宽和高;当inSampleSize=1时,采样后的图片为图片的原始大小。当inSampleSize=2时,采样后的图片的宽高均为原始图片宽高的1/2,这时像素为原始图片的1/(2x2),占用内存也为原始图片的1/(2x2);inSampleSize的取值应该总为2的整数倍,否则会向下取整,取一个最接近2的整数倍,比如inSampleSize=3时,系统会取inSampleSize=2。
假设一张1024x1024,模式为ARGB_8888的图片,inSampleSize=2,原始占用内存大小是4MB,采样后的图片占用内存大小就是(1024/2) x (1024/2 )x4 = 1MB。具体的采样步骤可以参考Android 之 Bitmap
** 优点:** 效率较高,解析速度快
缺点:采样率inSampleSize的取值只能是2的次方数(例如:inSampleSize=15,实际取值为8;inSampleSize=17,实际取值为16;实际取值会往2的次方结算),因此该方法不能精确的指定图片的大小
三、Bitmap 注意事项
1、不用的bitmap即使释放
if (!bmp.isRecycled()) {
bmp.recycle(); //回收图片所占的内存
bitmap = null;
system.gc(); //提醒系统及时回收
}
2、捕获OutOfMemoryError
bitmap在实例化的过程中是很耗内存的。很容易出现OutOfMemery内存溢出的情况。而且一出现程序就会crash。所以,需要对bitmap的实例化的时做OutOfMemoryError捕获,需要注意的是OutOfMemoryError并不是异常而是错误。一般情况下java中异常是可以捕获的。而错误是不可以的,因为Error的出现一般情况下程序就会终止。OutOfMemoryError比较特殊。
Bitmap bitmap = null;
try {
// 实例化Bitmap
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
// 如果实例化失败 返回默认的Bitmap对象
return defaultBitmapMap;
}
**3、缓存通用的bitmap对象** 在加载用户头像的时候,如果用户没有上传的头像,一般会加载一个默认头像。而用户的默认头像又是一样的,所以,对于相同头像的bitmap应该做缓存处理。`如果不进行缓存,尽管看到的是同一张图片文件,但是使用BitmapFactory类的方法来实例化出来的Bitmap,是不同的Bitmap对象。缓存可以避免新建多个Bitmap对象,避免内存的浪费。`
4、图片质量压缩
上述用inSampleSize压缩是尺寸压缩,Android中还有一种压缩方式叫质量压缩。质量压缩是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,经过它压缩的图片文件大小(kb)会有改变,但是导入成bitmap后占得内存是不变的,宽高也不会改变。因为要保持像素不变,所以它就无法无限压缩,到达一个值之后就不会继续变小了。显然这个方法并不适用与缩略图,其实也不适用于想通过压缩图片减少内存的适用,仅仅适用于想在保证图片质量的同时减少文件大小的情况而已
。
private void compressImage(Bitmap image, int reqSize) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 质量压缩方法,这里100表示不压缩,
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
int options = 100;
>
// 循环判断压缩后的图片是否大于reqSize,大于则继续压缩
while (baos.toByteArray().length / 1024 > reqSize) {
baos.reset();//清空baos
// 这里压缩options,把压缩后的数据放到baos中
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
options -= 10;
}
// 把压缩后的baos放到ByteArrayInputStream中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
//decode图片
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null); }
**�四、Android加载大量图片内存溢出解决方案 **
①、在加载图片的时候,尽量不要直接使用
setImageBitmap
或setImageResource
或BitmapFactory.decodeResource
来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存,可以通过BitmapFactory.decodeStream
方法,创建出一个bitmap,再将其设为ImageView的 source
②、使用BitmapFactory.Options
对图片进行压缩
③、运用Java软引用,进行图片缓存,将需要经常加载的图片放进缓存里,避免反复加载
**五、其他 **
图片的旋转,合成,圆角,缩放,裁剪,bitmap与drawable互相转换,以及保存到sd卡的相关操作参考Android 之 Bitmap
WebView
WebView的实现主要依靠WebView和WebSettings这两个类来实现。WebView提供容器,WebSetting设置WebView支持的属性。
WebView使用过程中需要注意的地方:
1、在实例化WebView的时候尽量不要使用当前Activity的引用。用代码New一个WebView而不是在XML中静态写入。有人利用LeakCanary检测过传入当前Activity引用时是否会出现内存泄露,结果是没有。接着换成Application传入,与之前传入的Activity引用进行对比发现,虽然两者都不会造成内存泄露,但是使用Application要使用Activity时所消耗的内存少20~30MB。所以,建议直接使用Application。
即WebView实例化的时候不要采用这种方式WebView webView = new WebView(�this);
应该采用这种方式WebView webView = new WebView(App.getContext());
而是采用动态加载的方式:
private WebView webView;
webView = new WebView(App.getContext());
//一定要设置WebView的LayoutParams,并且值MATCH_PARENT。否则的话就会出现有的网页无法加载的情况
webView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
binding.webviewContainer.addView(webView);
**2、WebView资源的释放**。页面销毁之前勿忘释放WebView资源。具体释放规则可以看下面这段代码
private void clearWebViewResource(){
if (webView != null){
webView.removeAllViews();
//在5.1上如果不加上这句话就会出现内存泄露。这是5.1的bug
// mComponentCallbacks导致的内存泄漏
((ViewGroup)webView.getParent()).removeView(webView);
webView.setTag(null);
webView.clearHistory();
webView.destroy();
webView = null;
}
}
基于上述写的Demo示例: [Demo](http://blog.csdn.net/loveyaozu/article/details/52933897)
Android res目录下的raw和assets
**assets **资源目录或者叫资产目录,里面存放的是无法直接访问的原生资源。与res属于同级目录。应用程序需要通过AssetsManager以二进制流的形式读取文件。应用程序编译的时候不会在R类中为assets目录下的文件创建索引。
raw在res目录下(res/raw)也是用于存放一些资源文件的。应用程序编译的时候raw目录下的资源文件会在R类中生成索引
res/raw与assets的比较
相同点:
都是用于存放资源文件的。两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。
不同点:
1、编译的时候res/raw目录下的文件会在R类中生成索引,访问的时候直接使用资源ID即R.id.filename;assets目录下的文件则不会,访问的时候需要通过AssetManager。
2、res/raw下不能再有下级目录;而assets则可以有。res/raw与assets目录下文件的读取
1、res/raw目录下文件访问方式:
InputStream is = getResources().openRawResource(R.raw.filename);
2、assets目录下文件的读取方式:
AssetManager am = getResources().getAssets();
InputStream is = am.open("filename.xxx");
或者直接这样
InputStream is = getResources().getAssets().open("filename.xxx");
Android对assets和raw目录下文件大小的限制
android 2.3之前在读取这两个资源文件夹中的文件时会有一定的限制,即单个文件大小不能超过1M ,如果读取超过1M的文件会报 "Data exceeds UNCOMPRESS_DATA_MAX (1314625 vs 1048576)" 的IOException。
Android文件路径
SD卡读写需要权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
// SD卡是否可用
boolean isSDCardAvailable = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
if (isSDCardAvailable){
//目录 /storage/emulated/0
String path = Environment.getExternalStorageDirectory().getPath();
//SD卡上面的缓存目录 /cache
String cachePath = Environment.getDownloadCacheDirectory().getPath();
}
//目录 /data/data/packageName/filesString
fileDir = getFilesDir().getPath();
//目录 /data/data/packageName/cacheString
cachePath = getCacheDir().getPath();
MVC,MVP 和 MVVM 设计模式
Sqlite数据库
1.面试题
** 注意**:数据库的增删改操作最好加上事务,根据测试发现开启事务比不开启事务的时候数据插入的效率会高很多,特别是批量插入的时候。
关于Sqlite的一些细节:
1、对于数据的批量插入,数据的修改与删除建议加上事务。有人做过测试,批量插入1000条数据的时候,使用事务和不使用事务的差别很大。使用事务的批量插入效率比不使用事务的批量插入要高的多。使用事务还有一点好处就是保证每次事务操作的原子性。出现错误自动回滚。
2、索引的使用。如果应用中查询操作量级较大,业务对要求查询要求较高的可以使用索引。
其他情况不要轻易使用。因为事物都有两面。
- ①、对于数据的增删,索引会�降低增删的效率,使用了索引会变慢,比如你想要删除字典中的一个字,那么你同时也需要删除这个字在拼音索引和部首索引中的信息。
- ②、建立索引会增加数据库的大小,比如字典中的拼音索引和部首索引实际上是会增加字典的页数,让字典变厚的。
3、大批量数据的插入或者删除的时候尽量开启新的线程。数据量过大,在插入或者查询的时候耗时就会比较大。如果在UI线程中执行容易出现ANR。
4、SQLiteDatabase的引用。在多线程中只使用一个SQLiteDatabase引用,在用SQLiteDataBase.close()的时需要注意调是否还有别的线程在使用这个实例。如果一个线程操作完成后就直接close了,别一个正在使用这个数据库的线程就会异常。
解决办法:
- ①、将SQLiteDatabase实例放在Application中,使其生命周期和App一致。
- ②、采用计数器的方式,在Application中添加一个线程安全的计数器,每次数据库操作完成后检查一下计数是否为0。为0就关闭SQLiteDatabase,不为0就不关闭。
5、数据查询时对cursor的遍历。
�遍历cursor时,我们通常的做法是这样:
private void badQueryWithLoop(SQLiteDatabase db) {
Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;
while (cursor.moveToNext()) {
long insertTime = cursor.getLong(cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME));
}
}
如果我们将获取ColumnIndex的操作提到循环之外,效果会更好一些
private void goodQueryWithLoop(SQLiteDatabase db) {
Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;
int insertTimeColumnIndex = cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME);
while (cursor.moveToNext()) {
long insertTime = cursor.getLong(insertTimeColumnIndex);
}
cursor.close();
}
**6、ContentValues容量调整** ContentValues内部采用了HashMap来存储Key-Value数据,ContentValues的初始容量是8,如果当添加的数据超过8之前,则会进行双倍扩容操作,因此建议对ContentValues填入的内容进行估量,设置合理的初始化容量,减少不必要的内部扩容操作。
事务事例:
public void insertStudent(Student student){
if (student == null){
return;
}
SQLiteDatabase database = null;
try {
database = dbHelper.getReadableDatabase();
database.beginTransaction();//开启事务
ContentValues cv = new ContentValues();
cv.put(StudentColumn.NAME,student.getName());
cv.put(StudentColumn.SEX,student.getSex());
cv.put(StudentColumn.STUDENT_NUM,student.getStudentNum());
database.insertOrThrow(TableHelpter.TB_STUDENT,null,cv);
database.setTransactionSuccessful(); //操作执行完成将事务设置成功,这一句必须要有。否则的话,在关闭事务的时候会回滚结果不提交。
}catch (Exception e){
e.printStackTrace();
} finally {
database.endTransaction();//数据库关闭前关闭事务
if (database != null){
database.close();
}
}
}
补充:数据库中事务的四大特性
1、原子性
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
2、一致性
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
3、隔离性
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
4、持久性
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
数据库并发带来的问题
由于事务具有隔离性,即事务之间互不影响。当数据库出现并发访问的时候就会带来一些列的问题。
1、脏读
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
2、不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
3、幻读
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
事务的隔离级别
现在来看看MySQL数据库为我们提供的四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
2.Sqlite详解
3.Sqlite性能优化
RecyclerView,ListView,GridView
**1.ListView **
ListView 面试相关:
1.如何加载不同类别的item?
BaseAdapter中有两个方法,一个是
public int getItemViewType(int position)
返回当前ItemView的类型,默认返回0。另一个方法是public int getViewTypeCount()
返回item view的类型个数,默认返回1。可以通过复写这两个方法,达到加载不同itemview的需求。假设,当前有两个类型的viewitem,一个是文本类型,一个是图片类型。我们就可以复写getViewTypeCount
的时候返回2,然后通过复写getItemViewType
,根据position通过getItem得到当前的object对象,然后根据这个object所对应的类型(文本,图片)返回对应类型的int值。
2.listview的优化
- 重用ConvertView(�getView在首次调用的时候,convertView是null,所以这个时候就需要创建ItemView的layout。后续调用的时候就不需要了,因为此时的convertView已经不为null了。所以,convertView可以复用。�不用每次都用inflate一下ItemView的layout布局,如果每次inflate,这样会非常消耗性能,尤其是item有成百上千个的时候。归根结底就是为了
避免重复inflate ItemView的layout布局
) - 采用View Holder模式(目的是缓存ItemView的layout中子控件,不用每次都通过findViewById的方式去加载,而是从ViewHolder的缓存中获取,速度就会快很多。)
关于上面两个原理,可以查看ListView常用知识点归纳
- 使用异步线程加载图片(一般都是直接使用图片库加载,如Glide, Picasso);
- 在adapter的getView方法中尽可能的减少逻辑判断,特别是耗时的判断;
- 避免GC(可以从LOGCAT查看有无GC的LOG);
- 在快速滑动时不要加载图片;ListView 快速滑动不加载图片
- 将ListView的scrollingCache和animateCache这两个属性设置为false(默认是true);
- 尽可能减少List Item的Layout层次(如可以使用RelativeLayout替换LinearLayout,或使用自定的View代替组合嵌套使用的Layout);
3.其他
ListView的item点击事件和item中button点击事件发送冲突
解决办法:方法一,在ItemView配置的xml文件中的根节点添加属性android:descendantFocusability="blocksDescendants"
�方法二,在要添加事件的控件上添加android:focusable="false"
**2.GridView **
** 3.RecyclerView**
** RecyclerView与ListView对比:**
1、ListView只支持线性显示规则;RecyclerView支持多种显示规则LinearLayoutManager
、GridLayoutManager
、StaggeredGridLayoutManager
。分别是线性规则,网格和瀑布流。可以通过setLayoutManager设置。所以RecyclerView的更加灵活多变。
2、ListView可以添加头和脚;RecyclerView不可以。
3、ListView的adapter需要继承重写 BaseAdapter 类,自定义 ViewHolder 和 convertView 一起完成复用优化工作。RecyclerView则继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder,设置布局管理器,控制布局效果。
4、ListView只支持纵向滚动,RecyclerView支持横向和纵向滚动
5、ListView不支持局部刷新,
notifyDataSetChanged()
是整体刷新。RecyclerView可以通过notifyItemChanged(...)
刷新单个item。
6、RecyclerView不支持
OnItemClickListener
和OnItemLongClickListener
事件。需要自己实现。
7、RecyclerView的动画效果比ListView更加丰富。
RecyclerView与ListView对比分析
RecyclerView 和 ListView 使用对比分析
ListView/RecyclerView通用adapter(实际开发中可以借鉴)
这两篇博客是对上面通用adapter的实现介绍
CoordinatorLayout布局
- CoordinatorLayout的使用
- CoordinatorLayout——小试牛刀
- CoordinatorLayout——源码分析
- CoordinatorLayout自定义Bahavior特效及其源码分析
- Android 一步一步分析CoordinatorLayout.Behavior
Android内存泄露与性能优化
内存泄露检测工具
Android性能优化方案
1.布局优化
尽量减少layout层级,减少界面绘制的工作量。
采用<include>,<merge>标签
2.自定义View的绘制
onDraw中不要创建大量的局部对象。因为onDraw方法会被频繁调用,这样就会在一瞬间产生大量的临时对象,不仅会占用过多内存还会导致系统频繁GC,降低程序执行效率。
onDraw中不要做太多耗时才操作。
3.内存优化
静态变量导致的内存泄露
示例:一个外部的静态Context变量引用了当前的Activity实例,当Activity销毁的时候无法被销毁。或者一个Activity的一个静态Context变量,引用了当前Activity,销毁之前没有释放,导致Activity无法被销毁。
解决方法:不要将Activty作为Context传给外部Context变量。如果外部需要传入Context,可以使用Application Context,因为Application Context的生命周期与APP的生命周期一致。如果Activity内部有静态Context变量持有当前Activity实例,在onDestroy的时候要将该变量持有Activity释放。单例模式导致的内存泄露
示例:单例模式内部的Context变量持有外部存入的Activity实例,在Activity销毁之前没有释放操作,导致Activity无法被销毁,内存无法回收。
解决办法:如果单例模式需要Context的时候,可以通过调用Context.getApplicationContext()方法或者Activity.getApplication()方法来传入Application对象。或者直接在Application 创建的时候,即onCreate的时候传入Application Context。属性动画导致的内存泄露
示例:属性动画中有一类无限循环的动画,如果在Activity播放了此类动画并且没有在onDestroy中去停止动画,那么动画会一直播放下去,并且这个时候Activity的View会被动画持有,而View又持有了Activity,最终导致Activity无法释放。
解决办法:在Activity的onDrstroy中调用animator.cancel()来停止动画。自定义Handler导致的内存泄露
示例:如果我们在Activity中定义一个非静态的Handler内部类,这样这个内部类就默认持有了当前Activity实例的引用。Handler常常伴随着一个执行耗时操作的异步线程(如下载多张图片),如果在完成耗时操作之前,Activity退出,异步线程持有handler的引用,handler持有Activity的引用,Activity的实例无法被销毁,从而导致内存泄漏
解决办法:用一个静态的内部类的来代替,同时Weakference的方式传入外部Activity的引用。同时在Activity的onDestroy方法里面调用mHandler.removeCallbacksAndMessages(null);移除该Handler相关的消息队列中所有消息和所有的Runnable。AsyncTask导致的内存泄露
原因和Handler内存泄露原因相似,解决办法也一样,采用静态内部类的方式。
4.其他
Bitmap对象使用完后,忘记了调用recycle()方法销毁;
Android内存泄露与性能优化相关博客
- Android开发艺术探索 第15章 Android性能优化 读书笔记
- Android 内存优化
- Android 内存泄漏总结
- Android 性能优化
- Android 性能优化
- android开发 之 优化篇
Android IPC(进程间通信)
进程间的通信方式:
1、Bundle,封装数据通过intent进行传输。优点是简单易用。缺点是只能传输bundle支持的数据类型。适用场景在四大组件之间�通信。
2、** 文件共享,各个进程通过读写同一份文件共享数据。优点是简单易用。缺点是不适合高并发的场景,并且无法做到即时通信。适用场景,不存在�并发访问的情况,对数据实时性没有太高的要求。
3、 AIDL,Android接口定义语言。优点是功能强大,支持一对多的并发通信,支持实时通信。缺点是适用相对复杂,需要处理好线程同步。适用场景,存在一对多的通信,且有RPC(远程调用)需求。
4、Messenger,基于消息的进程间通信。优点是功能简单,支持一对多串行通信。缺点,不能很好处理的高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型。适用场景,低并发一对多即时通信,无RPC需求,或者无需返回结果的RPC需求。
5、ContentProvider,内容提供者,专门用于不同应用间的数据共享。优点是在数据源访问方面功能强大,支持一对多并发的数据共享。可以通过call方法扩展其他操作。缺点,可以理解其为受约束的AIDL,主要提供数据源的CRUD操作。适用场景,一对多的进程间的数据共享。
6、Socket **,适用TCP或UDP协议进行网络通信。优点是功能强大,可以通过网络传输字节流,支持一对多并发实时通信。缺点是实现细节比较繁琐和复杂,不支持直接的RPC。适用场景,网络数据交换。
Android 多进程模式
积极作用:android系统为每个应用程序分配的内存是有限的。虽然随着硬件技术的发展,android手机内存越来越大。但是可供app使用的内存有时还会觉得有点捉襟见肘,不够用。所以我们可以通过给四大组件指定android:process属性的方式,让比较耗内存的组件运行在单独的进程中。
但是多进程模式也带来了诸多问题:
1.单例模式和静态成员完全失效。原因很简单,单例和静态成员只在进程作用域内有效。一个进程相当于一个应用程序。所以,每个进程都会有一份单例对象和静态成员。
2.同样线程同步也会失效。原因与上述相同,线程同步只在进程作用域内有效。
3.SharedPreferences的可靠性下降。虽然SharedPreferences有Context.MODE_MULTI_PROCESS
多进程模式,但是可靠性任然很低。这个模式在6.0的时候被标记为deprecated过时的。
4.Applicaion会被多次创建。原因是一个进程相当于一个应用程序,所以Application自然而然的就会被创建多次。
- Android面试题(二)——IPC机制
- Android开发艺术探索 第2章 IPC机制 读书笔记
- Android IPC - Binder 学习总结
- android IPC通信机制梳理
- android AIDL
- Android IPC机制2-AIDL的使用
- Android IPC机制
- 使用Messenger进行跨进程通信
- Android IPC机制(二)——利用Messenger实现跨进程通信
注:几种IPC的代码示例集锦
IPC几种实现方式样例
媒体相关
Android Data Binding(MVVM模式)
Data Binding 使用方法
准备
新建一个 Project,确保 Android 的 Gradle 插件版本不低于 1.5.0-alpha1:
classpath 'com.android.tools.build:gradle:1.5.0'
然后修改对应模块(Module)的 build.gradle:
android {
....
dataBinding {
enabled = true
}
}
基础
工程创建完成后,我们通过一个最简单的例子来说明 Data Binding 的基本用法。
布局文件
使用 Data Binding 之后,xml 的布局文件就不再用于单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了 layout,并且新增了一个节点 data。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data> </data>
<!--原先的根节点(Root Element)-->
<LinearLayout>
....
</LinearLayout>
</layout>
要实现 MVVM 的 ViewModel就需要把数据(Model)与 UI(View) 进行绑定,data节点的作用就像一个桥梁,搭建了 View
和 Model
之间的通路。我们先在 xml 布局文件的 data节点中声明一个 variable
,这个变量会为 UI 元素提供数据(例如 TextView
的android:text
),然后在 Java 代码中把『后台』数据与这个 variable
进行绑定。下面我们使用 Data Binding 创建一个展示用户信息的表格。
数据对象
添加一个 POJO 类 - User,非常简单,两个属性以及他们的getter
和 setter
。
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
稍后,我们会新建一个 User类型的变量,然后把它跟布局文件中声明的变量进行绑定。
定义 Variable
回到布局文件,在data
节点中声明一个 User
类型的变量 user
。
<data>
<variable name="user"type="com.liangfeizc.databindingsamples.basic.User" />
</data>
其中type
属性就是我们在 Java 文件中定义的 User
类。当然,data
节点也支持 import
,所以上面的代码可以换一种形式来写。
<data>
<import type="com.liangfeizc.databindingsamples.basic.User" />
<variable name="user" type="User" />
</data>
然后我们刚才在 build.gradle
中添加的那个件- com.android.databinding
会根据 xml
文件的名称 Generate 一个继承自 ViewDataBinding
的类。 当然,IDE 中看不到这个文件,需要手动去 build
目录下找。例如,这里 xml
的文件名叫 activity_basic.xml
,那么生成的类就是 ActivityBasicBinding
。
注意
java.lang.*
包中的类会被自动导入,可以直接使用,例如要定义一个 String类型的变量:
<variable name="firstName" type="String" />
绑定 Variable
修改 BasicActivity 的 onCreate
方法,用 DatabindingUtil.setContentView()
来替换掉 setContentView()
,然后创建一个 user
对象,通过binding.setUser(user)
与 variable
进行绑定。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityBasicBinding binding = DataBindingUtil.setContentView( this, R.layout.activity_basic);
User user = new User("fei", "Liang");
binding.setUser(user);
}
除了使用框架自动生成的 ActivityBasicBinding
,我们也可以通过如下方式自定义类名。
<data class="com.example.CustomBinding"></data>
注意ActivityBasicBinding
类是自动生成的,所有的 set
方法也是根据 variable
名称生成的。例如,我们定义了两个变量。
<data>
<variable name="firstName" type="String" />
<variable name="lastName" type="String" />
</data>
那么就会生成对应的两个 set
方法。
setFirstName(String firstName);
setLastName(String lastName);
使用 Variable
数据与 Variable 绑定之后,xml 的 UI 元素就可以直接使用了。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}" />
至此,一个简单的数据绑定就完成了,可参考完整代码
高级用法
使用类方法
首先定义一个静态方法
public class MyStringUtils {
public static String capitalize(final String word) {
if (word.length() > 1) {
return String.valueOf(word.charAt(0)).toUpperCase() + word.substring(1);
}
return word;
}
}
然后在 xml
的 data
节点中导入:
<import type="com.liangfeizc.databindingsamples.utils.MyStringUtils" />
使用方法与 Java 语法一样:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{MyStringUtils.capitalize(user.firstName)}" />
类型别名
如果我们在data
节点了导入了两个同名的类怎么办?
<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" />
<variable name="user" type="User" />
这样一来出现了两个 User
类,那user
变量要用哪一个呢?不用担心,import
还有一个 alias属性。
<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
Null Coalescing 运算符
android:text="@{user.displayName ?? user.lastName}"
就等价于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性值
通过 @{}
可以直接把 Java 中定义的属性值赋值给 xml 属性。
<TextView android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
使用资源
这个例子,官方教程有错误,可以参考Android Data Binder 的一个bug,完整代码在此
<TextView android:padding="@{large? (int)@dimen/largePadding : (int)@dimen/smallPadding}"
android:background="@android:color/black"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
Observable Binding
本来这一节的标题应该叫双向绑定,但是很遗憾,现在的 Data Binding 暂时支持单向绑定,还没有达到 Angular.js 的威力。要实现 Observable Binding
,首先得有一个implement
了接口 android.databinding.Observable
的类,为了方便,Android 原生提供了已经封装好的一个类 - BaseObservable
,并且实现了监听器的注册机制。我们可以直接继承 BaseObservable
。
public class ObservableUser extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return firstName;
}
@Bindable
public String getLastName() {
return lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
BR是编译阶段生成的一个类,功能与R.java
类似,用 @Bindable
标记过getter
方法会在 BR中生成一个 entry。通过代码可以看出,当数据发生变化时还是需要手动发出通知。 通过调用notifyPropertyChanged(BR.firstName)
可以通知系统 BR.firstName
这个 entry
的数据已经发生变化,需要更新 UI。
除此之外,还有一种更细粒度的绑定方式,可以具体到成员变量,这种方式无需继承 BaseObservable
,一个简单的 POJO就可以实现。
public class PlainUser {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
系统为我们提供了所有的 primitive type 所对应的 Observable类,例如ObservableInt
、ObservableFloat
、ObservableBoolean
等等,还有一个 ObservableField
对应着 reference type。剩下的数据绑定与前面介绍的方式一样,具体可参考ObservableActivity。
带 ID 的 View
Data Binding 有效降低了代码的冗余性,甚至完全没有必要再去获取一个View
实例,但是情况不是绝对的,万一我们真的就需要了呢?不用担心,只要给 View 定义一个 ID,Data Binding 就会为我们生成一个对应的 final
变量。
<TextView android:id="@+id/firstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
上面代码中定义了一个 ID 为 firstName
的 TextView
,那么它对应的变量就是public final TextView firstName;
具体代码可参考 ViewWithIDsActivity.java
ViewStubs
xml
中的 ViewStub
经过 binding 之后会转换成 ViewStubProxy, 具体代码可参考 ViewStubActivity.java简单用代码说明一下,xml 文件与之前的代码一样,根节点改为 layout
,在 LinearLayout
中添加一个ViewStub
,添加ID。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout ...>
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/view_stub" ... />
</LinearLayout>
</layout>
在 Java 代码中获取 binding
实例,为ViewStubProy
注册 ViewStub.OnInflateListener
事件:
binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
ViewStubBinding binding = DataBindingUtil.bind(inflated);
User user = new User("fee", "lang");
binding.setUser(user);
}
});
Dynamic Variables
完整代码可以参考 dynamic以 RecyclerView
为例,Adapter
的 DataBinding 需要动态生成,因此我们可以在onCreateViewHolder
的时候创建这个DataBinding,然后在onBindViewHolder
中获取这个 DataBinding。
public static class BindingHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public BindingHolder(View itemView) {
super(itemView);
}
public ViewDataBinding getBinding() {
return binding;
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
}
@Override
public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
ViewDataBinding binding = DataBindingUtil.inflate( LayoutInflater.from(viewGroup.getContext()), R.layout.list_item, viewGroup, false);
BindingHolder holder = new BindingHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
User user = users.get(position);
holder.getBinding().setVariable(BR.user, user);
holder.getBinding().executePendingBindings();
}
注意此处 DataBindingUtil
的用法:
ViewDataBinding binding = DataBindingUtil.inflate( LayoutInflater.from(viewGroup.getContext()), R.layout.list_item, viewGroup, false);
还有另外一种比较简洁的方式,直接在构造 Holder 时把 View与自动生成的 XXXBinding
进行绑定。
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {
private static final int USER_COUNT = 10;
@NonNull
private List<User> mUsers;
public UserAdapter() {
mUsers = new ArrayList<>(10);
for (int i = 0; i < USER_COUNT; i ++) {
User user = new User(RandomNames.nextFirstName(), RandomNames.nextLastName());
mUsers.add(user);
}
}
public static class UserHolder extends RecyclerView.ViewHolder {
private UserItemBinding mBinding;
public UserHolder(View itemView) {
super(itemView);
mBinding = DataBindingUtil.bind(itemView);
}
public void bind(@NonNull User user) {
mBinding.setUser(user);
}
}
@Override
public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.user_item, viewGroup, false);
return new UserHolder(itemView);
}
@Override
public void onBindViewHolder(UserHolder holder, int position) {
holder.bind(mUsers.get(position));
}
@Override
public int getItemCount() {
return mUsers.size();
}
}
Attribute setters
有了 Data Binding,即使属性没有在 declare-styleable中定义,我们也可以通过 xml 进行赋值操作。 为了演示这个功能,我自定义了一个 View - NameCard,属性资源 R.styleable.NameCard 中只定义了一个 age
属性,其中 firstName
和 lastName
只有对应的两个 setter
方法。只要有setter
方法就可以像下面代码一样赋值:
<com.liangfeizc.databindingsamples.attributesetters.UserView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/largePadding"
app:onClickListener="@{activity.clickListener}"
app:firstName="@{@string/firstName}"
app:lastName="@{@string/lastName}" app:age="27" />
onClickListener
也是同样道理,只不过我们是在 Activity
中定义了一个Listener
。
转换器 (Converters)
非常重要
使用 Converter 一定要保证它不会影响到其他的属性,例如这个 @BindingConversion- convertColorToString 就会影响到android:visibility, 因为他们都是都符合从 int 到 int 的转换。
在 xml 中为属性赋值时,如果变量的类型与属性不一致,通过 DataBinding 可以进行转换。例如,下面代码中如果要为属性 android:background
赋值一个int
型的 color
变量:
<View
android:background="@{isError.get() ? @color/red : @color/white}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_height="@{height}" />
只需要定义一个标记了@BindingConversion
的静态方法即可(方法的定义位置可以随意):
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
具体代码可参考 ConversionsActivity.java。
其他
Android应用程序安装目录:
/system/app系统自带的应用程序,无法删除。
/data/app 用户程序安装的目录,有删除权限。安装时把apk文件复制到此目录。
/data/data 存放应用程序的数据。
data/dalvik-cache 将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)。
APP安装过程:复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。
Android 如何查找so文件所在目录,安装APK时so安装到哪个目录