ListView异步加载图片–图片缓存和错位问题解决方案

最后更新于:2022-04-01 11:18:23

问题1: 加载太多的图片很容易造成OOM异常。 ## 一、图片缓存 **方法1:使用二级缓存 ->自己维护一个缓存区** 只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象。 所以可以这么做:map里面的键是用来放图片地址的,既可以是网络上的图片地址,也可以SDcard上的图片地址, map里面的值里面放的是持有软引用的Bitmap. ~~~ private Map<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>(); ~~~ 每次为ImageView设置图片时,先去map中寻找,存在就直接用,不存在则从网络加载,下载好后存到map中。 **方法2:使用一级缓存,LruCache** 2.1 LRU算法简介: LRU是Least Recently Used 近期最少使用算法。 内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。 什么是LRU算法? LRU是Least Recently Used的缩写,即最少使用页面置换算法,是为虚拟页式存储管理服务的。 2.2 在Android中,有一个叫做LruCache类专门用来做图片缓存处理的。 它有一个特点,当缓存的图片达到了预先设定的值的时候,那么近期使用次数最少的图片就会被回收掉。 步骤: 1)要先设置缓存图片的内存大小,这里设置为手机内存的1/10 手机内存的获取方式:int MAXMEMONRY = (int) (Runtime.getRuntime() .maxMemory() / 10); 2)LruCache里面的键值对分别是URL和对应的图片 3)使用时和上面类似,每次给item中的ImageView设置图片时先从缓存中查找,存在直接用,不存在则从网络加载,加载完成存到缓存中。 ~~~ private LruCache<String, BitmapDrawable> mMemoryCache; public MyCache() { // 获取应用程序最大可用内存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 10;//大小为当前程序运行时内存的1/10 mMemoryCache = new LruCache<String, BitmapDrawable>(cacheSize) { @Override protected int sizeOf(String key, BitmapDrawable drawable) { return drawable.getBitmap().getByteCount(); } }; } ~~~ **方法3.使用三级缓存:利用外部存储(即文件系统来缓存下载的图片)** 只是把缓存放到了外部存储中,其他用法不变。 注意: **3.1根据实际情况决定在程序结束后要不要把缓存文件删除,如果程序中加载的图片更新速度很快(如新闻,更新速度很快),就需要在程序结束后清空缓存文件。反之,可以不用删除。** **方法4.一级三级缓存配合使用** 设置图片时先从一级缓存中取,存在则直接使用,不存在再去三级缓存中查找,存在直接使用,不存在再从网络加载, 注意: 4.1加载完成后,分别存到一级缓存和二级缓存。使用这种方案时,可以把一级缓存设置小一点。 4.2根据实际情况决定在程序结束后要不要把缓存文件删除,如果程序中加载的图片更新速度很快(如新闻,更新速度很快),就需要在程序结束后清空缓存文件。反之,可以不用删除。 ## 二、四种方案的比较 1.:方案1、2使用的内存是运行时内存,速度相对较快,方案3使用的是二级缓存,速度相对较慢,方案4是一级缓存和二级缓存的配合使用,速度适中。 2.:方案1是自己维护一个缓存,缓存中的图片何时被回收完全由GC决定,所以可能会占用很大内存;方案2是用Android提供的缓存,内部存储原理和方案1类似,但使用了LRU算法,优点能较智能的决定那些图片该回收,哪些不能回收,所以会把内存控制在一个较合理的状态。方案3使用二级缓存,速度虽然慢了点,但是外部存储基本不用担心内存不够用。方案4是一级缓存和二级缓存的结合使用,一级缓存加快使用速度,二级缓存增加使用内存。但操作较麻烦,保存需要两次,也浪费了一些内存。 **总结:**根据实际情况使用不同方案,方案1不推荐使用,方案2 3 4 各有优点. ## 三、listView异步加载图片错位的问题 **1.问题**:使用ListView异步加载图片时,如果不做处理,会出现图片图片错位的情况,特别是在网速不给力的情况下。 效果图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-23_571af4d5716cb.jpg "") **2.原因**:由ListVIew内部回收机制所致。 RecycleBin机制: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-23_571af4d60025a.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-01_565da63ce5c6f.jpg "") 分析:当重用 convertView 时,最初一屏显示 7 条记录, getView 被调用 7 次,创建了 7 个 convertView. 当 Item1 划出屏幕, Item8 进入屏幕时,这时没有为 Item8 创建新的 view 实例, Item8 复用的是 Item1 的 view 如果没有异步不会有任何问题,虽然 Item8 和 Item1 指向的是同一个 view,但滑到 Item8 时刷上了 Item8 的数据,这时 Item1 的数据和 Item8 是一样的,因为它们指向的是同一块内存, 但 Item1 已滚出了屏幕你看不见。当 Item1 再次可见时这块 view 又涮上了 Item1 的数据。 但当有异步下载时就有问题了,假设 Item1 的图片下载的比较慢,Item8 的图片下载的比较快,你滚上去 使 Item8 可见,这时 Item8 先显示它自己下载的图片没错,但等到 Item1 的图片也下载完时你发现 Item8 的图片也变成了 Item1 的图片,因为它们复用的是同一个 view。 如果 Item1 的图片下载的比 Item8 的图片快, Item1 先刷上自己下载的图片,这时你滑下去,Item8 的图片还没下载完, Item8 会先显示 Item1 的图片,因为它们是同一快内存,当 Item8 自己的图片下载完后 Item8 的图片又刷成 了自己的,你再滑上去使 Item1 可见, Item1 的图片也会和 Item8 的图片是一样的, 因为它们指向的是同一块内存。 ## 四、解决方案 **1.给ImageView添加tag** 1)在getView()方法中得到一个Imageview时,添加一个tag,tag一般是一个url 2)在异步任务加载图片成功时,通过listView的findViewWithTag(tag)方法得到一个imageview,然后设置图片 ~~~ @Override public View getView( int position, View convertView, ViewGroup parent) { if (listView == null) listView = (ListView) parent; View view = null; ImageView imageView = null; if (convertView != null) view = convertView; else { view = LayoutInflater.from(context).inflate(R.layout. image_item, parent, false); } imageView = (ImageView) view.findViewById(R.id.image ); imageView.setImageResource(R.drawable. ic_launcher); String url = data[position]; imageView.setTag(url); BitmapDrawable bitmapDrawable = cache.getBitmapFromMemoryCache(url); if (bitmapDrawable != null && imageView != null) { imageView.setImageDrawable(bitmapDrawable); } else { new ImageLoadAsyncTask(listView , context , cache).execute(url); } return view; } @Override protected void onPostExecute(BitmapDrawable drawable) { ImageView mImageView = (ImageView) listView.findViewWithTag(imageUrl ); if (mImageView != null && drawable != null) { mImageView.setImageDrawable(drawable); } } ~~~ **2、使用Volley的NetworkImageView** 1)在item布局文件中,用NetworkImageView替换ImageView 2)使用ImageLoader图片加载器 ~~~ <?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" android:orientation= "vertical" > <com.android.volley.toolbox.NetworkImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="120dp" android:scaleType="fitXY" android:src="@drawable/ic_launcher" /> </LinearLayout> ~~~ ~~~ @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, n ull) ; } else { view = convertView; } NetworkImageView image = (NetworkImageView) view .findViewById(R.id. image); image.setDefaultImageResId(R.drawable. ic_launcher); image.setErrorImageResId(R.drawable. ic_launcher); image.setImageUrl(url, mImageLoader); return view; } ~~~ **3、使用弱引用把asynctask和imageview相关联** 1)本质是要让ImageView和BitmapWorkerTask之间建立一个双向关联,互相持有对方的引用,再通过适当的逻辑判断来解决图片乱序问题,然后为了防止出现内存泄漏的情 况,双向关联要使用弱引用的方式建立。 1.自定义的一个 Drawable,让这个Drawable持有BitmapWorkerTask的弱引用。 ~~~ /** * 自定义的一个 Drawable,让这个 Drawable持有BitmapWorkerTask的弱引用。 */ public 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(); } } ~~~ 2.定义自己的异步加载任务,让这个任务拥有ImageView的弱引用 ~~~ /** * 异步下载图片的任务。 * * @author guolin */ class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> { String imageUrl; private BitmapCache bitmapCache; private Context context; private WeakReference<ImageView> imageViewReference; public BitmapWorkerTask(ImageView imageView, Context context, BitmapCache bitmapCache) { imageViewReference = new WeakReference<ImageView>(imageView); this.context = context; this.bitmapCache = bitmapCache; } @Override protected BitmapDrawable doInBackground(String... params) { imageUrl = params[0]; // 在后台开始下载图片 Bitmap bitmap = downloadBitmap(imageUrl); BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap); bitmapCache.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 = BitmapWorkManager.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; } } ~~~ 3.使用 ~~~ @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 = mMemoryCache.getBitmapFromMemoryCache(url);// 从缓存中取对应url图片 if (drawable != null) { image.setImageDrawable(drawable); // cancelPotentialWork返回true代表当前ImageView正在加载另一张图片, // 所以要cancel上个任务,来执行新的任务 } else if (BitmapWorkManager.cancelPotentialWork(url, image)) { BitmapWorkerTask task = new BitmapWorkerTask(image, getContext(), mMemoryCache); AsyncDrawable asyncDrawable = new AsyncDrawable(getContext() .getResources(), mLoadingBitmap, task); image.setImageDrawable(asyncDrawable); task.execute(url); } return view; } ~~~ [源码](http://download.csdn.net/detail/u011102153/9172245) 注:Android中对于图片的加载有很多开源的框架,这里列举几个: 1.xUtils(中国人做的哦) [https://github.com/wyouflf/xUtils](https://github.com/wyouflf/xUtils) 2.Volley(谷歌,非常好用) [https://github.com/adamrocker/volley](https://github.com/adamrocker/volley) 3.Picasso(感觉没前两个好) [https://github.com/square/picasso](https://github.com/square/picasso) 4.Glide [https://github.com/bumptech/glide](https://github.com/bumptech/glide) 5.Universal ImageLoader 6.Fresco (Facebook,非常强大) [https://github.com/facebook/fresco](https://github.com/facebook/fresco)
';

