Android WebView加载html5页面实例教程
实例一:WebView加载html5实现炫酷引导页面
大多数人都知道,一个APP的引导页面还是挺重要的,不过要想通过原生的Android代码做出一个非常炫酷的引导页相对还是比较复杂的,正巧html5在制作炫酷动画网页方面比较给力,我们不妨先利用html5做出手机引导页面,然后将其嵌入APP中。
首先我们分析一下,都需要做哪些工作?
1、制作html5引导页面。
2、把做好的页面放入Android工程中assets文件夹下。
3、利用WebView加载asset文件夹下的html文件。
4、在引导页最后一页的按钮上捕捉点击事件,结束引导页,进入程序。
简单的讲,整个工作就分以上四步,其中涉及到了Android中一个自带的浏览器控件--WebView,在介绍引导页之前,先来大体说一下WebView的基本用法。
一、使用WebView浏览网页
用WebView浏览网页,这是最常用也是最简单的用法,与普通的ImageView组件的用法基本相似,它也提供了大量方法来执行浏览器操作,常用的几个如下:
·void goBack():后退。
·void goForward():前进。
·void loadUrl(String url):加载url网页。
·boolean zoomIn():放大网页。
·boolean zoomOut():缩小网页。
……
下面看一个例子,通过WebView浏览百度首页,效果图如下。
首先不要忘了在AndroidMainfest.xml加入访问网络的权限:
<uses-permission android:name="android.permission.INTERNET"/>
布局文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <WebView android:id="@+id/wv_webview" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
java代码如下:
public class WebViewLoadWeb extends Activity { WebView webView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.webview); webView = (WebView)findViewById(R.id.wv_webview); loadWeb(); } public void loadWeb(){ String url = "https://www.baidu.com/"; //此方法可以在webview中打开链接而不会跳转到外部浏览器 webView.setWebViewClient(new WebViewClient()); webView.loadUrl(url); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //重写onKeyDown,当浏览网页,WebView可以后退时执行后退操作。 if(keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()){ webView.goBack(); return true; } return super.onKeyDown(keyCode, event); } }
上面代码首先加载布局文件中WebView视图,然后通过setWebViewClient()方法设置了打开新连接不会跳转到外部浏览器。最后通过loadUrl()方法加载了网址。至于该WebView如何发送请求,如何解析服务器,这些细节对我们来说是完全透明的,我们不需要关心。
另外需要一提的是,当使用WebView浏览网页时,不做处理的话,按下手机的返回键会直接结束WebView所在的Activity,通过重写onKeyDown()方法,当WebView可以返回时,让其执行返回操作。
二、使用WebView加载HTML代码。
WebView提供了一个loadData(String data, String mimeType, String encoding)方法,该方法可用于加载并显示HTML代码,不过这个方法在加载html代码时很可能会出现乱码的现象。建议用WebView的另一个方法loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)。可以把这个方法认为是loadData()的增强版,它不会产生乱码。以下是他的几个参数说明:
·data:指定需要加载的html代码。
·mimeType:指定html代码的MIME类型,对于HTML代码可指定为text/html。
·encoding:指定html代码编码所用的字符集。
下面看一个使用WebView加载html代码的简单实例,效果图如下:
java代码如下:
public void loadHtmlData(){ String data = "<html>" + "<head>" + "<title>欢迎您</title>" + "</head>" + "<body>" + "<p>我是一段html代码</p>" + "</body>" + "</html>"; webView.setWebViewClient(new WebViewClient()); //使用简单的loadData()方法总会导致乱码,有可能是Android API的Bug //webView.loadData(data, "text/html", "GBK"); webView.loadDataWithBaseURL(null, data, "text/html", "utf-8", null); }
上面代码很简单,就是用loadDataWithBaseURL()方法加载html代码,在这里就不过多介绍了。
下面进入本篇介绍的重点,通过加载本地html文件实现炫酷引导页。
三、加载本地HTML文件实现炫酷引导页。
接下来为本篇重点,通过加载H5的方式可以很轻松做出炫酷的引导页,当然前提时你得先做出或者找到一个很好H5引导页文件。需要说明的都已经在文章开头说过了,就不废话了,先上效果图:
相信不用我解释大家就能看懂,最左边的是第一张页面,中间的是过度动画效果,最右边的是最后一张,其中在最后一张上面有个按钮,捕捉到这个按钮的跳转链接是关键。下面先来看代码(html文件在assets文件夹下):
public class WebViewLoadHtml extends Activity { private String url; WebView webView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //将屏幕设置为全屏 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //去掉标题栏 requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.webview); webView = (WebView)findViewById(R.id.wv_webview); url = "file:///android_asset/guide/index.html"; loadLocalHtml(url); } @SuppressLint({ "JavascriptInterface", "SetJavaScriptEnabled" }) public void loadLocalHtml(String url){ WebSettings ws = webView.getSettings(); ws.setJavaScriptEnabled(true);//开启JavaScript支持 webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { //重写此方法,用于捕捉页面上的跳转链接 if ("http://start/".equals(url)){ //在html代码中的按钮跳转地址需要同此地址一致 Toast.makeText(getApplicationContext(), "开始体验", Toast.LENGTH_SHORT).show(); finish(); } return true; } }); webView.loadUrl(url); } }
上面代码首先将程序设为全屏无标题栏,这样才更像引导页(注:截图中没有全屏是应为使用截图工具时唤出了状态栏)。需要注意的是当加载具有js的文件时需通过WebSettings的setJavaScriptEnabed()方法开启对js的支持。然后通过setWebViewClient()重写shouldOverrideUrlLoading()方法,具体用法源码注释中已给出。
其中用到html文件的按钮跳转链接的源码如下:
<div class="swiper-slide"> <span>第三页</span> <span class="subtitle">这是第三页</span> <a href="javascript:" onClick="window.open('http:start')" class="swiper_read_more">开启APP之旅</a> </div>
实例二:WebView从assets中加载html5页面,实现地理位置定位
今天调研一个html5页面的定位问题,发现在手机浏览器上html5是可以实现定位的,但是在webview中就无法定位了。而我居然以为html5的地理定位在webview中不可行。html5页面内容如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <p id="demo">点击这个按钮,获得您的坐标:</p> <button onclick="getLocation()">试一下</button> <script> var x=document.getElementById("demo"); function getLocation() { if (navigator.geolocation) { navigator.geolocation.watchPosition(showPosition); } else{x.innerHTML="Geolocation is not supported by this browser.";} } function showPosition(position) { x.innerHTML="Latitude: " + position.coords.latitude + "<br />Longitude: " + position.coords.longitude; } </script> </body> </html>
后来又在网上查了查,发现,需要设置一些东西。设置websetting的属性:
webView.setWebViewClient(new WebViewClient()); //webView.loadUrl("http://news.baidu.com/"); webView.loadUrl("file:///android_asset/index.html"); WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); /** * 以下部分可以不要 */ // //启用数据库 // webSettings.setDatabaseEnabled(true); // String dir = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); // // //启用地理定位 // webSettings.setGeolocationEnabled(true); // //设置定位的数据库路径 // webSettings.setGeolocationDatabasePath(dir); /** * 此处很重要,必须要 */ //***最重要的方法,一定要设置,这就是出不来的主要原因 webSettings.setDomStorageEnabled(true); webView.setWebChromeClient(new WebChromeClient(){ //配置权限(同样在WebChromeClient中实现) @Override public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { callback.invoke(origin, true, false); super.onGeolocationPermissionsShowPrompt(origin, callback); } });
问题解决!
特别试了试:
同时打开网络和GPS可以定位,获得的是经纬度信息。
只打开网络可以定位,只打开GPS也可以定位。
关闭网络和GPS也可以定位。
由此可见,是由地理位置定位所得,只能获得经纬度信息。
若要获得详细的地址信息,需要调用地图API实现。如下所示:
纬度:116.40387397,经度:39.91488908 详细地址:北京市天安门
在Android所有系统自带的控件当中,ListView这个控件算是用法比较复杂的了,关键是用法复杂也就算了,它还经常会出现一些稀奇古怪的问题,让人非常头疼。比如说在ListView中加载图片,如果是同步加载图片倒还好,但是一旦使用异步加载图片那么问题就来了,这个问题我相信很多Android开发者都曾经遇到过,就是异步加载图片会出现错位乱序的情况。遇到这个问题时,不少人在网上搜索找到了相应的解决方案,但是真正深入理解这个问题出现的原因并对症解决的人恐怕还并不是很多。那么今天我们就来具体深入分析一下ListView异步加载图片出现乱序问题的原因,以及怎么样对症下药去解决它。
本篇文章的原理基础建立在上一篇文章之上,如果你对ListView的工作原理还不够了解的话,建议先去阅读 Android ListView工作原理完全解析,带你从源码的角度彻底理解 。
问题重现
要想解决问题首先我们要把问题重现出来,这里只需要搭建一个最基本的ListView项目,然后在ListView中去异步请求图片并显示,问题就能够得以重现了,那么我们就新建一个ListViewTest项目。
项目建好之后第一个要解决的是数据源的问题,由于ListView中需要从网络上请求图片,那么我就提前准备好了许多张图片,将它们上传到了我的CSDN相册当中,然后新建一个Images类,将所有相册中图片的URL地址都配置进去就可以了,代码如下所示:
public class Images {
public final static String[] imageUrls = new String[] {
"201508/05/1438760758_3497.jpg",
"201508/05/1438760758_6667.jpg",
"201508/05/1438760757_3588.jpg",
"201508/05/1438760756_3304.jpg",
"201508/05/1438760755_6715.jpeg",
"201508/05/1438760726_5120.jpg",
"201508/05/1438760726_8364.jpg",
"201508/05/1438760725_4031.jpg",
"201508/05/1438760724_9463.jpg",
"201508/05/1438760724_2371.jpg",
"201508/05/1438760707_4653.jpg",
"201508/05/1438760706_6864.jpg",
"201508/05/1438760706_9279.jpg",
"201508/05/1438760704_2341.jpg",
"201508/05/1438760704_5707.jpg",
"201508/05/1438760685_5091.jpg",
"201508/05/1438760685_4444.jpg",
"201508/05/1438760684_8827.jpg",
"201508/05/1438760683_3691.jpg",
"201508/05/1438760683_7315.jpg",
"201508/05/1438760663_7318.jpg",
"201508/05/1438760662_3454.jpg",
"201508/05/1438760662_5113.jpg",
"201508/05/1438760661_3305.jpg",
"201508/05/1438760661_7416.jpg",
"201508/05/1438760589_2946.jpg",
"201508/05/1438760589_1100.jpg",
"201508/05/1438760588_8297.jpg",
"201508/05/1438760587_2575.jpg",
"201508/05/1438760587_8906.jpg",
"201508/05/1438760550_2875.jpg",
"201508/05/1438760550_9517.jpg",
"201508/05/1438760549_7093.jpg",
"201508/05/1438760549_1352.jpg",
"201508/05/1438760548_2780.jpg",
"201508/05/1438760531_1776.jpg",
"201508/05/1438760531_1380.jpg",
"201508/05/1438760530_4944.jpg",
"201508/05/1438760530_5750.jpg",
"201508/05/1438760529_3289.jpg",
"201508/05/1438760500_7871.jpg",
"201508/05/1438760500_6063.jpg",
"201508/05/1438760499_6304.jpeg",
"201508/05/1438760499_5081.jpg",
"201508/05/1438760498_7007.jpg",
"201508/05/1438760478_3128.jpg",
"201508/05/1438760478_6766.jpg",
"201508/05/1438760477_1358.jpg",
"201508/05/1438760477_3540.jpg",
"201508/05/1438760476_1240.jpg",
"201508/05/1438760446_7993.jpg",
"201508/05/1438760446_3641.jpg",
"201508/05/1438760445_3283.jpg",
"201508/05/1438760444_8623.jpg",
"201508/05/1438760444_6822.jpg",
"201508/05/1438760422_2224.jpg",
"201508/05/1438760421_2824.jpg",
"201508/05/1438760420_2660.jpg",
"201508/05/1438760420_7188.jpg",
"201508/05/1438760419_4123.jpg",
};
}设置好了图片源之后,我们需要一个ListView来展示所有的图片。打开或修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</ListView>
</LinearLayout>很简单,只是在LinearLayout中写了一个ListView而已。接着我们要定义ListView中每一个子View的布局,新建一个image_item.xml布局,加入如下代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="120dp"
android:src="@drawable/empty_photo"
android:scaleType="fitXY"/>
</LinearLayout>仍然很简单,image_item.xml布局中只有一个ImageView控件,就是用它来显示图片的,控件在默认情况下会显示一张empty_photo。这样我们就把所有的布局文件都写好了。
接下来新建ImageAdapter做为ListView的适配器,代码如下所示:
public class ImageAdapter extends ArrayAdapter<String> {
/**
* 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
*/
private LruCache<String, BitmapDrawable> mMemoryCache;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, BitmapDrawable>(cacheSize) {
@Override
protected int sizeOf(String key, BitmapDrawable drawable) {
return drawable.getBitmap().getByteCount();
}
};
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String url = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
} else {
view = convertView;
}
ImageView image = (ImageView) view.findViewById(R.id.image);
BitmapDrawable drawable = getBitmapFromMemoryCache(url);
if (drawable != null) {
image.setImageDrawable(drawable);
} else {
BitmapWorkerTask task = new BitmapWorkerTask(image);
task.execute(url);
}
return view;
}
/**
* 将一张图片存储到LruCache中。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @param drawable
* LruCache的键,这里传入从网络上下载的BitmapDrawable对象。
*/
public void addBitmapToMemoryCache(String key, BitmapDrawable drawable) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, drawable);
}
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的BitmapDrawable对象,或者null。
*/
public BitmapDrawable getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
/**
* 异步下载图片的任务。
*
* @author guolin
*/
class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {
private ImageView mImageView;
public BitmapWorkerTask(ImageView imageView) {
mImageView = imageView;
}
@Override
protected BitmapDrawable doInBackground(String... params) {
String imageUrl = params[0];
// 在后台开始下载图片
Bitmap bitmap = downloadBitmap(imageUrl);
BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
addBitmapToMemoryCache(imageUrl, drawable);
return drawable;
}
@Override
protected void onPostExecute(BitmapDrawable drawable) {
if (mImageView != null && drawable != null) {
mImageView.setImageDrawable(drawable);
}
}
/**
* 建立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;
}
}
}
ImageAdapter中的代码还算是比较简单的,在getView()方法中首先根据当前的位置获取到图片的URL地址,然后使用inflate()方法加载image_item.xml这个布局,并获取到ImageView控件的实例,接下来开启了一个BitmapWorkerTask异步任务来从网络上加载图片,最终将加载好的图片设置到ImageView上面。注意这里为了防止图片占用过多的内存,我们还是使用了LruCache技术来进行内存控制,对这个技术不熟悉的朋友可以参考我之前的一篇文章 Android高效加载大图、多图解决方案,有效避免程序OOM 。
最后,程序主界面的代码就非常简单了,修改MainActivity中的代码,如下所示:
public class MainActivity extends Activity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list_view);
ImageAdapter adapter = new ImageAdapter(this, 0, Images.imageThumbUrls);
listView.setAdapter(adapter);
}
}这就是整个程序所有的代码了,记得还需要在AndroidManifest.xml中添加INTERNET权限。
那么目前程序的思路其实是很简单的,我们在ListView的getView()方法中开启异步请求,从网络上获取图片,当图片获取成功就后就将图片显示到ImageView上面。看起来没什么问题对吗?那么现在我们就来运行一下程序看一看效果吧。
恩?怎么会这个样子,当滑动ListView的时候,图片竟然会自动变来变去,而且图片显示的位置也不正确,简直快乱成一锅粥了!可是我们所有的逻辑都很简单呀,怎么会导致出现这种图片自动变来变去的情况?很遗憾,这是由于Listview内部的工作机制所导致的,如果你对Listview的工作机制不了解,那么就会很难理解这种现象,不过好在上篇文章中我已经讲解过ListView的工作原理了,因此下面就让我们一起分析一下这个问题出现的原因。
原因分析
上篇文章中已经提到了,ListView之所以能够实现加载成百上千条数据都不会OOM,最主要在于它内部优秀的实现机制。虽然作为普通的使用者,我们大可不必关心ListView内部到底是怎么实现的,但是当你了解了它的内部原理之后,很多之前难以解释的问题都变得有理有据了。
ListView在借助RecycleBin机制的帮助下,实现了一个生产者和消费者的模式,不管有任意多条数据需要显示,ListView中的子View其实来来回回就那么几个,移出屏幕的子View会很快被移入屏幕的数据重新利用起来,原理示意图如下所示:
那么这里我们就可以思考一下了,目前数据源当中大概有60个图片的URL地址,而根据ListView的工作原理,显然不可能为每张图片都单独分配一个ImageView控件,ImageView控件的个数其实就比一屏能显示的图片数量稍微多一点而已,移出屏幕的ImageView控件会进入到RecycleBin当中,而新进入屏幕的元素则会从RecycleBin中获取ImageView控件。
那么,每当有新的元素进入界面时就会回调getView()方法,而在getView()方法中会开启异步请求从网络上获取图片,注意网络操作都是比较耗时的,也就是说当我们快速滑动ListView的时候就很有可能出现这样一种情况,某一个位置上的元素进入屏幕后开始从网络上请求图片,但是还没等图片下载完成,它就又被移出了屏幕。这种情况下会产生什么样的现象呢?根据ListView的工作原理,被移出屏幕的控件将会很快被新进入屏幕的元素重新利用起来,而如果在这个时候刚好前面发起的图片请求有了响应,就会将刚才位置上的图片显示到当前位置上,因为虽然它们位置不同,但都是共用的同一个ImageView实例,这样就出现了图片乱序的情况。
但是还没完,新进入屏幕的元素它也会发起一条网络请求来获取当前位置的图片,等到图片下载完的时候会设置到同样的ImageView上面,因此就会出现先显示一张图片,然后又变成了另外一张图片的情况,那么刚才我们看到的图片会自动变来变去的情况也就得到了解释。
问题原因已经分析出来了,但是这个问题该怎么解决呢?说实话,ListView异步加载图片的问题并没有什么标准的解决方案,很多人都有自己的一套解决思路,这里我准备给大家讲解三种比较经典的解决办法,大家通过任何一种都可以解决这个问题,但是我们每多学习一种思路,水平就能够更进一步的提高。
解决方案一 使用findViewWithTag
findViewWithTag算是一种比较简单易懂的解决方案,其实早在 Android照片墙应用实现,再多的图片也不怕崩溃 这篇文章当中,我就采用了findViewWithTag来避免图片出现乱序的情况。那么这里我们先来看看怎么通过修改代码把这个问题解决掉,然后再研究一下findViewWithTag的工作原理。
使用findViewWithTag并不需要修改太多的代码,只需要改动ImageAdapter这一个类就可以了,如下所示:
public class ImageAdapter extends ArrayAdapter<String> {
private ListView mListView;
......
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (mListView == null) {
mListView = (ListView) parent;
}
String url = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
} else {
view = convertView;
}
ImageView image = (ImageView) view.findViewById(R.id.image);
image.setImageResource(R.drawable.empty_photo);
image.setTag(url);
BitmapDrawable drawable = getBitmapFromMemoryCache(url);
if (drawable != null) {
image.setImageDrawable(drawable);
} else {
BitmapWorkerTask task = new BitmapWorkerTask();
task.execute(url);
}
return view;
}
......
/**
* 异步下载图片的任务。
*
* @author guolin
*/
class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {
String imageUrl;
@Override
protected BitmapDrawable doInBackground(String... params) {
imageUrl = params[0];
// 在后台开始下载图片
Bitmap bitmap = downloadBitmap(imageUrl);
BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
addBitmapToMemoryCache(imageUrl, drawable);
return drawable;
}
@Override
protected void onPostExecute(BitmapDrawable drawable) {
ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);
if (imageView != null && drawable != null) {
imageView.setImageDrawable(drawable);
}
}
......
}
}
改动的地方就只有这么多,那么我们来分析一下。由于使用findViewWithTag必须要有ListView的实例才行,那么我们在Adapter中怎样才能拿到ListView的实例呢?其实如果你仔细通读了上一篇文章就能知道,getView()方法中传入的第三个参数其实就是ListView的实例,那么这里我们定义一个全局变量mListView,然后在getView()方法中判断它是否为空,如果为空就把parent这个参数赋值给它。
另外在getView()方法中我们还做了一个操作,就是调用了ImageView的setTag()方法,并把当前位置图片的URL地址作为参数传了进去,这个是为后续的findViewWithTag()方法做准备。
最后,我们修改了BitmapWorkerTask的构造函数,这里不再通过构造函数把ImageView的实例传进去了,而是在onPostExecute()方法当中通过ListView的findVIewWithTag()方法来去获取ImageView控件的实例。获取到控件实例后判断下是否为空,如果不为空就让图片显示到控件上。
这里我们可以尝试分析一下findViewWithTag的工作原理,其实顾名思义,这个方法就是通过Tag的名字来获取具备该Tag名的控件,我们先要调用控件的setTag()方法来给控件设置一个Tag,然后再调用ListView的findViewWithTag()方法使用相同的Tag名来找回控件。
那么为什么用了findViewWithTag()方法之后,图片就不会再出现乱序情况了呢?其实原因很简单,由于ListView中的ImageView控件都是重用的,移出屏幕的控件很快会被进入屏幕的图片重新利用起来,那么getView()方法就会再次得到执行,而在getView()方法中会为这个ImageView控件设置新的Tag,这样老的Tag就会被覆盖掉,于是这时再调用findVIewWithTag()方法并传入老的Tag,就只能得到null了,而我们判断只有ImageView不等于null的时候才会设置图片,这样图片乱序的问题也就不存在了。
这是第一种解决方案。
解决方案二 使用弱引用关联
虽然这里我给这种解决方案起名叫弱引用关联,但实际上弱引用只是辅助手段而已,最主要的还是关联,这种解决方案的本质是要让ImageView和BitmapWorkerTask之间建立一个双向关联,互相持有对方的引用,再通过适当的逻辑判断来解决图片乱序问题,然后为了防止出现内存泄漏的情况,双向关联要使用弱引用的方式建立。相比于第一种解决方案,第二种解决方案要明显复杂不少,但在性能和效率方面都会有更好的表现。
我们仍然只需要改动ImageAdapter中的代码,但这次改动的地方比较多,所以我就把ImageAdapter中的全部代码都贴出来了,如下所示:
public class ImageAdapter extends ArrayAdapter<String> {
private ListView mListView;
private Bitmap mLoadingBitmap;
/**
* 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
*/
private LruCache<String, BitmapDrawable> mMemoryCache;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
mLoadingBitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.empty_photo);
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, BitmapDrawable>(cacheSize) {
@Override
protected int sizeOf(String key, BitmapDrawable drawable) {
return drawable.getBitmap().getByteCount();
}
};
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (mListView == null) {
mListView = (ListView) parent;
}
String url = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
} else {
view = convertView;
}
ImageView image = (ImageView) view.findViewById(R.id.image);
BitmapDrawable drawable = getBitmapFromMemoryCache(url);
if (drawable != null) {
image.setImageDrawable(drawable);
} else if (cancelPotentialWork(url, image)) {
BitmapWorkerTask task = new BitmapWorkerTask(image);
AsyncDrawable asyncDrawable = new AsyncDrawable(getContext()
.getResources(), mLoadingBitmap, task);
image.setImageDrawable(asyncDrawable);
task.execute(url);
}
return view;
}
/**
* 自定义的一个Drawable,让这个Drawable持有BitmapWorkerTask的弱引用。
*/
class AsyncDrawable extends BitmapDrawable {
private WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(
bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
/**
* 获取传入的ImageView它所对应的BitmapWorkerTask。
*/
private BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
/**
* 取消掉后台的潜在任务,当认为当前ImageView存在着一个另外图片请求任务时
* ,则把它取消掉并返回true,否则返回false。
*/
public boolean cancelPotentialWork(String url, ImageView imageView) {
BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
String imageUrl = bitmapWorkerTask.imageUrl;
if (imageUrl == null || !imageUrl.equals(url)) {
bitmapWorkerTask.cancel(true);
} else {
return false;
}
}
return true;
}
/**
* 将一张图片存储到LruCache中。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @param drawable
* LruCache的键,这里传入从网络上下载的BitmapDrawable对象。
*/
public void addBitmapToMemoryCache(String key, BitmapDrawable drawable) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, drawable);
}
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的BitmapDrawable对象,或者null。
*/
public BitmapDrawable getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
/**
* 异步下载图片的任务。
*
* @author guolin
*/
class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {
String imageUrl;
private WeakReference<ImageView> imageViewReference;
public BitmapWorkerTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
protected BitmapDrawable doInBackground(String... params) {
imageUrl = params[0];
// 在后台开始下载图片
Bitmap bitmap = downloadBitmap(imageUrl);
BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
addBitmapToMemoryCache(imageUrl, drawable);
return drawable;
}
@Override
protected void onPostExecute(BitmapDrawable drawable) {
ImageView imageView = getAttachedImageView();
if (imageView != null && drawable != null) {
imageView.setImageDrawable(drawable);
}
}
/**
* 获取当前BitmapWorkerTask所关联的ImageView。
*/
private ImageView getAttachedImageView() {
ImageView imageView = imageViewReference.get();
BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask) {
return imageView;
}
return null;
}
/**
* 建立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;
}
}
}那么我们一点点开始解析。首先刚才说到的,ImageView和BitmapWorkerTask之间要建立一个双向的弱引用关联,上述代码中已经建立好了。ImageView中可以获取到它所对应的BitmapWorkerTask,而BitmapWorkerTask也可以获取到它所对应的ImageView。
下面来看一下这个双向弱引用关联是怎么建立的。BitmapWorkerTask指向ImageView的弱引用关联比较简单,就是在BitmapWorkerTask中加入一个构造函数,并在构造函数中要求传入ImageView这个参数。不过我们不再直接持有ImageView的引用,而是使用WeakReference对ImageView进行了一层包装,这样就OK了。
但是ImageView指向BitmapWorkerTask的弱引用关联就没这么容易了,因为我们很难将BitmapWorkerTask的一个弱引用直接设置到ImageView当中。这该怎么办呢?这里使用了一个比较巧的方法,就是借助自定义Drawable的方式来实现。可以看到,我们自定义了一个AsyncDrawable类并让它继承自BitmapDrawable,然后重写了AsyncDrawable的构造函数,在构造函数中要求把BitmapWorkerTask传入,然后在这里给它包装了一层弱引用。那么现在AsyncDrawable指向BitmapWorkerTask的关联已经有了,但是ImageView指向BitmapWorkerTask的关联还不存在,怎么办呢?很简单,让ImageView和AsyncDrawable再关联一下就可以了。可以看到,在getView()方法当中,我们调用了ImageView的setImageDrawable()方法把AsyncDrawable设置了进去,那么ImageView就可以通过getDrawable()方法获取到和它关联的AsyncDrawable,然后再借助AsyncDrawable就可以获取到BitmapWorkerTask了。这样ImageView指向BitmapWorkerTask的弱引用关联也成功建立。
现在双向弱引用的关联已经建立好了,接下来就是逻辑判断的工作了。那么怎样通过逻辑判断来避免图片出现乱序的情况呢?这里我们引入了两个方法,一个是getBitmapWorkerTask()方法,这个方法可以根据传入的ImageView来获取到它对应的BitmapWorkerTask,内部的逻辑就是先获取ImageView对应的AsyncDrawable,再获取AsyncDrawable对应的BitmapWorkerTask。另一个是getAttachedImageView()方法,这个方法会获取当前BitmapWorkerTask所关联的ImageView,然后调用getBitmapWorkerTask()方法来获取该ImageView所对应的BitmapWorkerTask,最后判断,如果获取到的BitmapWorkerTask等于this,也就是当前的BitmapWorkerTask,那么就将ImageView返回,否则就返回null。最后,在onPostExecute()方法当中,只需要使用getAttachedImageView()方法获取到的ImageView来显示图片就可以了。
那么为什么做了这个逻辑判断之后,图片乱序的问题就可以得到解决呢?其实最主要的奥秘就是在getAttachedImageView()方法当中,它会使用当前BitmapWorkerTask所关联的ImageView来反向获取这个ImageView所关联的BitmapWorkerTask,然后用这两个BitmapWorkerTask做对比,如果发现是同一个BitmapWorkerTask才会返回ImageView,否则就返回null。那么什么情况下这两个BitmapWorkerTask才会不同呢?比如说某个图片被移出了屏幕,它的ImageView被另外一个新进入屏幕的图片重用了,那么就会给这个ImageView关联一个新的BitmapWorkerTask,这种情况下,上一个BitmapWorkerTask和新的BitmapWorkerTask肯定就不相等了,这时getAttachedImageView()方法会返回null,而我们又判断ImageView等于null的话是不会设置图片的,因此就不会出现图片乱序的情况了。
除此之外还有另外一个方法非常值得大家注意,就是cancelPotentialWork()方法,这个方法可以大大提高整个ListView图片加载的工作效率。这个方法接收两个参数,一个图片的url,一个ImageView。看一下它的内部逻辑,首先它也是调用了getBitmapWorkerTask()方法来获取传入的ImageView所对应的BitmapWorkerTask,接下来拿BitmapWorkerTask中的imageUrl和传入的url做比较,如果两个url不等的话就调用BitmapWorkerTask的cancel()方法,然后返回true,如果两个url相等的话就返回false。
那么这段逻辑是什么意思呢?其实并不复杂,两个url做比对时,如果发现是相同的,说明请求的是同一张图片,那么直接返回false,这样就不会再去启动BitmapWorkerTask来请求图片,而如果两个url不相同,说明这个ImageView被另外一张图片重新利用了,这个时候就调用了BitmapWorkerTask的cancel()方法把之前的请求取消掉,然后重新启动BitmapWorkerTask来去请求新图片。有了这个操作保护之后,就可以把一些已经移出屏幕的无效的图片请求过滤掉,从而整体提升ListView加载图片的工作效率。
这是第二种解决方案。
解决方案三 使用NetworkImageView
前面两种解决方案都需要我们自己去做额外的逻辑处理,因为ImageView本身是不能自动解决这个问题的,但是如果我们使用NetworkImageView这个控件的话就非常简单了,它自身就已经考虑到了这个问题,我们直接使用它就可以了,不用做任何额外的处理也不会出现图片乱序的情况。
NetworkImageView是Volley当中提供的控件,对于这个控件我之前专门写过一篇博客来讲解,还不熟悉这个控件的朋友可以先去阅读 Android Volley完全解析(二),使用Volley加载网络图片 。
下面我们看一下如何用NetworkImageView来解决这个问题,首先需要修改一下image_item.xml文件,因为我们已经不再使用ImageView控件了,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="120dp"
android:src="@drawable/empty_photo"
android:scaleType="fitXY"/>
</LinearLayout>很简单,只是把ImageView替换成了NetworkImageView。然后修改ImageAdapter中的代码,如下所示:
public class ImageAdapter extends ArrayAdapter<String> {
ImageLoader mImageLoader;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
RequestQueue queue = Volley.newRequestQueue(context);
mImageLoader = new ImageLoader(queue, new BitmapCache());
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String url = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
} else {
view = convertView;
}
NetworkImageView image = (NetworkImageView) view.findViewById(R.id.image);
image.setDefaultImageResId(R.drawable.empty_photo);
image.setErrorImageResId(R.drawable.empty_photo);
image.setImageUrl(url, mImageLoader);
return view;
}
/**
* 使用LruCache来缓存图片
*/
public class BitmapCache implements ImageCache {
private LruCache<String, Bitmap> mCache;
public BitmapCache() {
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
@Override
public Bitmap getBitmap(String url) {
return mCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
}
}没错,就是这么简单,一共60行左右的代码搞定一切!我们不需要自己再去写一个BitmapWorkerTask来处理图片的下载和显示,也不需要自己再去管理LruCache的逻辑,一切NetworkImageView都帮我们做好了。至于上面的代码我就不再做解释了,因为实在是太简单了。
那么当然了,虽然现在没有做任何额外的逻辑处理,但是也根本不会出现图片乱序的情况,因为NetworkImageView在内部都帮我们处理掉了。不过大家可能都很好奇,NetworkImageView到底是如何做到的呢?那么就让我们来分析一下它的源码吧。
NetworkImageView中开始加载图片的代码是setImageUrl()方法,源码分析就从这里开始吧,如下所示:
/**
* Sets URL of the image that should be loaded into this view. Note that calling this will
* immediately either set the cached image (if available) or the default image specified by
* {@link NetworkImageView#setDefaultImageResId(int)} on the view.
*
* NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} and
* {@link NetworkImageView#setErrorImageResId(int)} should be called prior to calling
* this function.
*
* @param url The URL that should be loaded into this ImageView.
* @param imageLoader ImageLoader that will be used to make the request.
*/
public void setImageUrl(String url, ImageLoader imageLoader) {
mUrl = url;
mImageLoader = imageLoader;
// The URL has potentially changed. See if we need to load it.
loadImageIfNecessary(false);
}
setImageUrl()方法中并没有几行代码,让人值得留意的是loadImageIfNecessary()这个方法,看上去具体加载图片的逻辑就是在这里进行的,那么我们就跟进去瞧一瞧:
/**
* Loads the image for the view if it isn't already loaded.
* @param isInLayoutPass True if this was invoked from a layout pass, false otherwise.
*/
private void loadImageIfNecessary(final boolean isInLayoutPass) {
int width = getWidth();
int height = getHeight();
boolean isFullyWrapContent = getLayoutParams() != null
&& getLayoutParams().height == LayoutParams.WRAP_CONTENT
&& getLayoutParams().width == LayoutParams.WRAP_CONTENT;
// if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content
// view, hold off on loading the image.
if (width == 0 && height == 0 && !isFullyWrapContent) {
return;
}
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
// currently loaded image.
if (TextUtils.isEmpty(mUrl)) {
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
setDefaultImageOrNull();
return;
}
// if there was an old request in this view, check if it needs to be canceled.
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
if (mImageContainer.getRequestUrl().equals(mUrl)) {
// if the request is from the same URL, return.
return;
} else {
// if there is a pre-existing request, cancel it if it's fetching a different URL.
mImageContainer.cancelRequest();
setDefaultImageOrNull();
}
}
// The pre-existing content of this view didn't match the current URL. Load the new image
// from the network.
ImageContainer newContainer = mImageLoader.get(mUrl,
new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (mErrorImageId != 0) {
setImageResource(mErrorImageId);
}
}
@Override
public void onResponse(final ImageContainer response, boolean isImmediate) {
// If this was an immediate response that was delivered inside of a layout
// pass do not set the image immediately as it will trigger a requestLayout
// inside of a layout. Instead, defer setting the image by posting back to
// the main thread.
if (isImmediate && isInLayoutPass) {
post(new Runnable() {
@Override
public void run() {
onResponse(response, false);
}
});
return;
}
if (response.getBitmap() != null) {
setImageBitmap(response.getBitmap());
} else if (mDefaultImageId != 0) {
setImageResource(mDefaultImageId);
}
}
});
// update the ImageContainer to be the new bitmap container.
mImageContainer = newContainer;
}这里在第43行调用了ImageLoader的get()方法来去请求图片,get()方法会返回一个ImageContainer对象,这个对象封装了图片请求地址、Bitmap等数据,每个NetworkImageView中都会对应一个ImageContainer。然后在第31行我们看到,这里从ImageContainer对象中获取封装的图片请求地址,并拿来和当前的请求地址做对比,如果相同的话说明这是一条重复的请求,就直接return掉,如果不同的话就调用cancelRequest()方法将请求取消掉,然后将图片设置为默认图片并重新发起请求。
那么解决图片乱序最核心的逻辑就在这里了,其实NetworkImageView的解决思路还是比较简单的,就是如果这个控件已经被移出了屏幕且被重新利用了,那么就把之前的请求取消掉,仅此而已。
而我们都知道,在通常情况下,仅仅这么处理可能是解决不了问题的,因为Java的线程无法保证一定可以中断,即使像第二种解决方案里使用的BitmapWorkerTask的cancel()方法,也不能保证一定可以把请求取消掉,所以还需要使用弱引用关联的处理方式。但是在NetworkImageView当中就可以这么任性,仅仅调用cancelRequest()方法把请求取消掉就可以了,这主要是得益于Volley的出色设计。由于Volley在网络方面的封装非常优秀,它可以保证,只要是取消掉的请求,就绝对不会进行回调,既然不会回调,那么也就不会回到NetworkImageView当中,自然也就不会出现乱序的情况了。
需要注意的是,Volley只是保证取消掉的请求不会进行回调而已,但并没有说可以中断任何请求。由此可见即使是Volley也无法做到中断一个正在执行的线程,如果有一个线程正在执行,Volley只会保证在它执行完之后不会进行回调,但在调用者看来,就好像是这个请求就被取消掉了一样。
那么这里我们只分析与图片乱序相关部分的源码,如果你想了解关于Volley更多的源码,可以参考我之前的一篇文章 Android Volley完全解析(四),带你从源码的角度理解Volley 。
这是第三种解决方案。
好了,关于ListView异步加载图片乱序的问题今天我们就讨论到这里,如果你把三种解决方案都理解清楚的话,那么对于这个问题研究的就算比较透彻了。下一篇文章仍然是ListView主题,我们将学习一下如何对ListView控件进行一些功能扩展,敬请期待。
centos下网站不能被局域网其他电脑访问的原因非常的简单,下面我们就一起来看看问题的解决办法,希望例子能够帮助到各位。
出现本错误的一般有两种情况
第一种情况:导包错误--检查import,找到这个:
删除之,再重新导入含有包名的R文件。
第二种情况:本情况应该更为多见,一般为布局文件中有错误,而无法生存R文件,可以检查一下:
你会发现果真没有生成R文件,这时你需要解决的就是查找布局文件中的错误,改正错误,生成R文件之后,本错误就会消失啦!
相关文章
- 下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
用js的document.write输出的广告无阻塞加载的方法
一、广告代码分析很多第三方的广告系统都是使用document.write来加载广告,如下面的一个javascript的广告链接。复制代码 代码如下:<script type="text/javascript" src="http://gg.5173.com/adpolestar/5173/;ap=2EBE5...2014-06-07- 当页面打开时我们需要执行一些操作,这个时候如果我们选择使用jquery的话,需要重写他的3中方法,自我感觉没什么区 别,看个人喜好了,第二种感觉比较简单明了: 第一种: 复制代码 代码如下: <script type="text/javas...2014-06-07
PHP传值到不同页面的三种常见方式及php和html之间传值问题
在项目开发中经常见到不同页面之间传值在web工作中,本篇文章给大家列出了三种常见的方式。接触PHP也有几个月了,本文总结一下这段日子中,在编程过程里常用的3种不同页面传值方法,希望可以给大家参考。有什么意见也希望大...2015-11-24Android开发中findViewById()函数用法与简化
findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20- 如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
- 这篇文章主要介绍了解决IDEA插件市场Plugins无法加载的问题,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-10-21
- 夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
- 直接为大家介绍制作过程,希望大家可以喜欢。HTML结构该页面切换特效的HTML结构使用一个<main>元素来作为页面的包裹元素,div.cd-cover-layer用于制作页面切换时的遮罩层,div.cd-loading-bar是进行ajax加载时的loading进...2015-10-30
- 为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
- 如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
- 深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
- 下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
- java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
- TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
- 这篇文章主要介绍了解决vue刷新页面以后丢失store的数据问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-12
android.os.BinderProxy cannot be cast to com解决办法
本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20- 这篇文章主要介绍了Angular性能优化之第三方组件和懒加载技术,对性能优化感兴趣的同学,可以参考下...2021-05-11
- 本文介绍了如何延迟javascript代码的加载,加快网页的访问速度。 当一个网站有很多js代码要加载,js代码放置的位置在一定程度上将会影像网页的加载速度,为了让我们的网页加载速度更快,本文总结了一下几个注意点...2013-10-13
- 对于乱码这个问题php开发者几乎都会有碰到过,我们下面主要是介绍了php文件乱码和页面乱码。PHP页面转UTF-8编码问题 1.在代码开始出加入一行: header("Content-Type: text/html;charset=utf-8"); 2.PHP文件编码问题...2015-10-21