转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay
从上一篇学习中,学习了多媒体技术中的怎么去用josup加载一个网页并解析html标签的用法,今天就接着前篇 【安卓TV开发(七) 移动智能终端多媒体之在线解析网页视频源】 的学习。同时也了解下避免安卓内存溢出解决方式和安卓常用的几种UI更新的方式。
一 准备异步加载工具
1 新建 VideoLoaderTask 用来获取视频列表
/** * Represents an asynchronous loaderVideoInfos task used to authenticate the * user. */ public class VideoLoaderTask extends AsyncTask<TvModle, String, List<TvTaiModel>> { @SuppressWarnings("unchecked") @Override protected List<TvTaiModel> doInBackground(TvModle... params) { // TODO Auto-generated method stub return lists = DataVideoManager.getData(params[0]); } @Override protected void onPostExecute(final List<TvTaiModel> resList) { mAuthTask = null; showProgress(false); if (resList != null && resList.size() > 0) { // Log.e(TAG ,success +"--"); adapter = new VideoWallAdapter(VideoInfoActivity.this, 0, resList, mPhotoWall); mPhotoWall.setAdapter(adapter); adapter.notifyDataSetChanged(); } else { Toast.makeText(VideoInfoActivity.this, "失败", Toast.LENGTH_SHORT).show(); } } @Override protected void onCancelled() { mAuthTask = null; showProgress(false); }此类设计到安卓AsyncTask的用法,需要大家了解此Api,具体原理是利用Thead+ handler机制实现,实际开发中我们更新UI也可以用安卓自带的UI线程runOnUiThread 代码可以如下,具体执行动作在run()实现,不管是用哪种 的方式更新UI,必须注意的是主线程不能执行网络耗时操作任务,容易出现ANR,(安卓4.0rom以后 主线程直接不能访问网络)。UI也必须由主线程来更新,子线程无UI操作权限。
1) 利用UI线程
this.runOnUiThread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } });2 ) 利用handler发送Message
mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } };3) 利用view的post方法
<span style="color: rgb(85, 85, 85); font-family: 'microsoft yahei'; font-size: 14.7368421554565px; line-height: 35px;"> View.post(</span>new Runnable() { @Override public void run() { // TODO Auto-generated method stub } })
AsyncTask.start() 一般用此类获取网络数据,但是本线程不能重复调用,不然会出异常,只有在task没有运行的情况下才能调用start()方法,多个线程同是调用其start() 也会出现线程安全问题。
2 因为网络任务还耗时的因此给Task加一个过渡loading
/** * Shows the progress UI and hides the geimainUI form. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) private void showProgress(final boolean show) { // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow // for very easy animations. If available, use these APIs to fade-in // the progress spinner. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { int shortAnimTime = getResources().getInteger( android.R.integer.config_shortAnimTime); mLoginStatusView.setVisibility(View.VISIBLE); mLoginStatusView.animate().setDuration(shortAnimTime) .alpha(show ? 1 : 0) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); } }); mPhotoWall.setVisibility(View.VISIBLE); mPhotoWall.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mPhotoWall.setVisibility(show ? View.GONE : View.VISIBLE); } }); } else { // The ViewPropertyAnimator APIs are not available, so simply show // and hide the relevant UI components. mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); mPhotoWall.setVisibility(show ? View.GONE : View.VISIBLE); } }3 获取到网络数据必定含有图片 为了防止OOM,新建ImageLoader
当然目前已经有开源的图片缓存框架,但是我们必须懂期原理,为了防止图片内存溢出,我们必须合理利用安卓内存,熟悉安卓回收机制的朋友也非常了解安卓强引用和弱引用,所谓的一级缓存和二级缓存,开发中常用的缓存技术,一般是采用合理分配内存和适当过渡承载实现,具体可以获取手机当前的内存大小,合理设置本次图片请求的最大的负载内存,超过了缓存大小 移除使用频率较低的图片键值,一般是去sd卡读取资源,然后再内存,最后才去请求网络数据。当然请求的图片实际尺寸过大会导致oom。因此必要时还需要根据当前屏幕上所要展示ImageView的大小去缩放bitmap,本人平时喜欢绘制bitmap带自自定义控件上,不喜欢用安卓原生的图片控件。其利弊以后我再慢慢道来。
1 ), 具体缓存逻辑如下,
public class ImageLoader { /** * 图片缓存加载器 */ private static LruCache<String, Bitmap> mMemoryCache; /** * ImageLoader。 */ private static ImageLoader mImageLoader; @SuppressLint("NewApi") private ImageLoader() { // 获取应用程序最大可用内存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 8; // 设置图片缓存大小为程序最大可用内存的1/8 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @SuppressLint("NewApi") @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount(); } }; } /** * 获取ImageLoader的实例。 * * @return ImageLoader的实例。 */ public static ImageLoader getInstance() { if (mImageLoader == null) { mImageLoader = new ImageLoader(); } return mImageLoader; } /** * 将一张图片存储到LruCache中。 * * @param key * LruCache的键,这里传入图片的URL地址。 * @param bitmap * LruCache的键,这里传入从网络上下载的Bitmap对象。 */ @SuppressLint("NewApi") public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemoryCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 从LruCache中获取一张图片,如果不存在就返回null。 * * @param key * LruCache的键,这里传入图片的URL地址。 * @return 对应传入键的Bitmap对象,或者null。 */ @SuppressLint("NewApi") public Bitmap getBitmapFromMemoryCache(String key) { return mMemoryCache.get(key); } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth) { // 源图片的宽度 final int width = options.outWidth; int inSampleSize = 1; if (width > reqWidth) { // 计算出实际宽度和目标宽度的比率 final int widthRatio = Math.round((float) width / (float) reqWidth); inSampleSize = widthRatio; } return inSampleSize; } public static Bitmap decodeSampledBitmapFromResource(String pathName, int reqWidth) { // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName, options); // 调用上面定义的方法计算inSampleSize值 options.inSampleSize = calculateInSampleSize(options, reqWidth); // 使用获取到的inSampleSize值再次解析图片 options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(pathName, options); } }
/** * 异步下载图片的任务。 * * @author guolin */ class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> { /** * 图片的URL地址 */ private String imageUrl; @Override protected Bitmap doInBackground(String... params) { imageUrl = params[0]; // 在后台开始下载图片 Bitmap bitmap = downloadBitmap(params[0]); if (bitmap != null) { // 图片下载完成后缓存到LrcCache中 addBitmapToMemoryCache(params[0], bitmap); } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); // 根据Tag找到相应的ImageView控件,将下载好的图片显示出来。 ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl); if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } taskCollection.remove(this); } /** * 建立HTTP请求,并获取Bitmap对象。 * * @param imageUrl * 图片的URL地址 * @return 解析后的Bitmap对象 */ private Bitmap downloadBitmap(String imageUrl) { Bitmap bitmap = null; HttpURLConnection con = null; try { URL url = new URL(imageUrl); con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(5 * 1000); con.setReadTimeout(10 * 1000); bitmap = BitmapFactory.decodeStream(con.getInputStream()); } catch (Exception e) { e.printStackTrace(); } finally { if (con != null) { con.disconnect(); } } return bitmap; } }二 新建视图 activity
我们可以利用第五篇中实现的UI界面加以利用 点击去请求音悦台MV视频数据。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_info); mContext = getBaseContext(); mImageThumbSize = getResources().getDimensionPixelSize( R.dimen.image_thumbnail_size); mImageThumbSpacing = getResources().getDimensionPixelSize( R.dimen.image_thumbnail_spacing); mLoginStatusView = this.findViewById(R.id.login_status); mLoginStatusMessageView = (TextView) this .findViewById(R.id.login_status_message); mPhotoWall = (GridView) findViewById(R.id.video_info); mPhotoWall.setOnItemClickListener(this); if (getIntent().getExtras() != null) { modle = (TvModle) getIntent().getExtras().getSerializable( "TvModle"); if (modle != null) { mUrl = modle.getUrl(); } } mPhotoWall.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { final int numColumns = (int) Math.floor(mPhotoWall .getWidth() / (mImageThumbSize + mImageThumbSpacing)); if (numColumns > 0) { int columnWidth = (mPhotoWall.getWidth() / numColumns) - mImageThumbSpacing; //mAdapter.setItemHeight(columnWidth); mPhotoWall.getViewTreeObserver() .removeGlobalOnLayoutListener(this); } } }); /*mUrl = Constant.QQ_TAI_URL;*/ attemptLoader(); } @Override protected void onDestroy() { super.onDestroy(); adapter.cancelAllTasks(); } /** * Attempts to sign in or register the account specified by the login form. * If there are form errors (invalid email, missing fields, etc.), the * errors are presented and no actual login attempt is made. */ public void attemptLoader() { if (mAuthTask != null) { return; } boolean cancel = false; if (cancel) { } else { // Show a progress spinner, and kick off a background task to // perform the user login attempt. showProgress(true); mAuthTask = new VideoLoaderTask(); mAuthTask.execute(modle); } }
三 增加控制逻辑
创建gridView适配器 VideoWallAdapter
public class VideoWallAdapter extends ArrayAdapter<TvTaiModel> implements OnScrollListener { /** * 记录所有正在下载或等待下载的任务。 */ private Set<BitmapWorkerTask> taskCollection; /** * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。 */ private LruCache<String, Bitmap> mMemoryCache; /** * GridView的实例 */ private GridView mPhotoWall; /** * 第一张可见图片的下标 */ private int mFirstVisibleItem; /** * 一屏有多少张图片可见 */ private int mVisibleItemCount; /** * 记录是否刚打开程序,用于解决进入程序不滚动屏幕,不会下载图片的问题。 */ private boolean isFirstEnter = true; private List< TvTaiModel> lists = null; @SuppressLint("NewApi") public VideoWallAdapter(Context context, int textViewResourceId, List<TvTaiModel> taiModels, GridView photoWall) { super(context, textViewResourceId, taiModels); //super(context, textViewResourceId); lists = taiModels; mPhotoWall = photoWall; taskCollection = new HashSet<BitmapWorkerTask>(); // 获取应用程序最大可用内存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 8; // 设置图片缓存大小为程序最大可用内存的1/8 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount(); } }; mPhotoWall.setOnScrollListener(this); } @Override public View getView(int position, View convertView, ViewGroup parent) { final String url = lists.get(position).getImg(); final String name = lists.get(position).getTitle(); Log.e("VideoWallAdapter", url); View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(R.layout.vedio_item, null); } else { view = convertView; } final ImageView photo = (ImageView) view.findViewById(R.id.photo); final TextView title = (TextView) view.findViewById(R.id.title); // 给ImageView设置一个Tag,保证异步加载图片时不会乱序 photo.setTag(url); setImageView(url, photo); title.setText(name); return view; }
此时的我们的基础工具类已完成, 接下来 给我们的FocusView所在的acitity注册处理点击事件,用于获取当前TV的视频Url
@Override public void onItemClick(FocusView mFocusView, View focusView, FocusItemModle<TvModle> focusItem, int Postion, int row, int col, long id) { Intent intent = new Intent(); Bundle bundle = new Bundle(); if (focusItem != null && focusItem.getModle() != null) { bundle.putSerializable("TvModle", focusItem.getModle()); } intent.putExtras(bundle); intent.setClass(FocusUIActivity.this, values[0]); startActivity(intent); }
最后运行 效果如下
这样就把音悦台的MV资源给解析出来并展现到我们自己的APP上,下一篇会继续结合本篇的逻辑,实现点击具体视频获取真实地址 播放网络视频功能,欢迎大家阅读,如需转载请标明出处 http://blog.csdn.net/sk719887916/article/details/40049137,欢迎交流分享。