ViewPager使用详解1

最后更新于:2022-04-01 11:18:20

## 一、简介 **1.特点:**可以左右滑动的控件,需要PagerAdapter配合使用,由v4包提供 类全名: android.support.v4.view.ViewPager **2.作用**:ViewPager作用主要是能使界面左右滑动。比如最常用的使用是做一个引导界面;多张图片的预览或自动变换的图片展示(如淘宝首页面上的广告);viewpager还可以结合fragment作为主界面框架(如微信主界面)。。。 **3.viewpager中的几个重要的方法** 1)setAdapter()设置ViewPager的适配器。 2)setOnPageChangeListener()设置页面改变事件监听器 3)setCurrentItem(int position) 显示第几页 4)setCurrentItem(int position,boolean smoothScroll) 显示第几页,是否执行滚动动画 5)setOffscreenPageLimit(int limit) 设置脱离屏幕的页面限制--最多同时加载的页面数,limit默认是1 -这个方法解释一下:默认情况下,viewpager在显示页面时,会加载当前显示页和它左右的页面,这也是为什么移动页面时可以显示下一页面一部分。如果想设置其他数,viewpager会加载对应值的页面,比如设置2,则当前显示页和它左两页和右两页都会加载。 ## 二、相关类介绍 **1.viewpager相关的几个重要的类** **1.1OnPageChangeListener:** –onPageScrollStateChanged(int state) 页面滚动发生或停止时 –onPageScrolled(int position, float offset, int offsetPixes) 滚动时 –onPageSelected(int position) 页面位置确定时 **1.2 PagerAdapter** –作用:主要配合ViewPager显示相关的View –用法: 1)创建类,并继承PagerAdatper 2) 必须实现的方法 –getCount() 获取View的数量 –instantiateItem(ViewGroup, int poistion) 实例化指定位置的View对象 –destroyItem(ViewGroup, int poistion, Object) 删除指定位置的View –isViewFromObject(View, Object) 判断当前的View是否为Object **1.3FragmentPagerAdaper** –作用:与PagerAdapter的功能相同,不过显示的View改为Fragment –FragmentStatePagerAdapter: 如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,应该使用FragmentStatePagerAdapter 保存当前界面,以及下一个界面和上一个界面(如果有),最多保存3个,其他会被销毁掉。 –FragmentPagerAdaper和FragmentStatePagerAdapter的区别: FragmentPagerAdaper销毁的只是视图,数据没有销毁,FragmentStatePagerAdapter则是全部销毁。拿fragment生命周期来说,使用FragmentPagerAdaper,当滑动页面时,被销毁的fragment会执行到onDestyoyView()但没有执行onDestroy()。而使用FragmentStatePagerAdapter时,会执行onDestroy()方法。后面实例会看到。 ## 三、使用 **1.使用步骤:** 1) 在布局文件中使用标签 ~~~ <android.support.v4.view.ViewPager/> ~~~ 2) 代码中增加显示的页面 3) 在Activity里实例化ViewPager组件,并设置它的Adapter 需要注意的是:根据不同的场景,选择不同的适配器父类,不结合fragment使用时使用继承PagerAdapter的适配器,结合Fragment使用时,根据需求,可以选择父类为FragmentPagerAdapter和FragmentStatePagerAdapter,这两个的区别在后面前面已经讲过了。 **2.案例一:用viewpager做一个欢迎界面** 先看效果图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717152a6d732.jpg "") **1) 布局文件** ~~~ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="${relativePackage}.${activityClass}" > <!-- 其显示的页面需要通过PagerAdapter适配器增加或移除 --> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- 导航布局 ,小点点 --> <LinearLayout android:id="@+id/ll_dots" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="10dp" android:gravity="center" android:orientation="horizontal" android:padding="10dp" > </LinearLayout> </FrameLayout> ~~~ **2) 然后看看几个主要的方法体:** **第一个是自定义的适配器**,各种方法都有注释。 ~~~ // 声明PageAdapter子类,用于管理viewpager中显示的控件 class WelComePageAdapter extends PagerAdapter { @Override public int getCount() { // TODO 返回页面的数量 return views.size(); } @Override public Object instantiateItem(ViewGroup container, int position) { // TODO 获取指定位置的View(UI),并增加到ViewPager中,同时作为当前页面的数据返回 Log.i("--", "instantiateItem--" + position); View view = views.get(position); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { // TODO 当前位置与VIewPager中显示页面的位置的间隔超出一页面,需要将当前位置的页面移除 Log.i("--", "destroyItem--" + position); container.removeView(views.get(position)); } @Override public boolean isViewFromObject(View view, Object obj) { // TODO 判断当前显示的页面的UI和数据对象是否一致 return view == obj; } } ~~~ **第二个是viewpager滑动监听事件**,这里自定义一个类看着清楚点,继承OnPageChangeListener,然后事项相应的方法,特别注意onPageScrolled和onPageSelected这两个方法,onPageScrolled在滑动时会一直回调,根据回调的三个参数,在这里可以进行相应的操作,比如微信那个,当你滑动时,相邻两个标签会有颜色的渐变,就在这里处理的。onPageSelected方法在滑动一个页面停止后回调。 ~~~ class WelcomPageChangeListner implements OnPageChangeListener { @Override public void onPageScrollStateChanged(int state) { Log.i("--", "onPageScrollStateChanged" + state); // TODO 页面滚动状态发生变化事件:开始滚动、停止滚动、正在设置页面 // ViewPager.SCROLL_STATE_DRAGGING 开始滚动 // ViewPager.SCROLL_STATE_IDLE 停止滚动 // ViewPager.SCROLL_STATE_SETTLING 正在设置页面,即将要停止,并且设置当前显示的页面 // setDot(position); // switch (state) { // case ViewPager.SCROLL_STATE_DRAGGING: // Log.i("--", "-SCROLL_STATE_DRAGGING-"); // break; // case ViewPager.SCROLL_STATE_IDLE: // Log.i("--", "-SCROLL_STATE_IDLE-"); // break; // case ViewPager.SCROLL_STATE_SETTLING: // Log.i("--", "-SCROLL_STATE_SETTLING-"); // break; // } } ~~~ ~~~ /** * 第一个参数:滚动页面开始的位置 <br> * 第二个参数:两个页面之间滚动的偏移量,范围:0-1<br> * 第三个页面:两个页面之间的滚动的像素偏移量<br> */ @Override public void onPageScrolled(int position, float offset, int offsetPixwls) { // TODO 从当前页面位置开始滚动事件 // Log.i("--", "-onPageScrolled-" + position + ",[" + offset + "]," // + "[" + offsetPixwls + "]"); } @Override public void onPageSelected(int position) { // TODO 指定位置的页面被选择 setDot(position); } } ~~~ **第三个是动态改变导航栏小点点的背景图。** ~~~ /** * 选择指定位置的导航图片为选择图片,之前选择的导航图片重置为未选择图片 * * @param position */ private void setDot(int position) { ImageView imageView = null; // 遍历导航布局张所有的子控件,判断子控件的位置是否未选择位置,若是则设置为选择图片 for (int i = 0; i < LlDot.getChildCount(); i++) { imageView = (ImageView) LlDot.getChildAt(i);// 获取布局中指定位置的子控件 if (i == position) { imageView.setImageResource(R.drawable.page_now); } else { imageView.setImageResource(R.drawable.page); } } } ~~~ 其他还有小点点的点击事件,具体看源代码。 **3.案例二:viewpager和view结合做一个主界面** 效果图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-23_571af4d4d8d7f.jpg "") 1)布局:最上面的导航栏是一个HorizonalScrollView,里面有一个LinearLayout(因为HorizonalScrollView里只能有一个子视图),然后在LinearLayout定义你想要的导航模块,TextView即可。 ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="${relativePackage}.${activityClass}" > <HorizontalScrollView android:id="@+id/hScrollView" android:layout_width="match_parent" android:layout_height="50dp" android:layout_marginBottom="2dp" android:scrollbars="none" > <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical" > <!-- 顶部模块的布局 --> <LinearLayout android:id="@+id/navLayout" android:layout_width="wrap_content" android:layout_height="45dp" android:orientation="horizontal" > <TextView android:id="@+id/tv_model1" android:layout_width="150dp" android:layout_height="match_parent" android:gravity="center" android:text="第一" android:textColor="#000" android:textSize="20sp" /> <TextView android:id="@+id/tv_model2" android:layout_width="150dp" android:layout_height="match_parent" android:gravity="center" android:text="第二" android:textColor="#000" android:textSize="20sp" /> <TextView android:id="@+id/tv_model3" android:layout_width="150dp" android:layout_height="match_parent" android:gravity="center" android:text="第三" android:textColor="#000" android:textSize="20sp" /> <TextView android:id="@+id/tv_model4" android:layout_width="150dp" android:layout_height="match_parent" android:gravity="center" android:text="第四" android:textColor="#000" android:textSize="20sp" /> <TextView android:id="@+id/tv_model5" android:layout_width="150dp" android:layout_height="match_parent" android:gravity="center" android:text="第五" android:textColor="#000" android:textSize="20sp" /> </LinearLayout> <!-- 指示器控件 --> <View android:id="@+id/view_nav" android:layout_width="150dp" android:layout_height="4dp" android:background="#0cf" /> <!-- 基准线 --> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#0cf" /> </LinearLayout> </HorizontalScrollView> <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_below="@id/hScrollView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> ~~~ **还需要几个fragment页,这里只展示一个,其他类似:** ~~~ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" android:background="#80f0"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="第一个页面" android:textSize="30sp" /> </RelativeLayout> ~~~ **2)代码中需要做的事情有:** 2).1 指示器的移动,也就是模块下那个粗线条的移动,viewpager本身就是可滑动的控件,这里需要指示器随着手指的滑动进行滑动 2).2选择导航模块的位置,将水平滚动到当前模块位置的中心点, 2).3模块点击事件,点击模块,显示相应的page 那么来一步步实现上面的工作: 2.1指示器的移动 要求是指示器随着手指的滑动,起始也就是动态的改变指示器的leftMargin ~~~ /** * 指示器移动 * @param position * @param offset */ private void navIndicateMove(int position, float offset){ int leftMargin = (int) (indicateParams.width * (position + offset)); indicateParams.leftMargin = leftMargin; navIndicate.setLayoutParams(indicateParams); } ~~~ 2.2选择导航模块的位置,将水平滚动到当前模块位置的中心点 因为整个导航模块都在一个HorizonalScrollView中,HorizonalScrollView中有两个方法可以指定的移动位置,一个是**hScrollView.scrollTo(x, y);//非平滑移动** **hScrollView.smoothScrollTo(x, y);//平滑移动** 所以根据选择的模块得到移动值即可。 ~~~ /** * 选择导航模块的位置,将水平滚动到当前模块位置的中心点 * * @param position */ private void selectNav(int position) { TextView modelTv = (TextView) navLayout.getChildAt(position); int left = modelTv.getLeft();// 获取当前控件的左边位置 // 怎么放到中间? // int scWidth = (getResources().getDisplayMetrics().widthPixels / 2); int offset = left - (getResources().getDisplayMetrics().widthPixels / 2) + modelTv.getWidth() / 2; hScrollView.smoothScrollTo(offset, 0);// 水平滚动到指定位置 //设置被选中的模块 for(int i = 0; i < navLayout.getChildCount(); i++){ modelTv = (TextView) navLayout.getChildAt(i); if(i == position){ modelTv.setTextColor(Color.argb(100, 255, 0, 0)); }else{ modelTv.setTextColor(Color.argb(255, 0, 0, 0)); } } } ~~~ **3.模块点击事件,点击模块,显示相应的page** 给每个模块添加点击事件,然后点击一个模块,只用调用viewPager的 viewPager.setCurrentItem(item);//非平滑滑动 viewPager.setCurrentItem(item, smoothScroll);//平滑滑动 ~~~ /** * 模块点击事件 */ private void modelTvClickEvent() { for(int i = 0; i < navLayout.getChildCount(); i++){ final TextView tv = (TextView) navLayout.getChildAt(i); tv.setTag(i); tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { int position = (Integer)tv.getTag(); // selectNav(position); // navIndicateMove(position, 0); viewPager.setCurrentItem(position, true); } }); } } ~~~ **这样,viewpager结合导航栏都可以同步滑动了。** **4.案例三:viewpager和fragment结合做一个主界面** 效果图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-23_571af4d52e586.jpg "") 结合fragment使用和结合view使用很相似,只是继承的适配器不同。 上面继承PagerAdapter,这里继承FragmentPagerAdapter或者FragmentStatePagerAdapter。 1)定义一个ListFragment 2)自定义适配器 3)初始化数据源 ListFragment的使用前面有一篇讲过,这里就不附代码了。 来看看自定义适配器:很简单就完事 ~~~ class InfoFragmentAdapter extends FragmentStatePagerAdapter{ public InfoFragmentAdapter(FragmentManager fm){ super(fm); } @Override public int getCount() { return fragments.size(); } @Override public Fragment getItem(int position) { //返回指定位置的碎片 return fragments.get(position); } } ~~~ **最后可以借这个案例看一看FragmentPagerAdapter和FragmentStatePagerAdapter区别:** ViewPager:默认创建自己和左右两页,当某个页面与显示的页面间放大点隔大于1,则销毁UI界面(注意,只是UI,里面的数据则不会销毁) FragmentPagerAdapter:管理fragment时销毁的是UI界面,(fragment生命周期只会执行到onDestroyView) FragmentStatePagerAdapter:管理fragment时,完全销毁(fragment生命周期会执行onDestroy) 只用把自定义适配器类的父类改一改,然后看fragment各生命周期 打印的日志即可看到区别,这里不做演示了。 现在,结合案例二和案例三就可以做一个现在很流行的主界面了。 [ 源码下载](http://download.csdn.net/detail/u011102153/9120819)
';

