当前位置: 编程技术>移动开发
本页文章导读:
▪设立点击ProgressDialog外的区域对话框不消失 设置点击ProgressDialog外的区域对话框不消失
ProgressDialog mpDialog = new ProgressDialog(OrderTable.this); mpDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);//设置风格为圆形进度条 mpDialog.setTitle("提示");//设.........
▪ (DOC)Displaying Bitmaps Efficiently 二 (DOC)Displaying Bitmaps Efficiently 2
Processing Bitmaps Off the UI Thread
非ui线程处理位图。
BitmapFactory.decode*方法,在上一篇讨论过的,不应该在ui线程上处理的情况:从硬盘加载或从网络加载。因为加.........
▪ (DOC)Displaying Bitmaps Efficiently 三 (DOC)Displaying Bitmaps Efficiently 3
Handle Configuration Changes
运行时配置改变了,如屏幕的方向改变了,导致Android会销毁,重启。这就需要避免处理所有的图片了,南昌需要一个更缓和,更高效的.........
[1]设立点击ProgressDialog外的区域对话框不消失
来源: 互联网 发布时间: 2014-02-18
设置点击ProgressDialog外的区域对话框不消失
ProgressDialog mpDialog = new ProgressDialog(OrderTable.this);
mpDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);//设置风格为圆形进度条
mpDialog.setTitle("提示");//设置标题
mpDialog.setIcon(R.drawable.icon);//设置图标
mpDialog.setMessage("这是一个圆形进度条");
mpDialog.setIndeterminate(false);//设置进度条是否为不明确
mpDialog.setCancelable(true);//设置进度条是否可以按退回键取消
设置点击进度对话框外的区域对话框不消失
dialog.setCanceledOnTouchOutside(false);
ProgressDialog mpDialog = new ProgressDialog(OrderTable.this);
mpDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);//设置风格为圆形进度条
mpDialog.setTitle("提示");//设置标题
mpDialog.setIcon(R.drawable.icon);//设置图标
mpDialog.setMessage("这是一个圆形进度条");
mpDialog.setIndeterminate(false);//设置进度条是否为不明确
mpDialog.setCancelable(true);//设置进度条是否可以按退回键取消
设置点击进度对话框外的区域对话框不消失
dialog.setCanceledOnTouchOutside(false);
[2] (DOC)Displaying Bitmaps Efficiently 二
来源: 互联网 发布时间: 2014-02-18
(DOC)Displaying Bitmaps Efficiently 2
Processing Bitmaps Off the UI Thread 非ui线程处理位图。 BitmapFactory.decode*方法,在上一篇讨论过的,不应该在ui线程上处理的情况:从硬盘加载或从网络加载。因为加载时间未知,如果时间过久,会导致程序失去响应。 这章节是关于AsyncTask在后台处理图片的。 AsyncTask类提供了一个简易的方法处理后台事务,并通知ui线程。使用它需要创建一个子类,覆盖一些方法这里举一个加载图片到ImageView的例子: class BitmapWorkerTask extends AsyncTask { private final WeakReference imageViewReference; private int data = 0; public BitmapWorkerTask(ImageView imageView) { // Use a WeakReference to ensure the ImageView can be garbage collected imageViewReference = new WeakReference(imageView); } // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { data = params[0]; return decodeSampledBitmapFromResource(getResources(), data, 100, 100)); } // Once complete, see if ImageView is still around and set bitmap. @Override protected void onPostExecute(Bitmap bitmap) { if (imageViewReference != null && bitmap != null) { final ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); } } } } WeakReference是为了避免ImageView被回收时由于引用造成无法回收。所以多次判断是否为null值。这种为空的情况如Activity已经到了其它Activity,或配置变化了。 加载图片就简单了: public void loadBitmap(int resId, ImageView imageView) { BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(resId); } Handle Concurrency: ListView,GridView是另一个麻烦的地方,为了有效地使用内存,这些组件会在用户滚动时回收一些子View,如果每一个View都触发一个AsyncTask,不能保证在操作完成时,相关的View还存在。它可能被回收了 http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html 更详细地说明了并发的问题,提供了一个解决办法。存储最近的AsyncTask。 提供专用的Drawable子类来存储task, static class AsyncDrawable extends BitmapDrawable { private final WeakReference bitmapWorkerTaskReference; public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { super(res, bitmap); bitmapWorkerTaskReference = new WeakReference(bitmapWorkerTask); } public BitmapWorkerTask getBitmapWorkerTask() { return bitmapWorkerTaskReference.get(); } } 在执行BitmapWorkerTask时,先创建一个AsyncDrawable,绑定到相关的ImageView中, public void loadBitmap(int resId, ImageView imageView) { if (cancelPotentialWork(resId, imageView)) { final BitmapWorkerTask task = new BitmapWorkerTask(imageView); final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); imageView.setImageDrawable(asyncDrawable); task.execute(resId); } } cancelPotentialWork方法就是检查是否关联的task已经在运行了。它先调用cancel()结束先前的方法, public static boolean cancelPotentialWork(int data, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { final int bitmapData = bitmapWorkerTask.data; if (bitmapData != data) { // Cancel previous task bitmapWorkerTask.cancel(true); } else { // The same work is already in progress return false; } } // No task associated with the ImageView, or an existing task was cancelled return true; } getBitmapWorkerTask这个方法用于关联特定的ImageView private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { if (imageView != null) { final Drawable drawable = imageView.getDrawable(); if (drawable instanceof AsyncDrawable) { final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getBitmapWorkerTask(); } } return null; } 最后一步是在onPostExecute()确认是否任务结束了和当前的关联ImageView匹配: class BitmapWorkerTask extends AsyncTask { ... @Override protected void onPostExecute(Bitmap bitmap) { if (isCancelled()) { bitmap = null; } if (imageViewReference != null && bitmap != null) { final ImageView imageView = imageViewReference.get(); final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (this == bitmapWorkerTask && imageView != null) { imageView.setImageBitmap(bitmap); } } } } 这个实现可以用于listview,gridview这样回收他们的子元素的组件中,只要简单地调用loadBitmap方法就可以了。 这里介绍的方法可能的问题是导致很多的线程创建,销毁,这也算是一个问题吧。 Caching Bitmaps 缓存图片 加载单张图片到ui中,比较容易,更复杂一些,一次加载一系列图片。这种情况屏幕上的图片可能通过滚动,不再显示。 一些组件通过回收子元素来回收内存,垃圾回收器释放你已经加载的位图,非长期的引用。这是必须的,但无法提供流畅的体验,你需要避免一直处理这些图片,把它们 贴到屏幕上,内存或硬盘的缓存就可以提供一些帮助了。 Use a Memory Cache 内存缓存提供位图的快速访问,LruCache类(在Support Library中也可用的) 适合缓存位图,保持最近的引用对象,是强引用,清除早期的对象。 SoftReference or WeakReference在之前的版本最好使用这样的引用保存位图,便于回收,3.0版本以前位图是存储在本地内存中的,不容易回收。 不管是哪个版本,都应该使用软引用或弱弱引用,(据说软引用更适合,但这里用弱引用) 对于LruCache的大小选择,有几个因素需要参考的: 你的应用剩下部分需要多数内存? 你需要一次加载多少图片到屏幕上? 屏幕的大小与设备的解析度,高解析度的设备xhdpi像Nexus相比Galaxy S hdpi需要大的缓存来保存相同数量的图片。 维度与配置决定了位图的占用资源的多少。 图片的访问频率。一些比较常用到,另一些不常用,需要保持一些常用的在缓存中,或建多个LruCache对象来存储图片。 在数量与质量间平衡, 有时缓存大量的低分辨率的图片,而加载大图是用后台线程来处理。 对所有没有固定统一的规则,需要自己分析处理。缓存的大小需要自己试验,不同的系统不同的手机需要适配,找到一个合适的值。 下面提供一个使用LruCache的例子: private LruCache mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... // Get memory class of this device, exceeding this amount will throw an // OutOfMemory exception. final int memClass = ((ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE)).getMemoryClass(); // Use 1/8th of the available memory for this memory cache. final int cacheSize = 1024 * 1024 * memClass / 8; mMemoryCache = new LruCache(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in bytes rather than number of items. return bitmap.getByteCount(); } }; ... } public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } 这个例子中假设hdpi的设备中最小值是4m(32/8),全屏的图片在800*480分辨率中需要消耗1.5m(800*480*4),所以缓存了2.5页图片。 当加载图片时,LruCache会先检查,然后没有才加载其它的图片 public void loadBitmap(int resId, ImageView imageView) { final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { mImageView.setImageBitmap(bitmap); } else { mImageView.setImageResource(R.drawable.image_placeholder); BitmapWorkerTask task = new BitmapWorkerTask(mImageView); task.execute(resId); } } BitmapWorkerTask的更新版本: class BitmapWorkerTask extends AsyncTask { ... // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); return bitmap; } ... } Use a Disk Cache 磁盘缓存。 内存缓存更快速,当然不能仅靠内存来维持,listview,gridview会很快地占用了内存中的图片,而且你的Activity可能被销毁,然后再加载,这时就需要另一处缓存了。 磁盘缓存主要在下载图片时用到,缓存后不用再次下载,从磁盘中加载当然比从网络中要快得多了 DiskLruCache已经是一种健壮的实现 了,在4.0中提供了源码libcore/luni/src/main/java/libcore/io/DiskLruCache.java, 看个例子: private DiskLruCache mDiskCache; private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB private static final String DISK_CACHE_SUBDIR = "thumbnails"; @Override protected void onCreate(Bundle savedInstanceState) { ... // Initialize memory cache ... File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); ... } class BitmapWorkerTask extends AsyncTask { ... // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { final String imageKey = String.valueOf(params[0]); // Check disk cache in background thread Bitmap bitmap = getBitmapFromDiskCache(imageKey); if (bitmap == null) { // Not found in disk cache // Process as normal final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); } // Add final bitmap to caches addBitmapToCache(String.valueOf(imageKey, bitmap); return bitmap; } ... } public void addBitmapToCache(String key, Bitmap bitmap) { // Add to memory cache as before if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } // Also add to disk cache if (!mDiskCache.containsKey(key)) { mDiskCache.put(key, bitmap); } } public Bitmap getBitmapFromDiskCache(String key) { return mDiskCache.get(key); } // Creates a unique subdirectory of the designated app cache directory. Tries to use external // but if not mounted, falls back on internal storage. public static File getCacheDir(Context context, String uniqueName) { // Check if media is mounted or storage is built-in, if so, try and use external cache dir // otherwise use internal cache dir final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED || !Environment.isExternalStorageRemovable() ? context.getExternalCacheDir().getPath() : context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName); } 虽然这里没有看到DiskLruCache的源码,但是可以想像得出,前面的章节已经了如何加载图片了。 内存缓存在ui线程中检查,磁盘缓存在后台线程中使用。 未完待续
[3] (DOC)Displaying Bitmaps Efficiently 三
来源: 互联网 发布时间: 2014-02-18
(DOC)Displaying Bitmaps Efficiently 3
Handle Configuration Changes 运行时配置改变了,如屏幕的方向改变了,导致Android会销毁,重启。这就需要避免处理所有的图片了,南昌需要一个更缓和,更高效的办法。 前面已经讨论过内存缓存了,这个缓存可以通过Fragment的setRetainInstance(true)得到,Activity重建以后,Fragment会重新加载,reattached附着到Activity中下面是一个使用Fragment与LruCache在配置改变时的例子。 private LruCache mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... RetainFragment mRetainFragment = RetainFragment.findOrCreateRetainFragment(getFragmentManager()); mMemoryCache = RetainFragment.mRetainedCache; if (mMemoryCache == null) { mMemoryCache = new LruCache(cacheSize) { ... // Initialize cache here as usual } mRetainFragment.mRetainedCache = mMemoryCache; } ... } class RetainFragment extends Fragment { private static final String TAG = "RetainFragment"; public LruCache mRetainedCache; public RetainFragment() {} public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); if (fragment == null) { fragment = new RetainFragment(); } return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } } 要测试的话可以旋转屏幕改变Fragment的获取方式 Displaying Bitmaps in Your UI 在你自己的ui中显示位图 ,这章节将前面的几章节综合讨论,在配置改变或并发时如何加载图片到gridview这样的组件中。 在ViewPager中的实现 这个非正式的view模式,是个不错的办法,可以使用ViewPager组件后端是PagerAdapter提供数据,一个更有效的是FragmentStatePagerAdapter,因为它会自动上和保存状态。 如果只是一小部分的图片,且你相信他们不会超过内存限制,使用上述的adapter是个不错的选择。 public class ImageDetailActivity extends FragmentActivity { public static final String EXTRA_IMAGE = "extra_image"; private ImagePagerAdapter mAdapter; private ViewPager mPager; // A static dataset to back the ViewPager adapter public final static Integer[] imageResIds = new Integer[] { R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3, R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6, R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9}; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.image_detail_pager); // Contains just a ViewPager mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length); mPager = (ViewPager) findViewById(R.id.pager); mPager.setAdapter(mAdapter); } public static class ImagePagerAdapter extends FragmentStatePagerAdapter { private final int mSize; public ImagePagerAdapter(FragmentManager fm, int size) { super(fm); mSize = size; } @Override public int getCount() { return mSize; } @Override public Fragment getItem(int position) { return ImageDetailFragment.newInstance(position); } } } 具体的Fragment public class ImageDetailFragment extends Fragment { private static final String IMAGE_DATA_EXTRA = "resId"; private int mImageNum; private ImageView mImageView; static ImageDetailFragment newInstance(int imageNum) { final ImageDetailFragment f = new ImageDetailFragment(); final Bundle args = new Bundle(); args.putInt(IMAGE_DATA_EXTRA, imageNum); f.setArguments(args); return f; } // Empty constructor, required as per Fragment docs public ImageDetailFragment() {} @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // image_detail_fragment.xml contains just an ImageView final View v = inflater.inflate(R.layout.image_detail_fragment, container, false); mImageView = (ImageView) v.findViewById(R.id.imageView); return v; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final int resId = ImageDetailActivity.imageResIds[mImageNum]; mImageView.setImageResource(resId); // Load image into ImageView } } 这里图片由ui线程来读取的,会导致程序的fc,这时可以你自己修改成从地方读取,用到前面的AsyncTask或缓存方法。 public class ImageDetailActivity extends FragmentActivity { ... public void loadBitmap(int resId, ImageView imageView) { mImageView.setImageResource(R.drawable.image_placeholder); BitmapWorkerTask task = new BitmapWorkerTask(mImageView); task.execute(resId); } ... // include BitmapWorkerTask class } public class ImageDetailFragment extends Fragment { ... @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (ImageDetailActivity.class.isInstance(getActivity())) { final int resId = ImageDetailActivity.imageResIds[mImageNum]; // Call out to ImageDetailActivity to load the bitmap in a background thread ((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView); } } } 其它一些方法如确定大小,从网络获取可以在BitmapWorkerTask中处理了,可以加入缓存。 public class ImageDetailActivity extends FragmentActivity { ... private LruCache mMemoryCache; @Override public void onCreate(Bundle savedInstanceState) { ... // initialize LruCache as per Use a Memory Cache section } public void loadBitmap(int resId, ImageView imageView) { final String imageKey = String.valueOf(resId); final Bitmap bitmap = mMemoryCache.get(imageKey); if (bitmap != null) { mImageView.setImageBitmap(bitmap); } else { mImageView.setImageResource(R.drawable.image_placeholder); BitmapWorkerTask task = new BitmapWorkerTask(mImageView); task.execute(resId); } } ... // include updated BitmapWorkerTask from Use a Memory Cache section } 将上面合在一起就可以了。 3.0以后就有Fragment,这是个不错的东西,我在微博程序中就大量使用,它可以有Activity类似的生命周期,可以保存一些状态便于重建等。 Load Bitmaps into a GridView ,ListView是一样的处理。 http://developer.android.com/design/building-blocks/grid-lists.html 这里有说到一些相关的知识,可以参考下。 GridView会回收一些子元素,所以它能有足够的内存一直加载新的图片。现在来看看: public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener { private ImageAdapter mAdapter; // A static dataset to back the GridView adapter public final static Integer[] imageResIds = new Integer[] { R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3, R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6, R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9}; // Empty constructor as per Fragment docs public ImageGridFragment() {} @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAdapter = new ImageAdapter(getActivity()); } @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View v = inflater.inflate(R.layout.image_grid_fragment, container, false); final GridView mGridView = (GridView) v.findViewById(R.id.gridView); mGridView.setAdapter(mAdapter); mGridView.setOnItemClickListener(this); return v; } @Override public void onItemClick(AdapterView parent, View v, int position, long id) { final Intent i = new Intent(getActivity(), ImageDetailActivity.class); i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position); startActivity(i); } private class ImageAdapter extends BaseAdapter { private final Context mContext; public ImageAdapter(Context context) { super(); mContext = context; } @Override public int getCount() { return imageResIds.length; } @Override public Object getItem(int position) { return imageResIds[position]; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup container) { ImageView imageView; if (convertView == null) { // if it's not recycled, initialize some attributes imageView = new ImageView(mContext); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setLayoutParams(new GridView.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } else { imageView = (ImageView) convertView; } imageView.setImageResource(imageResIds[position]); // Load image into ImageView return imageView; } } } 同样是使用了Fragment,上面也是在ui线程对图片解码,所以需要修改为缓存的方式: public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener { ... private class ImageAdapter extends BaseAdapter { ... @Override public View getView(int position, View convertView, ViewGroup container) { ... loadBitmap(imageResIds[position], imageView) return imageView; } } public void loadBitmap(int resId, ImageView imageView) { if (cancelPotentialWork(resId, imageView)) { final BitmapWorkerTask task = new BitmapWorkerTask(imageView); final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); imageView.setImageDrawable(asyncDrawable); task.execute(resId); } } static class AsyncDrawable extends BitmapDrawable { private final WeakReference bitmapWorkerTaskReference; public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { super(res, bitmap); bitmapWorkerTaskReference = new WeakReference(bitmapWorkerTask); } public BitmapWorkerTask getBitmapWorkerTask() { return bitmapWorkerTaskReference.get(); } } public static boolean cancelPotentialWork(int data, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { final int bitmapData = bitmapWorkerTask.data; if (bitmapData != data) { // Cancel previous task bitmapWorkerTask.cancel(true); } else { // The same work is already in progress return false; } } // No task associated with the ImageView, or an existing task was cancelled return true; } private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { if (imageView != null) { final Drawable drawable = imageView.getDrawable(); if (drawable instanceof AsyncDrawable) { final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getBitmapWorkerTask(); } } return null; } ... // include updated BitmapWorkerTask class ListView是一样的,就不说了。 源码可以在示例程序中找到。 至此,也写过几篇关于网络图片载入到ListView中的文章了,这里翻译了文档中的一些文章,结合了缓存使用,文档中的办法固然也算是一种,但也不是完美的,不同的情况还是要不同对待,分析问题后再选择相应的办法。 文档只是提供了一些解决的办法,如何组合,如何应用不能一概而论的。因为它是一种通用型的。 如产生很多的线程就是一个问题,这些文档中显然没有提到,还有可以把缓存设置为静态,全局变量,文档中没有提到可能是因为静态变量不提倡用吧(有一篇关于静态变量引用的文章),但是对于一个程序来说,多处地方用到相同的缓存图片也有可能,为了方便使用把缓存设置成全局变量也是可以的。
最新技术文章: