今天把OS X升级到10.9 Mavericks,居然java环境出错了,于是趁这个机会顺便把jdk升级到1.7,下载安装jdk1.7一切搞定之后打开eclipse时竟然弹出提示:
To open “Eclipse,” you need a Java SE 6 runtime. Would you like to install one now?
经过查找和实验,把解决方案记录分享在此。
如,我的系统上是修改:/Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Info.plist 文件,将这部分
<key>JVMCapabilities</key>
<array>
<string>CommandLine</string>
</array>
改为如下:(主要是添加了4行东东)
<key>JVMCapabilities</key>
<array>
<string>JNI</string>
<string>BundledApp</string>
<string>WebStart</string>
<string>Applets</string>
<string>CommandLine</string>
</array>
修改后,重启系统,再打开Eclipse这样的软件就会正常启动了。
相信很多Android开发者一定受够了速度慢、体验差效率及其地下的官方模拟器了,自己在平时的开发中几乎是不会用模拟器的,等的时间太久了,但是在一些尺寸适配或是兼容性测试的时候没有足够多的机器进行测试,这个时候就必须得用模拟器来代替了。用的久了真的不堪忍受那龟速般的模拟器,好在今天发现了一款神级模拟器Genymotion,就像发现新大陆般喜爱,下面就来介绍下。
Genymotion是一套完整的工具,它提供了Android虚拟环境。如果你没有物理机器,又不想忍受官方模拟器的折磨,Genymotion会是你非常不错的选择, 它简直就是开发者、测试人员、推销者甚至是游戏玩家的福音。
Genymotion支持Windows、Linux和Mac OS,容易安装和使用,下面就然我们一起来体验神器给我们带来的快感吧。
支持OpenGL加速,提供最好的3D性能体验
可以从Google Play安装应用
支持全屏并改善了使用感受
可同时启动多个模拟器
支持传感器管理,如电池状态、GPS、Accelerator加速器
支持Shell控制模拟器
完全兼容ADB,您可以从主机控制您的模拟器
易安装
兼容Microsoft Windows 32/64 bits, Mac OSX 10.5+ and Linux 32/64 bits
可以配置模拟器参数,如屏幕分辨率、内存大小、CPU数量
轻松下载、部署最新的Genymotion虚拟设备。
安装基本是一路next,虽然Genymotion是免费版的,但是要求注册个账号才可以配置模拟器,配置好启动真是神速啊。运行效果:
Genymotion还支持Eclipse IDE,这大大方便了我们使用Genymotion来开发应用。安装方式:
启动Eclipse,Help->Install New Software…->Add
填写一下信息:
Name: Genymobile
Location: http://plugins.genymotion.com/eclipse
最后附上官网地址:http://www.genymotion.com/
之前项目中用到的图片异步加载库是LazyList。随着需求的不算增长,需求有点不太能满足,缺少可配置的选项,于是换到了强大的UniversalImageLoader。
Android-Universal-Image-Loader是一个开源的图片异步加载库,该项目的目的是提供一个可重复使用的仪器为异步图像加载,缓存和显示。该库非常强大,国内外很多有名的应用程序都有使用,在该类库的默认缓存文件夹中甚至发现了google, instagram, qq, baidu都有在用。
多线程的图像加载
尽可能多的配置选项(线程池,加载器,解析器,内存/磁盘缓存,显示参数等等)
图片可以缓存在内存中,或者设备文件目录下,或者SD卡中
可以添加图片加载监听器
可以自定义显示每一张图片时都带不同参数
支持Widget
Android 1.5以上支持
<manifest>
<uses-permission android:name="android.permission.INTERNET" />
<!-- Include next permission if you want to allow UIL to cache images on SD card -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
<application android:name="MyApplication">
...
</application>
</manifest>
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Create global configuration and initialize ImageLoader with this configuration
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
...
.build();
ImageLoader.getInstance().init(config);
}
}
所有的选项都是可选的,只选择你真正想制定的去配置。
File cacheDir = StorageUtils.getCacheDirectory(context);
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
//如果图片尺寸大于了这个参数,那么就会这按照这个参数对图片大小进行限制并缓存
.memoryCacheExtraOptions(480, 800) // default=device screen dimensions
.discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75)
.taskExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
.taskExecutorForCachedImages(AsyncTask.THREAD_POOL_EXECUTOR)
.threadPoolSize(3) // default
.threadPriority(Thread.NORM_PRIORITY - 1) // default
.tasksProcessingOrder(QueueProcessingType.FIFO) // default
.denyCacheImageMultipleSizesInMemory()
.memoryCache(new LruMemoryCache(2 * 1024 * 1024))
.memoryCacheSize(2 * 1024 * 1024)
.discCache(new UnlimitedDiscCache(cacheDir)) // default
.discCacheSize(50 * 1024 * 1024)
.discCacheFileCount(100)
.discCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
.imageDownloader(new BaseImageDownloader(context)) // default
.imageDecoder(new BaseImageDecoder()) // default
.defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
.enableLogging()
.build();
显示参数可以分别被每一个显示任务调用(ImageLoader.displayImage(…))
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.ic_stub) // 在显示真正的图片前,会加载这个资源
.showImageForEmptyUri(R.drawable.ic_empty) //空的Url时
.showImageOnFail(R.drawable.ic_error)
.resetViewBeforeLoading() //
.delayBeforeLoading(1000) // 延长1000ms 加载图片 (想不出来用在什么场景下)
.cacheInMemory()
.cacheOnDisc()
.preProcessor(...)
.postProcessor(...)
.extraForDownloader(...) //可以向加载器携带一些参数
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
.bitmapConfig(Bitmap.Config.ARGB_8888) // default
.decodingOptions(...)
.displayer(new SimpleBitmapDisplayer()) // default
.handler(new Handler()) // default
.build();
String imageUri = "http://site.com/image.png"; // from Web
String imageUri = "file:///mnt/sdcard/image.png"; // from SD card
String imageUri = "content://media/external/audio/albumart/13"; // from content provider
String imageUri = "assets://image.png"; // from assets
String imageUri = "drawable://" + R.drawable.image; // from drawables (only images, non-9patch)
// Load image, decode it to Bitmap and display Bitmap in ImageView
imageLoader.displayImage(imageUri, imageView, displayOptions,
new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
...
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
...
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
...
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
...
}
});
ImageSize targetSize = new ImageSize(120, 80); // result Bitmap will be fit to this size
imageLoader.loadImage(imageUri, targetSize, displayOptions, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
// Do whatever you want with Bitmap
}
});
我们在编写Android程序的时候经常要用到许多图片,不同图片总是会有不同的形状、不同的大小,但在大多数情况下,这些图片都会大于我们程序所需要的大小。比如说系统图片库里展示的图片大都是用手机摄像头拍出来的,这些图片的分辨率会比我们手机屏幕的分辨率高得多。大家应该知道,我们编写的应用程序都是有一定内存限制的,程序占用了过高的内存就容易出现OOM(OutOfMemory)异常。我们可以通过下面的代码看出每个应用程序最高可用内存是多少。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.d("TAG", "Max memory is " + maxMemory + "KB");
因此在展示高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。下面我们就来看一看,如何对一张大图片进行适当的压缩,让它能够以最佳大小显示的同时,还能防止OOM的出现。
BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。比如SD卡中的图片可以使用decodeFile方法,网络上的图片可以使用decodeStream方法,资源文件中的图片可以使用decodeResource方法。这些方法会尝试为已经构建的bitmap分配内存,这时就会很容易导致OOM出现。为此每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。如下代码所示:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
为了避免OOM异常,最好在解析每张图片的时候都先检查一下图片的大小,除非你非常信任图片的来源,保证这些图片都不会超出你程序的可用内存。现在图片的大小已经知道了,我们就可以决定是把整张图片加载到内存中还是加载一个压缩版的图片到内存中。以下几个因素是我们需要考虑的:
预估一下加载整张图片所需占用的内存
为了加载这一张图片你所愿意提供多少内存
用于展示这张图片的控件的实际大小
当前设备的屏幕尺寸和分辨率
比如,你的ImageView只有128*96像素的大小,只是为了显示一张缩略图,这时候把一张1024*768像素的图片完全加载到内存中显然是不值得的。那我们怎样才能对图片进行压缩呢?通过设置BitmapFactory.Options中inSampleSize的值就可以实现。比如我们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素。原本加载这张图片需要占用13M的内存,压缩后就只需要占用0.75M了(假设图片是ARGB_8888类型,即每个像素点占用4个字节)。下面的方法可以根据传入的宽和高,计算出合适的inSampleSize值:
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
使用这个方法,首先你要将BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片。然后将BitmapFactory.Options连同期望的宽度和高度一起传递到到calculateInSampleSize方法中,就可以得到合适的inSampleSize值了。之后再解析一次图片,使用新获取到的inSampleSize值,并把inJustDecodeBounds设置为false,就可以得到压缩后的图片了。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
下面的代码非常简单地将任意一张图片压缩成100*100的缩略图,并在ImageView上展示。
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
使用图片缓存技术在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。在很多情况下,(比如使用ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM。为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况。这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。这个时候,使用内存缓存技术可以很好的解决这个问题,它可以让组件快速地重新加载和处理图片。下面我们就来看一看如何使用内存缓存技术来对图片进行缓存,从而让你的应用程序在加载很多图片的时候可以提高响应速度和流畅性。内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:
你的设备可以为每个应用程序分配多大的内存?
设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
图片的尺寸和大小,还有每张图片会占据多少内存空间。
图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。
并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。 下面是一个使用 LruCache 来缓存图片的例子:
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。 当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。
public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
}
BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
// 在后台加载图片。
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100);
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
}
掌握了以上两种方法,不管是要在程序中加载超大图片,还是要加载大量图片,都不用担心OOM的问题了!
Android开发中我们经常会用到各种各样的loading,于是自己总结了常用的loading并分享出来。首先来看下具体效果图:
完整源码参见:stormzhang / CustomLoading
下面主要说下代码的关键部分:
第一个就是在app中常见的loading效果,主要是用帧动画实现的,所谓帧动画就是一组组图片顺序播放; 下面直接看下代码实现:
首先在drawable文件夹下建立一个xml文件,内容如下:
frame_loading.xml
<?xml version="1.0" encoding="UTF-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false" >
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_01"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_02"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_03"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_04"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_05"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_06"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_07"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_08"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_09"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_10"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_11"
android:gravity="left" />
</item>
<item android:duration="150">
<clip
android:clipOrientation="horizontal"
android:drawable="@drawable/loading_12"
android:gravity="left" />
</item>
</animation-list>
注意上面item下得clip标签,这个是必须的,不然不同的屏幕尺寸适配会有问题。然后在布局文件里这样调用:
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/frame_loading" />
第二种主要是用rotate动画来实现的,具体代码如下:
rotate_loading.xml
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" >
<bitmap
android:antialias="true"
android:filter="true"
android:src="@drawable/loading_360" />
</rotate>
然后调用方法和第一种一样。
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/rotate_loading" />
第三种loading的是自定义了一个组件,主要用到了ClipDrawable的setLevel()方法,代码如下:
public class CustomClipLoading extends FrameLayout {
private static final int MAX_PROGRESS = 10000;
private ClipDrawable mClipDrawable;
private int mProgress = 0;
private boolean running;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 如果消息是本程序发送的
if (msg.what == 0x123) {
mClipDrawable.setLevel(mProgress);
}
}
};
public CustomClipLoading(Context context) {
this(context, null, 0);
}
public CustomClipLoading(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomClipLoading(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
View view = LayoutInflater.from(context).inflate(layout.custom_loading, null);
addView(view);
ImageView imageView = (ImageView) findViewById(id.iv_progress);
mClipDrawable = (ClipDrawable) imageView.getDrawable();
Thread s = new Thread(r);
s.start();
}
public void stop() {
mProgress = 0;
running = false;
}
Runnable r = new Runnable() {
@Override
public void run() {
running = true;
while (running) {
handler.sendEmptyMessage(0x123);
if (mProgress > MAX_PROGRESS) {
mProgress = 0;
}
mProgress += 100;
try {
Thread.sleep(18);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
其中custom_loading布局文件的内容如下:
custom_loading.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/iv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/loading_bg"
android:paddingLeft="3dp"
android:paddingTop="3dp"
android:scaleType="centerInside"
android:src="@drawable/clip_loading" />
然后clip_loading的内容如下:
clip_loading.xml
<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:clipOrientation="vertical"
android:drawable="@drawable/loading_progress"
android:gravity="bottom" >
</clip>
至此这个组件就定义好了,那么接下来就是在avtivity布局文件的使用了:
<com.storm.ui.CustomClipLoading
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
第四和第五种loading只要是用到了一个开源的项目ProgressWheel,使用方法也很简单,具体见项目说明。