从源码角度看Handler原理

最后更新于:2022-04-01 11:18:18

在Android中,有一个规定就是除了主线程,其他线程不能操作UI视图,因为不这样做的话会出现线程不安全问题。但还有一个规定UI线程在执行一个操作如果5秒内没有响应就会包ANR错误。所以UI线程中不允许访问网络这样的耗时操作,那么问题来了,子线程执行耗时操作,比如从网络获取图片,但子线程不能更新UI,而主线程能更新UI,但不能去下载图片。这样handler消息处理机制就出现了。 ### 1. Handler是什么? handler是Android给我们提供用来更新UI的一套机制,也是一套消息处理的机制,我们可以发送消息,也可以通过它处理消息。 ### 2. 为什么要使用Handler? Android在设计时,就封装了一套消息消息创建、传递、处理机制,如果不遵循这样的机制就没办法更新UI,就会抛出异常。 ### 3. Android中为什么要设计只能通过Handler机制更新UI? 解决多线程并发问题。如果允许多线程更新UI会导致界面错乱,如果非要使用多线程并使用加锁机制更新UI又会导致性能下降,所以更新UI的操作全部交给主线程。 ### 4.那么handler机制是怎样的机制呢? 了解handler机制,首先需要清楚四个核心类: **Message:**对发送的消息的封装 **MessageQueue:**消息队列,存放所有的消息 **Looper:**可以循环读取消息(从MessageQueue中读取) **Handler:**处理消息,同时也是发送消息的 **具体来看看handler是怎么实现的。** 4.1使用1:主线程接收子线程发来的消息(下载图片为例) 1)在主线程中初始化handler对象: ~~~ private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { //处理子线程发送过来的message Bitmap bitmap = (Bitmap)msg.obj; imageView.setImageBitmap(bitmap); } }; ~~~ 2)然后子线程中下载好图片后发送图片给主线程: ~~~ new Thread(new Runnable() { @Override public void run() { HttpGet get = new HttpGet(path); HttpClient client = new DefaultHttpClient(); HttpResponse response = null; try { response = client.execute(get); if (response.getStatusLine().getStatusCode() == 200) { byte[] arr = EntityUtils.toByteArray(response .getEntity()); Bitmap bitmap = BitmapFactory.decodeByteArray(arr, 0, arr.length); // 下载完成时把图片发送给主线程 // 从MessageQueue中获取可用的Message对象,如果没有可用的Message对象则会创建一个新的Message对象 Message msg = Message.obtain(); // 把发送的图片封装到msg中 msg.obj = bitmap; // 使用Handler发送msg handler.sendMessage(msg);// 把msg发送给实例化handler的线程 } } catch (Exception e) { e.printStackTrace(); } } }).start(); ~~~ 消息的拦截: ~~~ /** * 拦截消息测试 * @author Administrator * */ public class ThirdActivity extends Activity { Handler handler = new Handler(new Callback() { @Override public boolean handleMessage(Message msg) { Log.i("--", "消息都要经过我这里"); /**返回false,消息会继续向下分发,返回true则拦截*/ return true; } }) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.i("--", "我才是处理消息的"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { handler.sendEmptyMessage(1); } }).start(); } } ~~~ 3)使用1有关handler机制的操作有: Handler handler = new Handler(); handleMessage(Message msg); Message msg = Message.obtain(); handler.sendMessage(msg); 那么现在来看一看它内部到底是怎样执行的。 ### 5.从源码一步一步分析handler原理 **5.1Handler handler = new Handler();** ~~~ public Handler() { this(null, false); } ~~~ **这个无参构造方法会调用两个参数的构造方法,注意上面的参数null和false,如下:** ~~~ public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } ~~~ **那来看看这个两个参数的构造方法都做了些什么。** FIND_POTENTIAL_LEAKS这个变量初始化为false private static final boolean FIND_POTENTIAL_LEAKS = false; **所以直接执行后面的,也就是这些:** ~~~ mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; ~~~ **这几句的操作就是得到一个Looper对象和一个MessageQueue对象,mLooper = Looper.myLooper();中Looper.myLooper()方法中是得到当前线程的Looper对象。那么当前线程是什么?在这里,因为我们是在主线程中实例化Handler对象,所以当前线程就是主线程,值得注意的是,主线程在创建时就会维护一个Looper对象和MessageQueue对象,所以这里得到的Looper对象和消息队列都是主线程的。** ~~~ public static Looper myLooper() { return sThreadLocal.get(); } ~~~ **mQueue = mLooper.mQueue;这句说明handler内部的MessageQueue和Looper的MessageQueue是一个。** **好,那么handler的初始化阶段先到这里。** **5.2 Message msg = Message.obtain()**; 再来看看这句做了哪些事, **注意:有时候我们会直接这么写Message msg = new Message();** ~~~ public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; sPoolSize--; return m; } } return new Message(); } ~~~ **这里的意思就是如果sPool不为空就返回sPool,否则就new一个新的Message对象。(sPool是回收放在消息池中的对象)** ~~~ public void recycle() { clearForRecycle(); synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } } ~~~ **5.3 handler.sendMessage(msg);** 关键的一句终于到了,来看看这个又做了什么。 **第一步:** ~~~ public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } ~~~ **第二步:** ~~~ public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } ~~~ **第三步:** ~~~ public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } ~~~ **第四步:** ~~~ private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } ~~~ 到第四步算是找到能办事的了,看这里都办了哪些事。 **1) msg.target = this;可以查看Message源码target就是Handler类型的,这里把msg的target指向this,也就是这个发送消息的handler。** **2)queue.enqueueMessage(msg, uptimeMillis);调用queue的enqueueMessage方法,去MessageQueue类中看看。** ~~~ final boolean enqueueMessage(Message msg, long when) { if (msg.isInUse()) { throw new AndroidRuntimeException(msg + " This message is already in use."); } if (msg.target == null) { throw new AndroidRuntimeException("Message must have a target."); } boolean needWake; synchronized (this) { if (mQuiting) { RuntimeException e = new RuntimeException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); return false; } msg.when = when; Message p = mMessages; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } } if (needWake) { nativeWake(mPtr); } return true; } ~~~ **很简单,就是把handler发送的msg加入到消息队列中。** **5.4handleMessage(Message msg);** 好了,最后只用处理msg了。这是个回调方法,所以必须要等到Looper去从消息队列中读取消息时才会执行。 那就去看看**Looper.loop();**这个循环读取消息的方法。 ~~~ public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycle(); } } ~~~ 开头几句是得到当前线程的Looper对象,然后同构Looper对象得到queue对象,很明显,5.1已经说了,那么这里得到的就是主线程那个Looper和MessageQueue。然后开始了死循环,一直从消息队列中读取消息,读不到就返回,否则执行后面操作。注意看后面操作,真正处理消息的时候到了。 **msg.target.dispatchMessage(msg);关键就是这句。 它调用了msg的target的dispatchMessage方法,之前说了,target就是发送消息的那个handler,好,再会Handler类看dispatchMessage方法。** ~~~ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } ~~~ **很简单,这就是一个消息分发处理嘛。**记得handler发送消息有两种方式,一种就是普通的handler.sendMessage(msg);方法,还有一种就是 ~~~ handler.post(new Runnable(){ @Override public void run() { //主线程应该执行的操作 } }); ~~~ 先来看第一种:直接回调hanleMessage(msg);方法,这样主程序中就执行该操作啦。 再来看第二种: ~~~ private static void handleCallback(Message message) { message.callback.run(); } ~~~ 调用message.callback的run方法。 从源码可以一步一步看出,如下,message的callback指向handler调用post方法传入的Runnable对象。 ~~~ private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } ~~~ 再回头看dispatchMessage方法,会调用下面这个方法: ~~~ private static void handleCallback(Message message) { message.callback.run(); } ~~~ 内部最后调用run方法。 handler原理基本就这样了。 **总结:handler对象定义在需要接收本线程或其他线程发送消息的线程中,该线程会维护一个Looper对象和MessageQueue对象,在其他线程或本线程通过handler发送的消息会加入到handler所在线程中的消息队列中,同时Looper的loop()方法会一直读取消息队列,读到消息后,又让发送消息的handler处理这个消息。** ### [源码下载](http://download.csdn.net/detail/u011102153/9116179)
';

Android四大组件-ContentProvider

最后更新于:2022-04-01 11:18:16

## ContentProvider:内容提供者 **ContentProvider简介:** 当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。 **Uri类简介** Uri代表了要操作的数据,Uri主要包含了两部分信息:1.需要操作的ContentProvider ,2.对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成: 1.scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。 2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。 3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下: • 要操作contact表中id为10的记录,可以构建这样的路径:/contact/10 • 要操作contact表中id为10的记录的name字段, contact/10/name • 要操作contact表中的所有记录,可以构建这样的路径:/contact 本篇主要围绕通话记录和联系人讲解ContentProvider的使用。 首先来了解一下通话记录和联系人的表结构。 这是手机中部分表: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717152a3315c.jpg "") 通话记录在calls表中,联系人表主要有三个:raw_contacts、data、mimetype表。 –联系人记录 –存放联系人信息的表(注意表结构): 联系人信息表:raw_contacts –_id –display_name联系人名称 联系人数据表:data –_id –raw_contact_id(外键,raw_contacts表的_id) –data1 –data2 –mimetype_id(数据类型,如电话,邮箱,地址 外键(mimeypes)) 数据类型表:mimeypes –_id –mimetype ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717152a4263d.jpg "") 数据类型表中需要注意的是name、phone_v2、email_v2字段,对应id是7、5、1; ##一、ContentResolver的使用 1)定义ContentProvieder组件提供的内容的Uri接口 2)定义被访问的表中的字段 3)添加访问权限 **简单使用1:查询最近联系人记录** ~~~ // 访问拨号应用下的ContentProvieder组件提供的内容的Uri接口 private Uri callUri = CallLog.Calls.CONTENT_URI; // 被访问的表中的字段 private String[] columns = { CallLog.Calls._ID, CallLog.Calls.NUMBER, CallLog.Calls.DATE, CallLog.Calls.TYPE }; private void loadData() { // 使用ContentResolver访问拨号记录应用下的ContentProvider组件提供的数据库中表的数据 // 得到ContentResolver对象 ContentResolver resolver = getContentResolver(); // 查询uri代表的资源(从 Uri代表的表中进行查询) Cursor cursor = resolver.query(callUri, columns, null, null, null); while (cursor.moveToNext()) { long id = cursor.getLong(0); String number = cursor.getString(1); long time = cursor.getLong(2); int type = cursor.getInt(3); String date = new SimpleDateFormat("yyyy-MM-dd E HH:mm:ss") .format(new Date(time)); String types = (type == 1 ? "拨入" : (type == 2 ? "拨出" : "未接")); datas.add(new CallInfo(id, number, date, types)); } adapter.notifyDataSetChanged(); } ~~~ **简单使用2:查询手机联系人,然后执行增删改操作** 需要了解联系人数据库中有哪些表及表的结构:通过adb shell可以查看。 –联系人记录 –存放联系人信息的表(注意表结构): 联系人信息表:raw_contacts –_id –display_name联系人名称 联系人数据表:data –_id –raw_contact_id(外键,raw_contacts表的_id) –data1:电话、邮箱、姓名等信息 –data2 –mimetype_id(数据类型,如电话,邮箱,地址 外键(mimeypes)) 数据类型表:mimeypes –_id –mimetype – mimetype_id=1:邮箱 – mimetype_id=5:电话 – mimetype_id=7:姓名 ~~~ // 访问raw_contacts这张表的Uri private Uri contactUri = Uri .parse("content://com.android.contacts/raw_contacts"); private String[] conColumn = { "_id", "display_name" }; // //访问data这张表的Uri private Uri dataUri = Uri.parse("content://com.android.contacts/data"); private String[] dataColumn = { "data1" }; ~~~ **—>增删该查操作都是第一步得到ContentResolver操作对象,第二步执行已经定义好的方法。** ~~~ <!-- 访问联系人的ContentProvider组件的权限 --> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS"/> ~~~ **2.1添加联系人:** // 向联系人表raw_contacts中添加新的联系人信息 ~~~ ContentValues value = new ContentValues(); value.put("display_name", name); value.put("display_name_alt", name); // 返回新插入的记录Uri,Uri中包含了_id // content://com.android.contacts/raw_contacts/#8 Uri datasUri = getContentResolver().insert(contactUri,value); ~~~ **这个方法值得注意的是返回的是一个uri,也就是新增的这条数据**的uri,**如果想得到该条记录的id可以调用这个方法:** **long _id = ContentUris.parseId(datasUri);** **–姓名:** value.put(“data1”, name); value.put(“mimetype”,”vnd.android.cursor.item/name”); **–电话:** value.put(“data1”, phone); value.put(“mimetype”,”vnd.android.cursor.item/phone_v2”); **–邮件:** value.put(“data1”, email); value.put(“mimetype”,”vnd.android.cursor.item/email_v2”); **2.2删除联系人:都是很简单的操作** // 再从联系人表中删除信息 getContentResolver().delete(contactUri, “_id=” + id,null); **2.3修改联系人:** ~~~ value.put("display_name", name); value.put("display_name_alt", name); long id = Long.parseLong(String.valueOf(datas.get(curItemPosition).get("id"))); // 更新联系人信息 getContentResolver().update(contactUri, value,"_id=" + id, null); ~~~ **2.4查询所有联系人** ~~~ ContentResolver resolver = getContentResolver(); // 先从联系人表中查询所有人的信息 Cursor cursor = resolver.query(contactUri, conColumn, null, null, null); ~~~ ##二、自定义ContentProvider **1.步骤:** 1)声明该contentProvider的唯一标识,通常使用包名加数据库名,必须小写 2)为该组件中可以被外界访问的数据库中的资源定义Code标识,不对外界开放的不用定义 3)定义访问资源的Uri的匹配器对象–使用该类生成被访问的资源的Uri 4)UriMatcher添加访问uri 5)清单文件中注册组件 name authorities 6)声明访问该组件的权限 7)重写provider的增删改查方法 **2.实现** **1)自定义数据库** ~~~ public class DBHelper extends SQLiteOpenHelper { public DBHelper(Context context) { super(context, "users.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { // TODO 初始化数据库 db.execSQL("create table t_user(_id integer primary key,uname,upass,money)"); db.execSQL("create table t_order(_id integer primary key,user_id,price,productname)"); db.execSQL("insert into t_user(uname,upass,money) values('lisa','123',200)"); db.execSQL("insert into t_user(uname,upass,money) values('zhangsan','1234',2000)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO 数据库升级时执行该方法 if (newVersion > oldVersion) { db.execSQL("drop table if exists t_user"); db.execSQL("drop table if exists t_order"); onCreate(db); } } } ~~~ **2)按上述步骤定义ContentProvider** ~~~ public class UserContentProvider extends ContentProvider { // 声明该ContentProvider的唯一标识--通常使用包名+数据库名--必须小写 public static final String AUTHORITY = "com.beiing.contentprovider_selfdefine.users"; // 为该组件中可以被外界访问的数据库中的资源定义Code标识 public static final int CODE_USER = 1; public static final int CODE_ORDER = 2; // 定义访问资源的Uri的匹配器对象--使用该类生成被访问的资源的Uri private static UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); // content://com.beiing.contentprovider_selfdefine.users/user uriMatcher.addURI(AUTHORITY, "user", CODE_USER); // content://com.beiing.contentprovider_selfdefine.users/order uriMatcher.addURI(AUTHORITY, "order", CODE_ORDER); } private DBHelper dbHelper; @Override public boolean onCreate() { // TODO 初始化 数据库操作的工具类 dbHelper = new DBHelper(getContext()); return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = null; int code = uriMatcher.match(uri); switch (code) { case CODE_USER: cursor = db.query("t_user", projection, selection, selectionArgs, null, null, sortOrder); break; case CODE_ORDER: break; } return cursor; } @Override public Uri insert(Uri uri, ContentValues values) { // TODO 向数据库中插入数据 SQLiteDatabase db = dbHelper.getWritableDatabase(); if (uriMatcher.match(uri) == CODE_USER) { long id = db.insert("t_user", null, values); // 返回新插入的记录的 Uri,回忆插入新数据时可以通过返回的uri得到id // content://com.beiing.contentprovider_selfdefine.users/user/6 return ContentUris.withAppendedId(uri, id); } return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO 删除数据库中的数据 SQLiteDatabase db = dbHelper.getWritableDatabase(); int num = 0; if (uriMatcher.match(uri) == CODE_USER) { num = db.delete("t_user", selection, selectionArgs); } return num; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO 修改数据库中的数据 SQLiteDatabase db = dbHelper.getWritableDatabase(); if (uriMatcher.match(uri) == CODE_USER) { return db.update("t_user", values, selection, selectionArgs); } return 0; } @Override public String getType(Uri uri) { return null; } } ~~~ **3)清单文件中注册内容提供器** ~~~ <!-- 注册 ContentProvider组件 android:authorities:声明该组件的唯一标识 android:permission:声明该组件的权限 android:exported="true":声明该组件可以被外界应用访问 --> <provider android:name="com.beiing.contentprovider_selfdefine.contentprovider.UserContentProvider" android:authorities="com.beiing.contentprovider_selfdefine.users" android:permission="com.beiing.contentprovider_selfdefine.READ_WRITE" android:exported="true" /> ~~~ **注意:还需要添加访问该provider的权限和修改的操作权限,否则其他程序不能访问或操作** ~~~ <!-- 声明访问该组件的权限 --> <permission android:name="com.beiing.contentprovider_selfdefine.READ_WRITE"/> ~~~ **4)在其他程序中使用** 和上面使用类似,不赘述了。 [源码](http://download.csdn.net/detail/u011102153/9099743)
';

Fragment生命周期和使用小结

最后更新于:2022-04-01 11:18:13

Fragment: 碎片,是一种可以嵌在活动中的UI片段. **1.静态:**在布局文件定义一个 ~~~ <fragment android:id="@+id/fragment1" android:name="com.example.gp08_day23_fragment2.fragment.TestFragment" android:layout_width="wrap_content" android:layout_height="wrap_content" /> //获取碎片管理器----可以直接在 activity获取到 manager = getFragmentManager(); //使用碎片管理器得到需要的碎片对象 Fragment fragment = manager.findFragmentById(R.id.fragment1); //得到碎片对象中的布局对象 View view = fragment.getView(); ~~~ **2.动态:** —-FragmentManager —-事务的使用 —-activity给fragment传值(Bundle对象) **2.1动态添加fragment步骤:** 1)在布局文件中给fragment占个位,一般用FrameLayout 2)创建fragment实例 3)获取到FragmentManager,在活动中可以直接调用getFragmentManager() 4)开启一个事务,通过调用beginTransaction方法开启 5)向容器内加入fragment,一般使用replace()方法实现,需要传入容器的id(就是在布局文件中占的位置)和fragment实例 6)提交事务,调用commit()方法完成。 **2.2先介绍一下传值** ~~~ 传值: //使用碎片管理器对象得到事务对象 FragmentTransaction transaction = manager.beginTransaction();//开启事务 // Fragment f = manager.findFragmentByTag("flag"); // if(f==null) // { // TestFragment fragment = new TestFragment(); // transaction.add(R.id.container, fragment, "flag"); // } TestFragment fragment = new TestFragment(); //activity给fragment传参数 Bundle bundle = new Bundle(); bundle.putString("msg", "hehe"+new Date()); fragment.setArguments(bundle); //replace是替换掉之前的fragment对象(remove--->add) transaction.replace(R.id.container, fragment); //提交事务 transaction.commit();//事务只能提交一次 接收: public class TestFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { TextView textView = new TextView(getActivity()); textView.setTextSize(20); //获取传递过来的参数 Bundle bundle = getArguments(); String value = bundle.getString("msg"); textView.setText(value); return textView; } } ~~~ 关键是这个: //activity给fragment传参数 Bundle bundle = new Bundle(); bundle.putString(“msg”, “hehe”+new Date()); fragment.**setArguments(bundle);** **3.生命周期** Fragment的生命周期:11个方法 * 1):初始化:对应于 activity的onCreate() * onAttach(Activity) 和所属的activity关联 * onCreate(Bundle )fragment的初始化 * onCreateView()初始化fragment显示的UI视图 * onActivityCreated()当activity的 onCreate()方法执行完(说明 activity的初始化已经完成) * 2):显示,隐藏 * onStart(),onResume(),onPause(),onStop() * 3):销毁 * onDestroyView() 销毁fragment显示的UI视图(View 对象) * onDestroy() 销毁的是fragment * onDettach() 和所属的activity断开 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ed64f3e.jpg "") 实例:源码地址见后面 和Activity联动: 创建时:从外到内创建 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717152972840.jpg "") 关闭时:从内到外销毁 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717152984a5c.jpg "") **4.自定义模拟回退栈** ~~~ // 作为回退栈 private LinkedList<Fragment> stack = new LinkedList<Fragment>(); public void addFragment(View v) { TestFragment fragment = new TestFragment(); Bundle bundle = new Bundle(); bundle.putString("msg", "hahahaha" + new Date()); fragment.setArguments(bundle); // 如果栈为空直接入栈,不为空则把当前的activity隐藏,然后加入新的fragment if (stack.size() == 0) getFragmentManager().beginTransaction() .add(R.id.container, fragment).commit(); else getFragmentManager().beginTransaction().hide(stack.peek())// 隐藏栈顶的 .add(R.id.container, fragment).commit(); // 入栈 stack.push(fragment); } // 点击回退按钮显示上一个fragment public void backFragment(View v) { if (!stack.isEmpty()) { getFragmentManager().beginTransaction().remove(stack.poll()) .commit(); } if (!stack.isEmpty()) getFragmentManager().beginTransaction().show(stack.peek()).commit(); } ~~~ 效果图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717152996c93.jpg "") **5.使用baskToStack(null);方法** ~~~ public void addFragment(View v) { TestFragment fragment = new TestFragment(); Bundle bundle = new Bundle(); bundle.putString("msg", "xixi" + new Date()); fragment.setArguments(bundle); FragmentTransaction tran = getFragmentManager().beginTransaction(); tran.add(R.id.container, fragment);//remove1 add 2 tran.addToBackStack(null);//把事务加入当前的回退栈 回滚 tran.commit(); } // 点击回退按钮显示上一个fragment public void backFragment(View v) { onBackPressed(); } ~~~ 效果和自定义回退栈相同。 –>使用回退栈的作用主要是返回时返回到上一个fragment **6.横屏竖屏动态变化布局** 1)创建一个在横屏时加载的布局文件夹 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_57171529dc498.jpg "") 竖屏布局:只有一个fragment ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <fragment android:id="@+id/fragment" android:layout_width="200dp" android:layout_height="match_parent" android:name="com.beiing.fragmentlandscapefragment.FilesFragment" /> </RelativeLayout> ~~~ 横屏布局: ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <fragment android:id="@+id/fragment" android:layout_width="200dp" android:layout_height="match_parent" android:name="com.beiing.fragmentlandscapefragment.FilesFragment" /> <FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_toRightOf="@id/fragment" /> </RelativeLayout> ~~~ 2)判断横屏竖屏:根据横屏竖屏得到不同的操作 ~~~ // 判断屏幕方向 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { ContentFragment fragment = new ContentFragment(); fragment.setArguments(bundle); getFragmentManager().beginTransaction() .replace(R.id.container, fragment) .addToBackStack(null) .commit(); } else { Intent intent = new Intent(getActivity(),ContentActivity.class); intent.putExtras(bundle); startActivity(intent); } ~~~ 效果图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717152a013df.jpg "") **我的总结:** 1)实际开发中,fragment使用非常多,用法大都是在一个显示界面中底部放上一些按钮,中间一个fragment,这样就不用一个activity一个activity的跳了 2)引包的时候注意,一个是android.app.Fragment,还有一个向下兼容的 android.support.v4.app.Fragment;用后一个包的fragment时activity也要用这个包的FragmentActivity 3)在实现1)说的那种类似标签页时,需要注意不要每次都通过事务FragmentTransaction的replace()方法去变换界面,这样会导致该fragment重新创建,如果这个fragment里面有从网络获取数据的操作,这样,变换一次加载一次,谁受得了,解决方法是,在MainActivity中定义相应fragment的成员,然后一次加入FragmentManager中,然后想要显示哪个fragment只用调用FragmentTransaction 的show方法和hide方法。后面具体项目会遇到。 [源码](http://download.csdn.net/detail/u011102153/9093565)
';

Intent使用小结

最后更新于:2022-04-01 11:18:11

**一、Intent简介**:Intent(意图)主要是解决Android应用的各项组件之间的通讯。 Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。 因此,Intent在这里起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。 **二、Intent的功能**:启动活动、启动服务、发送广播等等 **三、显示意图和隐式意图** **1.显示意图:**对于明确指出了目标组件名称的Intent,我们称之为显式Intent。 直接用组件的名称定义目标组件,这种方式很直接。但是由于开发人员往往并不清楚别的应用程序的组件名称,因此,显示Intent更多用于在应用程序内部传递消息。比如在某应用程序内,一个Activity启动一个Service。 ~~~ //显式意图第一种方式 //Intent intent = new Intent(this,TargetActivity.class); //显式意图第二种方式 //Intent intent = new Intent(); //intent.setClass(this, TargetActivity.class); //intent.setClassName(this, "com.bei_ing.TargetActivity"); //intent.setClassName("com.bei_ing", "com.bei_ing.TargetActivity"); //显式意图第三种方式 Intent intent = new Intent(); //ComponentName component = new ComponentName(this, TargetActivity.class); //ComponentName component = new ComponentName(this, "com.bei_ing.TargetActivity"); ComponentName component = new ComponentName("com.bei_ing", "com.bei_ing.TargetActivity"); intent.setComponent(component); startActivity(intent); //开启一个服务 startService(intent); //发送一个广播 sendBroadcast(intent); ~~~ **2.隐式意图:**对于没有明确指出目标组件名称的Intent,则称之为隐式Intent。 恰恰相反,它不会用组件名称定义需要激活的目标组件,它更广泛地用于在不同应用程序之间传递消息。 Android系统寻找与Intent请求意图最匹配的组件具体的选择方法 是:Android将Intent的请求内容和一个叫做IntentFilter的过滤器比较,IntentFilter中包含系统中所有可能的待选组件。 如果IntentFilter中某一组件匹配隐式Intent请求的内容,那么Android就选择该组件作为该隐式Intent的目标组件。 Android如何知道应用程序能够处理某种类型的Intent请求呢?这需要应用程序在Android-Manifest.xml中声明自己所含组件的过滤器(即可以匹配哪些Intent请求)。 一个没有声明Intent-Filter的组件只能响应指明自己名字的显示Intent请求,而无法响应隐式Intent请求。 而一个声明了IntentFilter的组件既可以响应显示Intent请求,也可以响应隐式Intent请求。在通过和IntentFilter比较来解析隐式Intent请求时,Android将以下三个因素作为选择的参考标准: Action Data Category ~~~ Intent intent = new Intent(); intent.setAction("action"); intent.setAction("hello.world"); intent.addCategory("category); startActivity(intent);//该方法默认加入了 category: android.intent.category.DEFAULT,所以在配置文件必须要加盖属性 //调用系统的拨打电话的功能 public void call(View v) { Intent intent = new Intent(); intent.setAction("android.intent.action.CALL"); intent.setData(Uri.parse("tel://120")); startActivity(intent) } //调用系统的发送短信的功能 public void send(View v) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_SENDTO); intent.setData(Uri.parse("smsto://18612346789")); startActivity(intent); } //调用系统的播放音乐的功能 public void play(View v) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse("file://mnt/sdcard/mp1.mp3"), "audio/mp3"); startActivity(intent); } ~~~ —->intent 的setData()方法中的参数类型是Uri格式(可以看一看MIME格式的定义) MIME:[http://baike.baidu.com/link?url=37nGvdZ18j73c6ktJ9H7ZqsadX3CIt9yji_s25TpGOhhJirnXaAMUNXoiRwqUrg6YVfiY1BsZFsDbOVTqXudJq](http://baike.baidu.com/link?url=37nGvdZ18j73c6ktJ9H7ZqsadX3CIt9yji_s25TpGOhhJirnXaAMUNXoiRwqUrg6YVfiY1BsZFsDbOVTqXudJq) **四、通过Intent传值:** **1.普通方式:** ~~~ //普通传值,普通类型如int String float double boolean intent.putExtra("name", "小白"); // intent.putExtra("isOk", true); intent.putExtra("age", 20); //intent.putExtra("float", 99.9f); //intent.putExtra("double", 99.9); ~~~ **2.传递对象:** **2.1 Serializable方式** 序列化,讲一个对象转化成可存储或可传输的状态,序列化的对象可以在网络上进行传输,也可以存储到本地,操作只用让一个类去实现Serializeable接口. ~~~ class Dog implements Serializable{ String name; int age; @Override public String toString() { return "Dog [name=" + name + ", age=" + age + "]"; } } 传值: Dog dog = new Dog(); dog.name = "小白"; dog.age = 1; intent.putExtra("dog", dog);//直接传 //Bundle bundle = new Bundle();//传bundle对象 //bundle.putSerializable("dog", dog); //intent.putExtras(bundle); 接收: Dog dog = (Dog) intent.getSerializableExtra("dog");//直接获取所传对象 //Bundle bundle =intent.getExtras();//接收一个bundle对象 //Dog dog = (Dog) bundle.get("dog");//再从bundle对象中获取 ~~~ **2.2 Parcelable方式** 与Serializable方式不同,Parcelable方式实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。 实现方式比Serializable稍微复杂一点: 1)实现Parcelable接口 2)重写describeContents()方法和writeToParcel(..)方法 3)提供一个CREATOR 常量,重写createFromParcel()方法和newArray()方法 ~~~ class Cat implements Parcelable{ String name; int age; @Override public String toString() { return "Cat [name=" + name + ", age=" + age + "]"; } @Override public int describeContents() { return 0;//直接返回0即可 } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name);//写出名字 dest.writeInt(age);//写出年龄 } public static final Parcelable.Creator<Cat> CREATOR = new Creator<Cat>() { @Override public Cat[] newArray(int size) { return new Cat[size]; } @Override public Cat createFromParcel(Parcel source) { Cat cat = new Cat(); cat.name = source.readString();//读取name cat.age = source.readInt();//读取age return cat; } }; } 传值:和Serializable没两样 Cat cat = new Cat(); cat.name = "花花"; cat.age = 1; intent.putExtra("cat", cat); 接收: Cat cat = intent.getParcelableExtra("cat"); ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ed4da5b.jpg "") **3.两种方式对比** Serializable的方式比较简单,但是由于会把整个对象进行序列化,因此效率会比Parcelable方式低一些。 [源码实例](http://download.csdn.net/detail/u011102153/9074907)
';

Android四大基本组件-Service详解

最后更新于:2022-04-01 11:18:09

## 一、官方文档 **Class Overview** *A Service is an application component representing either an application’s desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use. Each service class must have a corresponding declaration in its package’s AndroidManifest.xml. Services can be started with Context.startService() and Context.bindService().* **服务是一种应用程序组件代表应用程序的意图,而不是与用户或其他应用程序使用电源的功能执行更长的运行操作。每个服务类必须有一个相应的服务声明在其AndroidManifest.xml。启动服务的方法有startservice(), bindservice()。后面会有具体事例测试这两种方法的使用。** *Note that services, like other application objects, run in the main thread of their hosting process. This means that, if your service is going to do any CPU intensive (such as MP3 playback) or blocking (such as networking) operations, it should spawn its own thread in which to do that work. More information on this can be found in Application Fundamentals: Processes and Threads. The IntentService class is available as a standard implementation of Service that has its own thread where it schedules its work to be done.* **注意,服务是运行在他们的主进程的主线程。这意味着,如果你的服务是要做任何CPU消耗大的操作(如MP3播放)或阻塞(如网络)的操作,它会以它自己的线程去做这项工作。关于这方面的更多信息可以在应用基础:进程和线程。intentservice类可以作为一个标准的服务实现,可以调度自己的线程去工作。** **What is a Service?** *Most confusion about the Service class actually revolves around what it is not:* - *A Service is not a separate process. The Service object itself does not imply it is running in its own process; unless otherwise specified, it runs in the same process as the application it is part of.* - *A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors).* **这里要说的是我们不仅要知道service是什么,还要知道它不是什么.** - **服务不是一个单独的进程。服务对象本身并不意味着它是在它自己的进程中运行;除非另有规定,它运行在同一进程中的应用就是它的一部分。** - **服务不是一个线程。这并不意味着主线程去完成相应工作(避免应用程序无响应的错误)**。 **Thus a Service itself is actually very simple, providing two main features:** - *A facility for the application to tell the system about something it wants to be doing in the background (even when the user is not directly interacting with the application). This corresponds to calls to Context.startService(), which ask the system to schedule work for the service, to be run until the service or someone else explicitly stop it.* - *A facility for an application to expose some of its functionality to other applications. This corresponds to calls to Context.bindService(), which allows a long-standing connection to be made to the service in order to interact with it.* - **用于告诉系统什么要在后台运行(即用户不直接与应用程序交互),调用Context.startService() 启动服务,这要求系统来管理该服务,该服务会一直运行到知道明确停止它为止,即stopService()。** - **一个应用程序提供它的一些功能给其他的应用。可以调用Context.bindService()来绑定服务,它允许一个长期的连接是为了服务与它进行交互。(操作中会获得service的binder对象,通过binder来调用相关的服务)** *When a Service component is actually created, for either of these reasons, all that the system actually does is instantiate the component and call its onCreate() and any other appropriate callbacks on the main thread. It is up to the Service to implement these with the appropriate behavior, such as creating a secondary thread in which it does its work.* *Note that because Service itself is so simple, you can make your interaction with it as simple or complicated as you want: from treating it as a local Java object that you make direct method calls on (as illustrated by Local Service Sample), to providing a full remoteable interface using AIDL.* **当一个服务组件被创造时,系统都会调用它的oncreate()和主线程的任何其他适当的回调。它是由服务来实现这些与适当的行为,如创建一个辅助线程来完成它的工作。** **值得注意的是,由于服务本身那么简单,你可以用service与其他应用或其他组件实现一些或简单或复杂的交互:比如要为本地Java对象你直接调用方法,也可以调用AIDL接口进行进程间操作。** **Service Lifecycle 服务的生命周期** *There are two reasons that a service can be run by the system. If someone calls Context.startService() then the system will retrieve the service (creating it and calling its onCreate() method if needed) and then call its onStartCommand(Intent, int, int) method with the arguments supplied by the client. The service will at this point continue running until Context.stopService() or stopSelf() is called. Note that multiple calls to Context.startService() do not nest (though they do result in multiple corresponding calls to onStartCommand()), so no matter how many times it is started a service will be stopped once Context.stopService() or stopSelf() is called; however, services can use their stopSelf(int) method to ensure the service is not stopped until started intents have been processed.* **有两个理由可以说明服务可以被系统运行。如果有人Context.startService()来启动服务。然后系统会检索服务(创造它,如果需要调用它的oncreate()方法)然后调用onStartCommand(Intent, int, int) 方法。然后该服务将一直运行,直到stopservice()或stopself()被调用。请注意,当服务启动后,再调用Context.startService() 方法时,(onstartcommand()会被调用oncreate()方法不会再次被调用,也就是说服务只会被创建一次,然调用stopservice()或stopself()会停止服务;然而,服务可以用stopSelf(int)来保证服务直到成功打开意图之后才停止。** *For started services, there are two additional major modes of operation they can decide to run in, depending on the value they return from onStartCommand(): START_STICKY is used for services that are explicitly started and stopped as needed, while START_NOT_STICKY or START_REDELIVER_INTENT are used for services that should only remain running while processing any commands sent to them.* **为启动的服务,有两个主要的操作模式,他们可以决定运行,取决于他们的回报onstartcommand()价值:start_sticky用于显式启动和停止所需要的服务,而start_not_sticky或start_redeliver_intent用于服务,应运行在处理任何命令发送给他们。** *Clients can also use Context.bindService() to obtain a persistent connection to a service. This likewise creates the service if it is not already running (calling onCreate() while doing so), but does not call onStartCommand(). The client will receive the IBinder object that the service returns from its onBind(Intent) method, allowing the client to then make calls back to the service. The service will remain running as long as the connection is established (whether or not the client retains a reference on the service’s IBinder). Usually the IBinder returned is for a complex interface that has been written in aidl.* **客户端也可以使用Context.bindService() 获得持久连接到服务。这同样造成服务如果它没有运行(虽然这样叫oncreate()),但不调用onstartcommand()。客户端将从onBind(Intent)方法得到IBinder对象,让绑定者可以调用相关服务。该服务被创建后将和绑定者保持连接(不管客户端是否仍保留服务的IBinder)。通常返回的是一个复杂的接口,这个已经在AIDL中实现。** *A service can be both started and have connections bound to it. In such a case, the system will keep the service running as long as either it is started or there are one or more connections to it with the Context.BIND_AUTO_CREATE flag. Once neither of these situations hold, the service’s onDestroy() method is called and the service is effectively terminated. All cleanup (stopping threads, unregistering receivers) should be complete upon returning from onDestroy().* **对于一个服务的绑定者,服务既可以被开启,也可以被连接。在这种情况下,系统会保持服务的运行只要是开始或有一个或多个连接到它的context.bind_auto_create标识。一旦这些情况都存在,服务的ondestroy()方法被调用后,服务才会有效终止。这样,所有的清理(停止线程,注销接收器)等方法都可以卸载ondestroy()中。** **Process Lifecycle 进程周期** *The Android system will attempt to keep the process hosting a service around as long as the service has been started or has clients bound to it. When running low on memory and needing to kill existing processes, the priority of a process hosting the service will be the higher of the following possibilities:* **Android系统会试图让进程托管服务当这个服务被开启或被绑定时。在低内存的时候需要杀死现有的进程,一个托管了服务将的进程优先级更高:** *If the service is currently executing code in its onCreate(), onStartCommand(), or onDestroy() methods, then the hosting process will be a foreground process to ensure this code can execute without being killed.* **如果服务是目前正在执行oncreate()、onstartcommand()或ondestroy()方法,然后宿主进程将变成前台进程以确保这些方法能被执行而不被杀死。** *If the service has been started, then its hosting process is considered to be less important than any processes that are currently visible to the user on-screen, but more important than any process not visible. Because only a few processes are generally visible to the user, this means that the service should not be killed except in extreme low memory conditions.* **如果此服务已被启动,任何可见进程都比它的宿主进程更重要,但比那些不可见的进程都要重要。因为只有很少的进程是可见进程,这表明服务应该不会在极端低内存情况下被杀死。** *If there are clients bound to the service, then the service’s hosting process is never less important than the most important client. That is, if one of its clients is visible to the user, then the service itself is considered to be visible.* **如果有客户端绑定到服务,那么服务的宿主进程比这些重要的客户端还重要。也就是说,如果绑定它的一个客户端是用户可见的,那么服务本身是可见的。** *A started service can use the startForeground(int, Notification) API to put the service in a foreground state, where the system considers it to be something the user is actively aware of and thus not a candidate for killing when low on memory. (It is still theoretically possible for the service to be killed under extreme memory pressure from the current foreground application, but in practice this should not be a concern.)* **一个启动的服务可以使用startForeground(int, Notification) API把服务设置为前台状态,系统认为是用户的主动意识,所以在内存不足时,该服务并不是被杀候选人。(这仍然是理论上可能的,在内存极端不足时,即便是前台service也有可能被杀。)** *Note this means that most of the time your service is running, it may be killed by the system if it is under heavy memory pressure. If this happens, the system will later try to restart the service. An important consequence of this is that if you implement onStartCommand() to schedule work to be done asynchronously or in another thread, then you may want to use START_FLAG_REDELIVERY to have the system re-deliver an Intent for you so that it does not get lost if your service is killed while processing it.* **注意,大部分时间你的服务一直在运行,在内存压力非常大的时候它也会被杀死。如果发生这种情况,系统将尝试重新启动服务。这是一个重要的结果,如果你实现onstartcommand()安排工作要在另一个线程异步完成,那么你可能想使用start_flag_redelivery让系统为你提供一个意图使它不会丢失之前的服务。** ## 二、具体实例 ### 1.最基本的用法 ~~~ package com.servicedetail; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void StartService(View v) { Intent startIntent = new Intent(this, MyService.class); startService(startIntent); } public void StopService(View v) { Intent startIntent = new Intent(this, MyService.class); stopService(startIntent); } } ~~~ ~~~ package com.servicedetail; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class MyService extends Service { @Override public void onCreate() { super.onCreate(); Log.i("--","onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("--","onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { Log.i("--","onBind"); return null; } @Override public void onDestroy() { Log.i("--","onDestroy"); super.onDestroy(); } @Override public boolean onUnbind(Intent intent) { Log.i("--","onUnbind"); return super.onUnbind(intent); } @Override public void onRebind(Intent intent) { Log.i("--","onRebind"); super.onRebind(intent); } } ~~~ —–>最简单的startService, stopService 第一次点击startService,stopService: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eca76a9.jpg "") 在destroy之前点击两次startService,只会调用一次onCreate方法 这是由于onCreate()方法只会在Service第一次被创建的时候调用,如果当前Service已经被创建过了,不管怎样调用startService()方法,onCreate()方法都不会再执行。因此你可以再多点击几次StartService按钮试一次,每次都只会有onStartCommand()方法中的打印日志 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ecb8ad0.jpg "") ### 2.Service和Activity通信 上面我们学习了Service的基本用法,启动Service之后,就可以在onCreate()或onStartCommand()方法里去执行一些具体的逻辑了。不过这样的话Service和Activity的关系并不大,只是Activity通知了Service一下:“你可以启动了。”然后Service就去忙自己的事情了。那么有没有什么办法能让它们俩的关联更多一些呢?比如说在Activity中可以指定让Service去执行什么任务。当然可以,只需要让Activity和Service建立关联就好了。 观察MyService中的代码,你会发现一直有一个onBind()方法我们都没有使用到,这个方法其实就是用于和Activity建立关联的,修改MyService中的代码,如下所示: ~~~ package com.servicedetail; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.View; public class MainActivity extends Activity { private MyService.MyBinder myBinder; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i("--", "MainActivity-->onServiceConnected"); myBinder = (MyService.MyBinder) service; myBinder.conServer(); } @Override public void onServiceDisconnected(ComponentName name) { Log.i("--", "MainActivity-->onServiceDisconnected"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void StartService(View v) { Intent startIntent = new Intent(this, MyService.class); startService(startIntent); } public void StopService(View v) { Intent startIntent = new Intent(this, MyService.class); stopService(startIntent); } public void BindService(View v) { Intent bindIntent = new Intent(this, MyService.class); bindService(bindIntent, connection, BIND_AUTO_CREATE); } public void UnbindService(View v) { if (connection != null) unbindService(connection); } } ~~~ 运行效果: (1):点击BindService ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ecca2b7.jpg "") 再点击UnbindService ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ecdb5e1.jpg "") —>可以看到activity和service建立连接了,而且这种步骤会destroy掉service。 (2):点击startService,在点击BindService ,再点击UnbindService,可以看到不会destroy掉service ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ecec70f.jpg "") 如果再点击stopService就会destroy service。 ### 3.如何销毁Service 在Service的基本用法这一部分,我们介绍了销毁Service最简单的一种情况,点击Start Service按钮启动Service,再点击Stop Service按钮停止Service,这样MyService就被销毁了,可以看到打印日志如下所示: ~~~ Service-->onCreateService Service-->onStartCommand Service-->onDestroy ~~~ 那么如果我们是点击的Bind Service按钮呢?由于在绑定Service的时候指定的标志位是BIND_AUTO_CREATE,说明点击Bind Service按钮的时候Service也会被创建,这时应该怎么销毁Service呢?其实也很简单,点击一下Unbind Service按钮,将Activity和Service的关联解除就可以了。 先点击一下Bind Service按钮,再点击一下Unbind Service按钮,打印日志如下所示: ~~~ Service-->onCreate Service-->onBind Service-->onUnbind Service-->onDestroy ~~~ 以上这两种销毁的方式都很好理解。那么如果我们既点击了Start Service按钮,又点击了Bind Service按钮会怎么样呢?这个时候你会发现,不管你是单独点击Stop Service按钮还是Unbind Service按钮,Service都不会被销毁,必要将两个按钮都点击一下,Service才会被销毁。也就是说,点击Stop Service按钮只会让Service停止,点击Unbind Service按钮只会让Service和Activity解除关联,一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁。 ### 4.创建前台Service Service几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是Service的系统优先级还是比较低的,当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service。如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止Service被回收才使用前台Service,有些项目由于特殊的需求会要求必须使用前台Service,比如QQ或微信等聊天的会在通知栏提示收到信息。 在onCreate()方法中或onBind()方法中添加如下代码: ~~~ Notification notification = new Notification(R.drawable.ic_launcher, "有通知到来", System.currentTimeMillis()); Intent notificationIntent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(this, "通知标题", "通知内容", pendingIntent); startForeground(1, notification); ~~~ 在开启服务要调用startService()后,在通知栏会保持一个通知(只要不调用stopService(),就会一直处在通知栏);,这是即使程序关闭,都不会消失。 如果调用bindService,程序关闭,服务就会关闭。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ed0ad08.jpg "") 最后需要注意的是: 上面的官方文档已经说了,service不是一个单独的线程,也就是说service就在主线程中,所以如果要处理耗时操作的话,主线程肯定会卡。所以,在service中处理耗时操作也要新开一个线程。那为什么不直接在activity中开一个线程来处理呢? **原因有:** 1.activity难以控制线程,一旦activity销毁后,之前创建的进程就无法再次获得。 2.在一个activity中创建的线程无法被另一个activity操作。 3.Service可以很好的管理线程,即使activity销毁了,只要再次绑定service还可以通过service操作处理事务的线程。 [源码](http://download.csdn.net/detail/u011102153/8710833)
';

Android-Activity的四状态、七生命周期、和四启动模式

最后更新于:2022-04-01 11:18:06

## 一、四大基本组件简介: Android四大基本组件:Activity、Service、Content Provider、Broadcast Receiver Activity:活动视图 一个负责与用户交互的显示界面的组件。 Service:服务:一个没有界面、运行在后台的服务。 Content Provider:内容提供者,一个应用程序可以使用Content Provider来共享自己的数据,另一个应用想要使用该应用提供的数据时,可以通过ContentResolver来访问。 Broadcast Receiver:广播接收者:广播消息接收器,监听的事件源是Android应用中的其他组件。 ## 二、概括 Android是使用任务(Task)来管理活动的,一个任务就是一组放在栈里的活动的集合,这个栈也称为返回栈,每当启动一个新的活动,它就会入栈,并处于栈顶的位置,当一个活动finish时会销毁活动,处于栈顶的活动会出栈。系统显示的总是栈顶的活动。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eb45fad.jpg "") ## 三、活动的四个状态 1)运行状态:当一个活动位于返回栈的栈顶时,这时活动就处于运行状态,**最不会被系统回收的就是运行状态的活动**。 2)暂停状态:当一个活动不再处于栈顶的位置,但仍然可见,弹出一个对话框或者一个不能占满屏幕的活动都会导致前一个活动处于暂停状态,**系统也不会轻易回收这样的活动,除非是内存极低的情况(回收可见的活动都会造成极不好的用户体验)** 3)停止状态:当一个活动不处于栈顶位置,且完全不可见的时候,就进入停止状态,**当内存较低时系统会回收这样的活动** 4)销毁状态:当一个活动从栈中移除后就编程销毁状态,**系统会回收这样的活动** ## 四、活动的七大生命周期 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eb61187.jpg "") 1)onCreate() :当Activity第一次被创建时调用,完成活动的初始化操作。 2)onStart() :当用户可以看到这个Activity时调用 3)onResume() :当获得了用户的焦点时,就是用户点击了屏幕 4)onPause() :当系统准备启动或回复另一个活动时调用。在这个方法中将一些小号CPU的资源释放,保存一些重要数据。 5)onStop() :当活动完全不可见是调用,当新启动的活动时对话框式的,还处于可见时,该方法是不会被调用 6)onDestroy():活动被销毁时调用 7)onRestart():当活动有停止状态变为运行状态时调用。 测试: 打开一个对话框式的活动: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eb8596f.jpg "") 打印: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eb97438.jpg "") 当跳转到另一个活动时: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eba9b2c.jpg "") ## 五、四种启动模式 1)standard:默认的模式,对于使用standard模式的活动,系统不管这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ebbbe95.jpg "") 测试: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ebd3192.jpg "") 当前Mainactivity的Id是40f76d48 点击第一个按钮:Id变为40f8bdc0,说明新创建了一个活动 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ebe556c.jpg "") 2)singleTop:当活动已经处在栈顶时,再启动这个活动时直接使用它而不会再次创建这个活动的实例,可以很好的解决重复创建栈顶活动的问题 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ec034f5.jpg "") 点击启动singleTop模式Activity时,跳到该界面,Id:40f979e8 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ec2357d.jpg "") 再点击该界面的按钮,Id没变,说明没有创建新的实例。 3)singleTask:singleTop模式可以很好的解决重复创建栈顶活动的问题,但是当该活动没有处于栈顶时,还有可能创建多个该实例,这时就可以用singleTask模式。当活动模式指定为singleTask模式时,每次启动该活动时,系统会在返回栈中查看是否存在该活动实例,如果存在则直接使用,不存才创建。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ec34daf.jpg "") 测试: 点击启动singleTaskActivity,Id是:40fcd3a0 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ec4eed1.jpg "") 再点击这个界面的按钮,跳到主界面,注意这是这个singleTaskActivity不处在栈顶,但还在栈中。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ebd3192.jpg "") 再重复第一步操作,依然跳到这个界面,Id没有变化。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ec4eed1.jpg "") 4)singleInstance:指定为singleInstance模式的活动会启用一个新的任务栈来管理这个活动。用这种模式的活动主要是为了供其他程序共享该活动,如浏览器的使用。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ec70eaf.jpg "") 测试: 创建一个新的应用,包含一个用singleInstance模式的activity,运行,Id是40f78bc8 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ec8f007.jpg "") 然后在主界面点击启动SingleInstance Activity,结果是从这个程序直接跳到该上面那个应用。 ## 六、总结 掌握好活动的生命周期能清楚的知道activity当前在什么状态,这样我们就知道在什么状态该做什么。活动的启动模式各有特点,没有最好的,只有最合适的,需要根据实际情况选择合适的模式。 [源码](http://download.csdn.net/detail/u011102153/9059255)
';

Android系统架构、JVM和DalvikVM的区别

最后更新于:2022-04-01 11:18:04

## 一、系统框架 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714ea8e95e.jpg "") 1.Anroid大致分为四层结构,五块区域 1)应用程序层(Applications) 所有安装在手机上的应用程序都属于这一层。 2)应用程序框架(Application Framework) 提供大量的API供开发人员使用,Android自带的一些核心应用就是使用这些API完成的。 3)函数库(Libraries) —Surface Manager:管理对显示子系统的访问,并可以对多个应用程序的2D和3D图层机提供无缝整合 —Media Framework:基于PacketVideo的OpenCORE,支持播放和录制多种流行的音频和视频格式,以及查看静态图片 —SQLite提供数据库的支持 —OpenGL|ES提供3D绘图的支持 —FreeType:位图和向量字体显示 —WebKit:Web浏览器引擎,为Android浏览器提供支持 —SGL:底层的2D图形引擎 —SSL:在Android中通信实现握手 —C/C++(libc)库来为Android系统提供了主要的特性支持 4)运行时(Android Runtime) 由两部分组成:Android核心库集和Dalvik虚拟机。核心库集提供了Java语言核心库所能使用的绝大部分功能,Dalvik虚拟机负责运行Android应用程序。 5)Linux内核 Linux内核提供安全性、内存管理、进程管理、网络协议栈和驱动模型等核心系统服务,Linux内核也是系统硬件和软件叠层之间的抽象层。 ## 二、JVM和DalvikVM的区别 DalvikVM: Dalvik虚拟机是Google的用于移动设备的Android平台的一个主要部分。虚拟机可运行Java平台应用程序,这些应用程序被转换成紧凑的Dalvik可执行格式(.dex),该格式适合内存和处理器速度受限的系统。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eac50df.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eae98df.jpg "") ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eb0a44a.jpg "") **区别:** 1.Dalvik并未完全遵守JVM规范,两者也不兼容。 2.JVM虚拟机运行的是字节码,Dalvik运行的是其专有的dex(Dalvik Executable)文件。 3.JVM直接从.class文件或者jar包中加载字节码然后运行,而Dalvik则无法从.class文件或jar包中加载字节码,而是通过DX工具将应用程序所欲的.class文件编译成.dex文件,然后运行.dex文件。 4.Dalvik 基于寄存器,而 JVM 基于栈。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。 5.Dalvik负责进程隔离和线程管理,每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571714eb2a8a3.jpg "")
';

前言

最后更新于:2022-04-01 11:18:02

> 原文出处:[Android从零开始](http://blog.csdn.net/column/details/androidfrom-0.html) 作者:[u011102153](http://blog.csdn.net/u011102153) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # Android从零开始 > 从基础到进阶讲解Android相关的知识和开发经验
';