diff --git a/README.md b/README.md index ce1e482..94e2127 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ ![](https://i.kinja-img.com/gawker-media/image/upload/s--fKCSXh1t--/c_scale,fl_progressive,q_80,w_800/gackoyrnmjd2i9mewj1d.jpg) ## Java +### Java基础 +* [Java之位运算符](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/Java%E4%B9%8B%E4%BD%8D%E8%BF%90%E7%AE%97%E7%AC%A6.md) + + ### Java高新技术 * [泛型--GenericType] * [虚拟机中的泛型信息](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/%E5%8F%8D%E5%B0%84(%E4%BA%8C)%E4%B9%8B%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%AD%E6%B3%9B%E5%9E%8B%E7%B1%BB%E5%9E%8B%E4%BF%A1%E6%81%AF.md) @@ -11,8 +15,22 @@ * [Java8--时间API] * [Java8--StreamAPI] +### Java多线程 +* [Java多线程之内存可见性synchronized,volatile](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/Java多线程之内存可见性.md) +* [Java多线程之线程中断interruption协作机制](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8B%E4%B8%AD%E6%96%AD%E7%BA%BF%E7%A8%8B.md) +* [Java多线程之线程的状态及方法](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8B%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%8A%E6%96%B9%E6%B3%95.md) + +### Java并发 +* [同步工具类之CountDownLatch,Semaphore,Barrier](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/Java%E5%B9%B6%E5%8F%91%E4%B9%8B%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7%E7%B1%BB.md) +* [Java并发之线程池(一)之ThreadPoolExecutor](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/Java%E5%B9%B6%E5%8F%91%E5%A4%9A%E7%BA%BF%E7%A8%8B(%E4%B8%80)%E4%B9%8BThreadPoolExecutor.md) +* [Java并发之线程池(二)之Executors] +* [Java并发之阻塞队列](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/Java%E5%B9%B6%E5%8F%91%E4%B9%8B%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97.md) + + + ### Java设计模式 * [观察者模式](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/Java%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F.md) +* [单例模式](https://github.com/showdy/Android_Note/blob/master/showdy_note/java/Java%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8BSingleton.md) @@ -20,10 +38,16 @@ ## Android ### UI控件篇 -* [Material Design风格控件]: - * [Toolbar详解](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/material_design/toolbar%E8%AF%A6%E8%A7%A3.md) - + * [Toolbar](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/material_design/toolbar%E8%AF%A6%E8%A7%A3.md) + * [TabLayout](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/material_design/Tablayout%E4%B9%8B%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%B7%E5%BC%8F.md) +### Android基础: +* [Activity](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/Android%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E4%B9%8BActivity.md) +* [Service](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/Android%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6__Service.md) +* [BroadcastReceiver](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/Android_BroadcastReceiver.md) +* [BitmapFactory.Options](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/BitmapFactory_Options.md) + + ### Android自定义View篇 * [自定义View--自定义基础](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/view/%E8%87%AA%E5%AE%9A%E4%B9%89View%E5%9F%BA%E7%A1%80%E7%AF%87.md) * [自定义View--View绘制的绘制流程](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/view/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%A7%E4%BB%B6%E4%B9%8BView%E7%BB%98%E5%88%B6%E6%B5%81%E7%A8%8B.md) @@ -38,30 +62,23 @@ ### Android异步消息篇 -* [Android异步处理技术之Hanlder](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/strategy/android%E5%BC%82%E6%AD%A5%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6%E4%B9%8BHandler.md) -* [Android异步处理技术之HandlerThread及IntentService源码分析](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/strategy/Android%E5%BC%82%E6%AD%A5%E6%9C%BA%E5%88%B6%E4%B9%8BHandlerThread%E5%92%8CIntentService%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md) -* [Android异步处理技术之AsyncQueryHandler] -* [Android异步处理技术之AsyncTask] -* [Android异步处理技术之Executor Framwork] -* [Android异步处理技术之Loader] +* [Android异步消息之Hanlder](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/strategy/android%E5%BC%82%E6%AD%A5%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6%E4%B9%8BHandler.md) +* [撸一个自己的Handler异步消息机制](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/%E6%92%B8%E4%B8%80%E4%B8%AA%E8%87%AA%E5%B7%B1%E7%9A%84Handler%E5%BC%82%E6%AD%A5%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6.md) +* [Android异步消息之HandlerThread及IntentService源码分析](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/strategy/Android%E5%BC%82%E6%AD%A5%E6%9C%BA%E5%88%B6%E4%B9%8BHandlerThread%E5%92%8CIntentService%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md) +* [Android异步消息之LocalBroadcastManager](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/LocalBroadcastManager%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md) +* [Android异步消息之AsyncQueryHandler] +* [Android异步消息之AsyncTask](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/AsyncTask%E4%BD%BF%E7%94%A8%E5%8F%8A%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md) +* [Android异步消息之Executor Framwork] +* [Android异步消息之Loader] + +### Android 网络篇 +* [Volley官方文档翻译](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/open_source/volley%E6%96%87%E6%A1%A3%E7%BF%BB%E8%AF%91.md) +* [Volley源码解析]() +* [使用Volley进行Https请求]() +* [使用Volley进行图片上传]() ### Android开发中常用的功能 * [Android_APK全量更新策略](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/strategy/apk%E6%9B%B4%E6%96%B0%E7%AD%96%E7%95%A5.md) * [拍照,相册及裁剪Android N适配](https://github.com/showdy/Android_Note/blob/master/showdy_note/android/strategy/%E4%BD%BF%E7%94%A8%E7%B3%BB%E7%BB%9F%E7%9B%B8%E5%86%8C%E5%9B%BE%E7%89%87%E6%88%96%E6%8B%8D%E7%85%A7%E5%B9%B6%E8%A3%81%E5%89%AA%E4%B9%8BAndroid_N%E9%80%82%E9%85%8D.md) - -### Android开发之网络篇 - - - -### Android Drawable篇 -* [ShapeDrawable] -* [LayerDrawable] -* [StateListDrawable] -* [LevelListDrawable] -* [TransitionDrawable] -* [InsetDrawable] -* [ScaleDrawable] -* [ClipDrawable] -* [自定义Drawable] diff --git a/showdy_note/android/Android_BroadcastReceiver.md b/showdy_note/android/Android_BroadcastReceiver.md new file mode 100644 index 0000000..0193676 --- /dev/null +++ b/showdy_note/android/Android_BroadcastReceiver.md @@ -0,0 +1,185 @@ +### Android Broadcast + +#### Broadcast使用场景 + +Android广播分为两个方面:广播发送者和广播接受者.通常情况下,BroadcastRecevier指广播接受者,广播作为Android组件之间的通讯方式,使用场景有: + +* 同一个APP内部的同一个组件类的消息通讯(单线程或者多个线程) +* 同一个APP内部不同的组件之间的消息通讯(单个进程) +* 同一个APP具有多个进程不同组件之间的消息通讯 +* 不同APP之间的组件之间的通讯 +* Android系统在特定情况下与APP之间的通讯. + +#### Broadcast实现的基本流程为: +* 广播接受者BroadcastRecevier通过Binder机制向AMS(Activity manager Service)进行注册 +* 广播发送者通过Binder机制向AMS发送广播 +* AMS查找符合条件(intentFilter/permission)的BroadcastRecevier,将广播发送给ReceiverDispatcher,Dispatcher将广播发送到BroadcastReceiver(一般情况是Activity)的消息循环队列中; +* 消息循环执行此广播,回调到BoradcastReceiver中的onReceiver()方法中. + + +#### 广播注册方式 + +* 静态注册: + +```xml + + + + +``` + +其中属性: + +* android:exported: 此广播能否接受其他APP发出的广播,这个属性默认值由intent-filter决定,如果有intent-filter,默认值为true,否则为false(Activity/Service中同样适用). +* android:name: 广播接受者名 +* android:permission: 如果设置,具有相同权限的广播发送的广播才能被此接受者接受. +* android:process: 广播接受者所处在的进程,默认为app. + +```java + + + + + + + + + +``` + +* 动态注册: + +> 动态注册广播对使用的Context要注意,因为广播接受者的存在取决于注册的context,如果是Activity,广播在当前Activity中有效,如果是Application context则与App应用生命周期相同. + + ```java + + registerReceiver(BroadcastReceiver receiver, IntentFilter filter) + + registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) + + ``` + +#### 广播发送及其广播类型 +* 广播的类型: + * Normal Broadcast 普通广播 + * Ordered Broadcast 有序广播 + * Sticky Broadcast 粘性广播(api21中废弃) + * System Broadcat 系统广播 + * Local Broadcast APP内部广播 + +* 广播发送的方式: + * `sendOrderedBroadcast(Intent, String)` 发送有序广播 + * `sendBroadcast(Intent) ` 发送普通广播 + * `LocalBroadcastManager.sendBroadcast ` 发送应用内广播 + +#### 不同注册方式的广播接收器回调onReceive(context, intent)中的context具体类型 + +* 对于静态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是ReceiverRestrictedContext; + +* 对于全局广播的动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Activity Context; + +* 对于通过LocalBroadcastManager动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Application Context。 + +> 注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册的ContextReceiver才有可能接收到(静态注册或其他方式动态注册的ContextReceiver是接收不到的)。 + +#### 如何在广播接收者onReceiver中进行耗时操作 + +广播接收者有生命周期,但是很短,当onReceiver()执行完毕,他生命周期就结束了.这次BroadcastRece已经不处于active状态,被系统杀掉的几率很高.如果此时去开线程进行异步超过或者打开Dialog都还没达到相应的效果就被系统杀掉,因为这个Receiver组件在运行,但是只是一个执行完毕的空进程.这情况下可以使用下面方法,来保持Receiver处于active状态,即便系统想要快速结束receive,也可以把操作移动其他线程防止主线程卡顿. + +* goAsync() +* JobService() + +```java + + public class MyBroadcastReceiver extends BroadcastReceiver { + private static final String TAG = "MyBroadcastReceiver"; + + @Override + public void onReceive(final Context context, final Intent intent) { + final PendingResult pendingResult = goAsync(); + AsyncTask asyncTask = new AsyncTask() { + @Override + protected String doInBackground(String... params) { + StringBuilder sb = new StringBuilder(); + sb.append("Action: " + intent.getAction() + "\n"); + sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n"); + Log.d(TAG, log); + // Must call finish() so the BroadcastReceiver can be recycled. + pendingResult.finish(); + return data; + } + }; + asyncTask.execute(); + } + } + + +``` + + +#### 广播的安全隐患以及相应的措施 + +Android中的广播可以跨进程甚至跨App直接通信,且注册是exported对于有intent-filter的情况下默认值是true,由此将可能出现安全隐患如下: + +* 1.其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理; + +* 2.其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。 + +无论哪种情形,这些安全隐患都确实是存在的。由此,最常见的增加安全性的方案是: + +* 1.对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收; + +* 2.在广播发送和接收时,都增加上相应的permission,用于权限验证; + +* 3.发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定在,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。 + +App应用内广播可以理解成一种局部广播的形式,广播的发送者和接收者都同属于一个App。实际的业务需求中,App应用内广播确实可能需要用到。同时,之所以使用应用内广播时,而不是使用全局广播的形式,更多的考虑到的是Android广播机制中的安全性问题。 + +相比于全局广播,App应用内广播优势体现在:1.安全性更高;2.更加高效。 + +为此,Android v4兼容包中给出了封装好的LocalBroadcastManager类,用于统一处理App应用内的广播问题,使用方式上与通常的全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将主调context变成了LocalBroadcastManager的单一实例。 + +```java + + //registerReceiver(mBroadcastReceiver, intentFilter); + //注册应用内广播接收器 + localBroadcastManager = LocalBroadcastManager.getInstance(this); + localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter); + + //unregisterReceiver(mBroadcastReceiver); + //取消注册应用内广播接收器 + localBroadcastManager.unregisterReceiver(mBroadcastReceiver); + + Intent intent = new Intent(); + intent.setAction(BROADCAST_ACTION); + intent.putExtra("name", "qqyumidi"); + //sendBroadcast(intent); + //发送应用内广播 + localBroadcastManager.sendBroadcast(intent); + +``` + +### 面试题: + +1. 广播中如何进行耗时操作?(Service/Notification) + * goAsync() + * JobService + +2. 广播是否可以开启Activity? + + 广播启动activity很可能影响用户体验,何况有时接受者还不止一个,可以考虑使用Notification. + +3. 广播来更新界面是否合适? + + 如果不是频繁更新刷新,可以是广播来达到效果.对于频繁地刷新动作,不要使用广播,广播发送和接收使用具有一定的代价,他的传输是通过Binder机制实现,那么系统会为广播做进程之间通讯做准备很好性能,另外,广播的接收具有一定的延时性,可能导致卡顿(Binder传输). + +4. 有时候基于数据安全考虑,我们想发送广播只有自己(本进程)能接收到,那么该如何去做呢?如果不使用LocalBroadcastManger,该怎么实现? + + 可能使用Handler,往主线程的消息池(Message Queue)发送消息,只有主线程的Handler可以分发处理它,广播发送的内容是一个Intent对象,我们可以直接用Message封装一下,留一个和sendBroadcast一样的接口。在handleMessage时把Intent对象传递给已注册的Receiver。 diff --git "a/showdy_note/android/Android345円233円233円345円244円247円347円273円204円344円273円266円__Service.md" "b/showdy_note/android/Android345円233円233円345円244円247円347円273円204円344円273円266円__Service.md" index 450842b..deb69c1 100644 --- "a/showdy_note/android/Android345円233円233円345円244円247円347円273円204円344円273円266円__Service.md" +++ "b/showdy_note/android/Android345円233円233円345円244円247円347円273円204円344円273円266円__Service.md" @@ -11,6 +11,8 @@ * `startService(Intent service)`,通过intent值来指定启动哪个Service,可以直接指定目标Service的名,也可以通过Intent的action属性来启动设置了相应action属性的Service,使用这种方式启动的Service,当启动它的Activity被销毁时,是不会影响到它的运行的,这时它仍然继续在后台运行它的工作。直至调用`StopService(Intent service)`方法时或者是当系统资源非常紧缺时,这个服务才会调用onDestory()方法停止运行。所以这种Service一般可以用做,处理一些耗时的工作。 * 四大组件默认都是和activity运行在同一个主线程中的,那就是说activity通过startservice方法启动一个服务后,被启动的服务和activity都是在同一个线程中的。所以当我主动销毁了这个activity,但是他所在的线程还是存在的,只不过是这个activity他所占用的资源被释放掉了,这个activity所在的主线程只有当android内存不足才会被杀死掉,否则一般的情况下这个activity所在的应用程序的线程始终存在,也就是这个activity所启动的服务也会一直运行下去。 #####Service +```java + public class LifeService extends Service { private static final String TAG = "LifeService"; @@ -31,7 +33,11 @@ super.onDestroy(); } } +``` + ##### Activity +```java + public class MainActivity extends AppCompatActivity { @Override @@ -51,6 +57,7 @@ stopService(intent); } } +``` ##### 运行结果: ![](img/start_service_lifecycle.png) @@ -60,6 +67,8 @@ * bindService开启的服务,可以调用到服务中的方法. * 启动的LifeService是和MainActivity在同一个进程里的,因为在注册服务时,没有配置它的android:process = "xxxx" 属性。 ##### Service +```java + public class LifeService extends Service { private static final String TAG = "LifeService"; @@ -107,7 +116,11 @@ Log.d(TAG, "服务里的方法被调用了"); } } +``` + ##### Activity +```java + public class MainActivity extends AppCompatActivity { private ServiceConnection conn; @@ -149,6 +162,7 @@ mBinder.callMethodInService(); } } +``` ##### 运行结果 ![](img/bind_service_1.png) @@ -163,6 +177,8 @@ * 混合方式开启服务: 保证服务后台长期运行, 还能调用服务中的方法. ![](img/service_binding_tree_lifecycle.png) ##### Service +```java + public class LifeService extends Service { private static final String TAG = "LifeService"; @@ -214,8 +230,11 @@ } } +``` ##### Activity +```java + public class MainActivity extends AppCompatActivity { private ServiceConnection conn; private LifeService.Mybind mBinder; @@ -264,7 +283,8 @@ mBinder.callMethodInService(); } } - +``` + ##### 运行结果 ![](img/mix_start_service.png) @@ -277,18 +297,28 @@ #### 接口 > 利用接口屏蔽方法内部实现的细节, 只暴露需要暴露的方法. ##### IService +```java + public interface IService { void callMethodInService(); } +``` + ##### Service +```java + private class Mybind extends Binder implements IService { public void callMethodInService() { methodInService(); } } +``` + ##### Activity - private class MyServiceConnection implements ServiceConnection { +```java + + private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -300,14 +330,222 @@ } } +``` -### Service组件的三种通讯方式: -* `startService` -* `bindService` -* `AIDL(android interface definition language)` +### IntentService 类 +Service 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。 您只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务也许是最好的选择。 + +IntentService 执行以下操作: + +* 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。 +* 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。 +* 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。 +* 提供 onBind() 的默认实现(返回 null)。 +* 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。 + +综上所述,您只需实现 onHandleIntent() 来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。) + +```java + + public class HelloIntentService extends IntentService { + + /** + * A constructor is required, and must call the super IntentService(String) + * constructor with a name for the worker thread. + */ + public HelloIntentService() { + super("HelloIntentService"); + } + + /** + * The IntentService calls this method from the default worker thread with + * the intent that started the service. When this method returns, IntentService + * stops the service, as appropriate. + */ + @Override + protected void onHandleIntent(Intent intent) { + // Normally we would do some work here, like download a file. + // For our sample, we just sleep for 5 seconds. + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + // Restore interrupt status. + Thread.currentThread().interrupt(); + } + } + } +``` +您只需要一个构造函数和一个 onHandleIntent() 实现即可。如果您决定还重写其他回调方法(如 onCreate()、onStartCommand() 或 onDestroy()),请确保调用超类实现,以便 IntentService 能够妥善处理工作线程的生命周期。 + +### 执行多线程耗时操作Service + +正如上一部分中所述,使用 IntentService 显著简化了启动服务的实现。但是,若要求服务执行多线程(而不是通过工作队列处理启动请求),则可扩展 Service 类来处理每个 Intent。 + +为了便于比较,以下提供了 Service 类实现的代码示例,该类执行的工作与上述使用 IntentService 的示例完全相同。也就是说,对于每个启动请求,它均使用工作线程执行作业,且每次仅处理一个请求。 + +```java + + public class HelloService extends Service { + private Looper mServiceLooper; + private ServiceHandler mServiceHandler; + + // Handler that receives messages from the thread + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + // Normally we would do some work here, like download a file. + // For our sample, we just sleep for 5 seconds. + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + // Restore interrupt status. + Thread.currentThread().interrupt(); + } + // Stop the service using the startId, so that we don't stop + // the service in the middle of handling another job + stopSelf(msg.arg1); + } + } + + @Override + public void onCreate() { + // Start up the thread running the service. Note that we create a + // separate thread because the service normally runs in the process's + // main thread, which we don't want to block. We also make it + // background priority so CPU-intensive work will not disrupt our UI. + HandlerThread thread = new HandlerThread("ServiceStartArguments", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + + // Get the HandlerThread's Looper and use it for our Handler + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); + + // For each start request, send a message to start a job and deliver the + // start ID so we know which request we're stopping when we finish the job + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + mServiceHandler.sendMessage(msg); + + // If we get killed, after returning from here, restart + return START_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + // We don't provide binding, so return null + return null; + } + + @Override + public void onDestroy() { + Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); + } + } +``` + +正如您所见,与使用 IntentService 相比,这需要执行更多工作。 -### Service执行耗时操作 -#### IntentService +但是,因为是由您自己处理对 onStartCommand() 的每个调用,因此可以同时执行多个请求。此示例并未这样做,但如果您希望如此,则可为每个请求创建一个新线程,然后立即运行这些线程(而不是等待上一个请求完成)。 + +请注意,onStartCommand() 方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,IntentService 的默认实现将为您处理这种情况,不过您可以对其进行修改)。从 onStartCommand() 返回的值必须是以下常量之一: + +* **`START_NOT_STICKY`** + + 如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 +* **`START_STICKY`** + + 如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。 +* **`START_REDELIVER_INTENT`** + + 如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。 + +### 前台服务 + +```java + + public class ForegroundService extends Service { + + @Override + public void onCreate() { + super.onCreate(); + + showNotification(); + } + + private void showNotification() { + + //创建点击跳转Intent + Intent inten = new Intent(this, MainActivity.class); + //创建任务栈Builder + TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(this); + taskStackBuilder.addParentStack(MainActivity.class); + taskStackBuilder.addNextIntent(inten); + PendingIntent pendingIntent = taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + + //创建通知详细信息 + Notification notification = new NotificationCompat.Builder(this) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle("foreground service") + .setContentText("show details news") + .setWhen(System.currentTimeMillis()) + .setContentIntent(pendingIntent) + .build(); + + NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + nm.notify(0, notification); + startForeground(0,notification); + + } + } + +``` + +### 系统服务 + + +系统服务提供了很多便捷服务,可以查询Wifi、网络状态、查询电量、查询音量、查询包名、查询Application信息等等等相关多的服务,具体大家可以自信查询文档,这里举例几个常见的服务 + +1. 判断Wifi是否开启 + + ```java + + WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE); + boolean enabled = wm.isWifiEnabled(); + ``` + +2. 获取系统最大音量 + + + ```java + + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + int max = am.getStreamMaxVolume(AudioManager.STREAM_SYSTEM); + ``` +3. 获取当前音量 + + ```java + + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + int current = am.getStreamMaxVolume(AudioManager.STREAM_RING); + ``` +4. 判断网络是否有连接 + + ```java + + ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + boolean isAvailable = info.isAvailable(); + ``` +### AIDL跨进程服务 -### service服务运行在ForeGground \ No newline at end of file +### AccessibilityService无障碍服务 diff --git "a/showdy_note/android/Android345円233円233円345円244円247円347円273円204円344円273円266円344円271円213円Activity.md" "b/showdy_note/android/Android345円233円233円345円244円247円347円273円204円344円273円266円344円271円213円Activity.md" index fcbdfdc..41e5460 100644 --- "a/showdy_note/android/Android345円233円233円345円244円247円347円273円204円344円273円266円344円271円213円Activity.md" +++ "b/showdy_note/android/Android345円233円233円345円244円247円347円273円204円344円273円266円344円271円213円Activity.md" @@ -1,60 +1,157 @@ -### Activity -#### 和Activity生命周期有关的几个问题: -1. onSaveInstanceState方法在Activity的哪两个生命周期方法之间调用? - -2. 弹出一个Dialog时,onPause会调用吗?什么情况下会,什么情况下不会? - -3. 横竖屏切换的时候,生命周期方法是如何调用的?如何进行配置呢? +###Activity的生命周期 -4. Activity调用了onDestory方法,就会在Activity的任务栈消失吗? - -5. 永久性质的数据,应该在哪个生命周期方法中保存? - -6. 在onCreate或者onRestoreInstance方法中恢复数据时,有什么区别? -#### Activity的生命周期 ![](img/activity_lifecycle.png) - * Activity的创建和销毁 - * onCreate(); - > 执行Activity某些基本设置的一些代码,比如声明用户界面(xml文件),定义成员变量,配置某些UI等等,oncreate一般是必须要实现的. - * onDestroy(); - > 一般不需要实现,因为本地类引用与Activity一同销毁,并且您的Activity应在 onPause() 和 onStop() 期间执行大多数清理操作。 但是,如果您的Activity包含您在 onCreate() 期间创建的后台线程或其他如若未正确关闭可能导致内存泄露的长期运行资源,应在 onDestroy() 期间终止它们. + * **`onCreate()`**: 执行Activity某些基本设置的一些代码,比如声明用户界面(xml文件),定义成员变量,配置某些UI等等,oncreate一般是必须要实现的. + + * **`onDestroy()`**:一般不需要实现,因为本地类引用与Activity一同销毁,并且您的Activity应在 onPause() 和 onStop() 期间执行大多数清理操作。 但是,如果您的Activity包含您在 onCreate() 期间创建的后台线程或其他如若未正确关闭可能导致内存泄露的长期运行资源,应在 onDestroy() 期间终止它们. * Activity开始和停止 - * onStart(); - > onStop() 方法应基本清理所有Activity的资源,将需要在Activity重新开始时重新实例化它们。但是,还需要在Activity初次创建时重新实例化它们(没有Activity的现有实例)。出于此原因,应经常使用 onStart() 回调方法作为 onStop() 方法的对应部分,因为系统会在它创建您的Activity以及从停止状态重新开始Activity时调用 onStart() 。 - * onStop(); - > Activity收到 onStop() 方法的调用时,它不再可见,并且应释放几乎所有用户不使用时不需要的资源。 一旦您的Activity停止,如果需要恢复系统内存,系统可能会销毁该实例。 在极端情况下,系统可能会仅终止应用进程,而不会调用Activity的最终 onDestroy() 回调,因此您使用 onStop() 释放可能泄露内存的资源非常重要。尽管 onPause() 方法在 onStop()之前调用,您应使用 onStop() 执行更大、占用更多 CPU 的关闭操作,比如向数据库写入信息。 + * **`onStart()`**: onStop() 方法应基本清理所有Activity的资源,将需要在Activity重新开始时重新实例化它们。但是,还需要在Activity初次创建时重新实例化它们(没有Activity的现有实例)。出于此原因,应经常使用 onStart() 回调方法作为 onStop() 方法的对应部分,因为系统会在它创建您的Activity以及从停止状态重新开始Activity时调用 onStart() 。 + + * **`onStop()`**: Activity收到 onStop() 方法的调用时,它不再可见,并且应释放几乎所有用户不使用时不需要的资源。 一旦您的Activity停止,如果需要恢复系统内存,系统可能会销毁该实例。 在极端情况下,系统可能会仅终止应用进程,而不会调用Activity的最终 onDestroy() 回调,因此您使用 onStop() 释放可能泄露内存的资源非常重要。尽管 onPause() 方法在 onStop()之前调用,您应使用 onStop() 执行更大、占用更多 CPU 的关闭操作,比如向数据库写入信息。 + * Activity运行和暂停 - * onResume(); - > Activity获得焦点,实现onResume()初始化在 onPause() 期间释放的组件并且执行每当Activity进入"继续"状态时必须进行的任何其他初始化操作(比如开始动画和初始化只在Activity具有用户焦点时使用的组件)。 - * onPause(); - > 当系统为您的Activity调用 onPause() 时,它从技术角度看意味着您的Activity仍然处于部分可见状态,但往往说明用户即将离开Activity并且它很快就要进入"停止"状态。 您通常应使用 onPause() 回调: + * **`onResume():`** Activity获得焦点,实现onResume()初始化在 onPause() 期间释放的组件并且执行每当Activity进入"继续"状态时必须进行的任何其他初始化操作(比如开始动画和初始化只在Activity具有用户焦点时使用的组件)。 + + * **`onPause()`**: 当系统为您的Activity调用 onPause() 时,它从技术角度看意味着您的Activity仍然处于部分可见状态,但往往说明用户即将离开Activity并且它很快就要进入"停止"状态。 您通常应使用 onPause() 回调: * 停止动画或其他可能消耗 CPU 的进行之中的操作。 * 提交未保存的更改,但仅当用户离开时希望永久性保存此类更改(比如电子邮件草稿)。 * 释放系统资源,比如广播接收器、传感器手柄(比如 GPS) 或当您的Activity暂停且用户不需要它们时仍然可能影响电池寿命的任何其他资源。 * 但是要注意: **不得使用** onPause() 永久性存储用户更改(比如输入表格的个人信息)。 只有在您确定用户希望自动保存这些更改的情况(比如,电子邮件草稿)下,才能在 onPause()中永久性存储用户更改。但您应避免在 onPause() 期间执行 CPU 密集型工作,比如**向数据库写入信息**,因为这会拖慢向下一Activity过渡的过程(您应改为在 onStop()期间执行高负载关机操作。 - + + * 数据存储与恢复: -![](img/basic-lifecycle-savestate.png) + + ![](img/basic-lifecycle-savestate.png) 当系统开始停止您的Activity时,它会 调用 onSaveInstanceState() (1),因此,您可以指定您希望在 Activity 实例必须重新创建时保存的额外状态数据。如果Activity被销毁且必须重新创建相同的实例,系统将在 (1) 中定义的状态数据同时传递给 onCreate() 方法(2) 和 onRestoreInstanceState() 方法(3)。 - * onSaveInstanceState() - * 处于onstop()方法前, 但是与onpause()没有多少必然的联系 - * onRestoreInstanceState() - * 处于onresume前, - * 您可以选择实现系统在 onStart() 方法之后调用的 onRestoreInstanceState(),而不是在onCreate() 期间恢复状态。 系统只在存在要恢复的已保存状态时调用 onRestoreInstanceState() ,因此您无需检查 Bundle 是否为 null: + * **`onSaveInstanceState()`**:处于onstop()方法前, 但是与onpause()没有多少必然的联系,可能在onPause()前调用,也可能在onStop()前调用 + + * **`onRestoreInstanceState()`**: 处于onresume前,您可以选择实现系统在 onStart() 方法之后调用的 onRestoreInstanceState(),而不是在onCreate() 期间恢复状态。 系统只在存在要恢复的已保存状态时调用 onRestoreInstanceState() ,因此您无需检查 Bundle 是否为 null: * 还有一些的其他的生命周期方法: - * onPostCreate() - > 当activity建立后调用,即在onstart()和onRestoreInstanceState()完成后调用 - * onPostResume() - > onCreate->onStart->onPostCreate->onResume->onPostResume + * **`onPostCreate()`**: 当activity建立后调用,即在onstart()和onRestoreInstanceState()完成后调用 + * **`onPostResume()`**: onCreate->onStart->onPostCreate->onResume->onPostResume + +#### 和Activity生命周期有关的几个问题: + +1. onSaveInstanceState方法在Activity的哪两个生命周期方法之间调用? + + **onSaveInstanceState()的调用与onPause()的调用没有先后之分,可能在onStop()前,也可能在onPause()前,但是可以保证一定在onStop()之前.** + +2. 弹出一个Dialog时,onPause会调用吗?什么情况下会,什么情况下不会? + + **首先,弹出的是本Activity的Dialog,并不会有任何生命周期方法调用。Dialog是一个View,它本身就依附在Acitivty上,可以理解为是属于本Activity的,所以它的焦点也自然是本Activity的焦点,自然不会有什么生命周期方法调用了。如果其他Activity的Dialog弹出了,onPause才会调用。** + +3. 横竖屏切换的时候,生命周期方法是如何调用的?如何进行配置呢? + + **横竖屏切换时,如果不做任何配置,生命周期方法的回调顺序为: + `onPause–onSaveInstanceState–onStop–onDestory–onCreate–onStart–onResume` + 也就是说Activity被销毁并重建了。如果不想这样可以在清单文件中的Activity添加一行配置: + `android:configChanges="keyboardHidden|orientation|screenSize"`** + +4. Activity调用了onDestory方法,就会在Activity的任务栈消失吗? + + **如果是点击back键销毁Activity,相当于调用了Activity的finish(),将Activity从任务栈中退出,再调用onDestroy(),如果Activity是意外被销毁,直接调用onDestroy(),Activity是不会从任务栈中清除的.** + +5. 永久性质的数据,应该在哪个生命周期方法中保存? + + **由于系统在紧急情况必须内存,onPause(),onStop(),onDestroy()三个方法,唯一能保证调用的只有onPause()方法,其他两个方法可能不会调用,所以在此方法中做重要数据的持久化存储,但是要注意的是,onPause()是非常轻量级的,不能做耗时操作,而由于无法保证系统会调用 onSaveInstanceState(),只应利用它来记录 Activity 的瞬态(UI 的状态)而切勿使用它来存储持久性数据** + +6. 在onCreate或者onRestoreInstance方法中恢复数据时,有什么区别? + + 区别: onRestoreInstanceState()一旦被调用,其参数Bundle saveInstance一定是有值的,我们不需要额外判断是否为null,但是onCreate()却不行,onCreate()正常启动,其参数Bundle saveInstance()为null,这个需要额外去判断.官方建议采用onResotreInstanceState()去恢复数据. + + **Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。onRestoreInstanceState()在onStart() 和 onPostCreate(Bundle)之间调用。** + +7. 如果一个Activity在用户可见时才处理某个广播,不可见时注销掉,那么应该在哪两个生命周期的回调方法去注册和注销BroadcastReceiver呢? + **应在onStart()中注册,而在onStop()中注销;一般情况下会选择在onStop()和onDestroy()中进行资源释放的操作, onPause() 调用期间必须保留的信息有所选择,因为该方法中的任何阻止过程都会妨碍向下一个 Activity 的转变并拖慢用户体验。.** + +8. 如果有一些数据在Activity跳转时(或者离开时)要保存到数据库,那么你认为是在onPause好还是在onStop执行这个操作好呢? + + **Activity A启动 Activity B必须要经历的生命周期为: Activity A先调用onPause(),然后Activity B调用onCreate(),onStart(),onResume(),接着Activity A再调用onStop().故而,Activity A在跳转前需要先在onPause()中将数据持久化存储,以便Activity B可以调用.** + + +### Activity任务栈 + +* 任务栈: 指在执行特定作业时与用户交互的一系列Activity,这些Activity按照各自的打开顺序排列在堆栈(返回栈),任务栈是一种"先进后出"的栈结构. + +![这里写图片描述](http://img.blog.csdn.net/20170207161237738?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2hvd2R5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) + +#### Activity的启动模式: + +* **`standard`**:标准模式.系统默认的启动模式,每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否存在.谁启动了这Activity,那么这个Activity就运行在那个Activity所在的栈中.如果启动Activity时传入ApplicationContext会报错,因为非Activity类型Context并没有所谓的栈,解决这个问题需要给待启动Activity设置`FLAG_ACTIVITY_NEW_TASK`标记位,相当于以singleTask模式启动. + +* **`singleTop`**: 栈顶复用模式.如果新Activity已经为栈顶,那么Activity不会被重新创建,同时onNewIntent()会被调用,但是Activity的onCreate()和onStart()不会被调用;如果新的Activity存在但是不在栈顶,那么Activity仍然会重新创建. + +* **`singleTask`**: 栈内复用模式.只要Activity在一个栈中存在,多次启动Activity都不会重新创建实例,和singleTop一样,系统会调用onNewIntent().但是singleTask模式具有clearTop的效果,会导致栈内待启动Activity上面的Activity被出栈. + +* **`singleInstance`**: 单例模式.singleInstance是一种加强版的singleTask模式,具有此启动模式Activity单独存在一个栈内复用. + + +#### 启动模式应用场景: + +* 假如目前有2个任务栈,前台任务栈有12,而后台任务栈有XY,假设XY的启动模式均为singleTask,那么启动Y时,整个后台任务栈都会被切换到前台,这时后退列表就变为12XY,当点击back键时,列表中Activity会一一出栈. + +![这里写图片描述](http://img.blog.csdn.net/20170207161258425?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2hvd2R5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) + +* 但是启动的不是Y而是X,情况就不一样了. + +![这里写图片描述](http://img.blog.csdn.net/20170207161313864?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2hvd2R5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) + + + +#### 任务相关性 + +* **`TaskAffinity`**: 任务相关性. + + 这个参数标识一个Activity所需要的任务栈的名称,默认情况为应用包名.当然,可以为每个Activity指单独的TaskAffinity属性,属性名需和包名不同,否则没有意义.TaskAffinity属性主要和singleTask启动模式以及allowTaskReparenting属性配对使用,其他情况没有意义.另外任务栈分为前台任务栈和后台任务栈,后台任务栈中所有Activity处于暂停状态. + + 当TaskAffinity与singleTask启动模式使用时, 他具有该模式Activity目前任务栈的名称,待启动的Activity会运行在名字和TaskAffinity相同的任务栈. + + 当TaskAffinity与allowTaskReparenting结合使用时,情况比较复杂,会产生特殊的效果. + + +#### 清理任务栈 + +如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。可以使用下列几个 Activity 属性修改此行为: + +* **`alwaysRetainTaskState`** + + 如果在任务的根 Activity 中将此属性设置为 "true",则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。 +* **`clearTaskOnLaunch`** + + 如果在任务的根 Activity 中将此属性设置为 "true",则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与 alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。 +* **`finishOnTaskLaunch`** + + 此属性类似于 clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。 此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 设置为 "true" 时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。 + + +#### Activity的Flags + +* **`FLAG_ACTIVITY_NEW_TASK`**: + + 该标记的作用是为Activity指定"singleTask"启动模式,效果和XML中指定一样. service中启动activity需要用到此标记. + +* **`FLAG_ACTIVITY_SINGLE_TOP`**: + + 该标记的作用是为Activity指定"singleTop"启动模式,效果和XML中指定一样. + +* **`FLAG_ACTIVITY_CLEAR_TOP`**: + + 具有此标记的Activity,当启动时,在同一个任务栈中所有位于他上面的Activity都要被清除出栈,此标记一般与singleTask启动模式一起使用.在这种情况下,若被启动Activity的实例已经存在,那么系统会调用onNewIntent.如果被启动Activity采用的standard模式,那么连同他之上的activity都要出栈,系统会创建新的activity实例放入栈顶. + + + +### 参考 +* [**ANDROID INSTANCESTATE**](http://stormzhang.com/android/2014/02/21/android-instancestate/) +* [**面试题: 怎么理解Activity的生命周期?**](http://www.jianshu.com/p/ae6e1d93cc8e) +* [**Activity的生命周期,你足够了解吗?**](http://blog.csdn.net/melodev/article/details/52075141) -### Activity任务栈 \ No newline at end of file diff --git "a/showdy_note/android/AsyncTask344円275円277円347円224円250円345円217円212円346円272円220円347円240円201円345円210円206円346円236円220円.md" "b/showdy_note/android/AsyncTask344円275円277円347円224円250円345円217円212円346円272円220円347円240円201円345円210円206円346円236円220円.md" new file mode 100644 index 0000000..b18f509 --- /dev/null +++ "b/showdy_note/android/AsyncTask344円275円277円347円224円250円345円217円212円346円272円220円347円240円201円345円210円206円346円236円220円.md" @@ -0,0 +1,465 @@ +## AsyncTask + +### AsyncTask使用注意事项: +* `AsyncTask`只能被执行(`execute`方法)一次,多次执行将会引发异常. +* 任务的取消只能打了一个标记,并不是真正取消,需要手动去掉用; + +### 构建AsyncTask抽象类的三个泛型参数; +* `AsyncTask`是一个抽象类,通常用于被继承.继承AsyncTask需要指定如下三个泛型参数: + * `Params`:启动任务时输入的参数类型. + * `Progress`:后台任务执行中返回进度值的类型. + * `Result`:后台任务执行完成后返回结果的类型. +### AsyncTask四个方法: +* `onPreExecute()`; + > 执行任务之前,一般做变量的初始化,或者ui的隐藏或者显示;该方法无参数 +* `doInBackground(T...params)`;//对应泛型参数params +> 后台执行任务,属于子线程,当调用publishProgress()方法时,会触发系统自动调用onProgressUpdate(); +* `onProgressUpdate(T... values)`;//对应泛型参数progress +> 用于更新进度 +* `onPostExecute(T...result)`;//对应泛型参数result +> 任务结束后调用,一般处理返回的结果,或者改变ui显示. +### AsyncTask历史版本问题: + +在Android1.6之前,AsyncTask是串行执行任务,Android1.6时候AsyncTask开始采用线程池处理并行任务,但是从Android3.0开始,为了避免AsyncTask带来的并发错误,又采用线程池串行执行任务,尽管此处,Android3.0后,AsyncTask还是支持并发执行任务,不过需要调用`executeOnExecutor()`方法. + +### 加载网络图片的实例: + +```java + public class ImageActivity extends Activity { + private ImageView imageView ; + private ProgressBar progressBar ; + private static String URL = "http://pic3.zhongsou.com/image/38063b6d7defc892894.jpg"; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.image); + imageView = (ImageView) findViewById(R.id.image); + progressBar = (ProgressBar) findViewById(R.id.progressBar); + //通过调用execute方法开始处理异步任务.相当于线程中的start方法. + new MyAsyncTask().execute(URL); + } + + class MyAsyncTask extends AsyncTask { + + //onPreExecute用于异步处理前的操作 + @Override + protected void onPreExecute() { + super.onPreExecute(); + //此处将progressBar设置为可见. + progressBar.setVisibility(View.VISIBLE); + } + + //在doInBackground方法中进行异步任务的处理. + @Override + protected Bitmap doInBackground(String... params) { + //获取传进来的参数 + String url = params[0]; + Bitmap bitmap = null; + URLConnection connection ; + InputStream is ; + try { + connection = new URL(url).openConnection(); + is = connection.getInputStream(); + //为了更清楚的看到加载图片的等待操作,将线程休眠3秒钟. + Thread.sleep(3000); + BufferedInputStream bis = new BufferedInputStream(is); + //通过decodeStream方法解析输入流 + bitmap = BitmapFactory.decodeStream(bis); + is.close(); + bis.close(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return bitmap; + } + + //onPostExecute用于UI的更新.此方法的参数为doInBackground方法返回的值. + @Override + protected void onPostExecute(Bitmap bitmap) { + super.onPostExecute(bitmap); + //隐藏progressBar + progressBar.setVisibility(View.GONE); + //更新imageView + imageView.setImageBitmap(bitmap); + } + } + } +``` + + + +### 模拟加载进度条: + +```java + + public class ProgressActivity extends Activity{ + private ProgressBar progressBar; + private MyAsyncTask myAsyncTask; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.progress); + progressBar = (ProgressBar) findViewById(R.id.progress); + myAsyncTask = new MyAsyncTask(); + //启动异步任务的处理 + myAsyncTask.execute(); + } + + //AsyncTask是基于线程池进行实现的,当一个线程没有结束时,后面的线程是不能执行的. + @Override + protected void onPause() { + super.onPause(); + if (myAsyncTask != null && myAsyncTask.getStatus() == Status.RUNNING) { + //cancel方法只是将对应的AsyncTask标记为cancelt状态,并不是真正的取消线程的执行. + myAsyncTask.cancel(true); + } + } + + class MyAsyncTask extends AsyncTask{ + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + //通过publishProgress方法传过来的值进行进度条的更新. + progressBar.setProgress(values[0]); + } + + @Override + protected Void doInBackground(Void... params) { + //使用for循环来模拟进度条的进度. + for (int i = 0;i < 100; i ++){ + //如果task是cancel状态,则终止for循环,以进行下个task的执行. + if (isCancelled()){ + break; + } + //调用publishProgress方法将自动触发onProgressUpdate方法来进行进度条的更新. + publishProgress(i); + try { + //通过线程休眠模拟耗时操作 + Thread.sleep(300); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return null; + } + } + } +``` + +------------------------------------------------------------------------ + +### AsyncTask的源码解析: + +AsyncTask的使用分两步: + +```java + + myAsyncTask = new MyAsyncTask(); + //启动异步任务的处理 + myAsyncTask.execute(); + +``` + +从执行起步任务的起始点开始, 进入execute()方法: + +```java + + public final AsyncTask execute(Params... params) { + return executeOnExecutor(sDefaultExecutor, params); + } + + @MainThread + public final AsyncTask executeOnExecutor(Executor exec, + Params... params) { + if (mStatus != Status.PENDING) { + switch (mStatus) { + case RUNNING: + throw new IllegalStateException("Cannot execute task:" + + " the task is already running."); + case FINISHED: + throw new IllegalStateException("Cannot execute task:" + + " the task has already been executed " + + "(a task can be executed only once)"); + } + } + + mStatus = Status.RUNNING; //状态改变执行中 + onPreExecute(); //执行前准备工作 + mWorker.mParams = params;//赋值 + exec.execute(mFuture); //执行异步任务 + return this; + } + +``` +一个异步进入,先判断该任务是否在执行,或者执行完毕,如果是,则抛出异常,说明一个任务只能被执行一次.否则,将任务状态改变为RUNNING,并将传进来的params传给mWorker, 那么mWorker是什么?,继续看源码: + + +```java + + public AsyncTask() { + mWorker = new WorkerRunnable() { + public Result call() throws Exception { + mTaskInvoked.set(true); + Result result = null; + try { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + //noinspection unchecked + result = doInBackground(mParams); + Binder.flushPendingCommands(); + } catch (Throwable tr) { + mCancelled.set(true); + throw tr; + } finally { + postResult(result); + } + return result; + } + }; + + mFuture = new FutureTask(mWorker) { + @Override + protected void done() { + try { + postResultIfNotInvoked(get()); + } catch (InterruptedException e) { + android.util.Log.w(LOG_TAG, e); + } catch (ExecutionException e) { + throw new RuntimeException("An error occurred while executing doInBackground()", + e.getCause()); + } catch (CancellationException e) { + postResultIfNotInvoked(null); + } + } + }; + } + + private static abstract class WorkerRunnable implements Callable { + Params[] mParams; + } +``` +可以看到mWorker在构造方法中完成了初始化工作,因为是个抽象类,就new了一个具体子类,实现call方法,并将原子类变量mTaskInvoked=true,最后调用doInbackGround(mParams),并将返回的result作为参数给postResult(),在postResult()方法中: + +```java + + private Result postResult(Result result) { + @SuppressWarnings("unchecked") + Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, + new AsyncTaskResult(this, result)); + message.sendToTarget(); + return result; + } + +``` +在postReuslt()中可以看到,采用异步消息机制,发送一个message.what为MESSAGE_POST_RESULT的消息,将异步执行的结果切换到主线,完成线程的切换工作. + +```java + + private static class InternalHandler extends Handler { + public InternalHandler() { + super(Looper.getMainLooper()); + } + + @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) + @Override + public void handleMessage(Message msg) { + AsyncTaskResult result = (AsyncTaskResult) msg.obj; + switch (msg.what) { + case MESSAGE_POST_RESULT: + // There is only one result + result.mTask.finish(result.mData[0]); + break; + case MESSAGE_POST_PROGRESS: + result.mTask.onProgressUpdate(result.mData); + break; + } + } + } + +``` + +可以看到Handler使用的是Looper.getMainLooper,说明Handler接收消息发生主线程,收到MESSAGE_POST_RESULT执行finish(): + +```java + + private void finish(Result result) { + if (isCancelled()) { + onCancelled(result); + } else { + onPostExecute(result); + } + mStatus = Status.FINISHED; + } + +``` + +可以看到在finish()对异步任务是否取消做了判断,如果异步任务已经取消,则调用onCancel()方法,否则调用onPostResult(),这里也可以看到当异步任务被取消后, onPostExecute()是不会执行.最后将状态置为FINISHED.构造函数仅仅只是完成了mWorker的初始化工作,并采用FutureTask将mWorker包装了下,并未真正执行,当然在任务执行结束后,会调用postResultNotInvoked(get()),来查看任务是否已经执行: + +```java + + private void postResultIfNotInvoked(Result result) { + final boolean wasTaskInvoked = mTaskInvoked.get(); + if (!wasTaskInvoked) { + postResult(result); + } + } + +``` +如果mTaskInvoked不为true,则postResult(),但是mWorker初始化的时,就已经将mTaskResult置为true,所以这个方法不会调用. + +下面看看 `exec.execute(mFuture); `到底做了什么: + +```java + + public final AsyncTask execute(Params... params) { + return executeOnExecutor(sDefaultExecutor, params); + } + +``` + +知道exec实际上是`sDefaultExecutor`: + +```java + + public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); + + private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; + + private static class SerialExecutor implements Executor { + final ArrayDeque mTasks = new ArrayDeque(); + Runnable mActive; + + public synchronized void execute(final Runnable r) { + mTasks.offer(new Runnable() { + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (mActive == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((mActive = mTasks.poll()) != null) { + THREAD_POOL_EXECUTOR.execute(mActive); + } + } + } + +``` + +sDefaultExecutor实际上SerialExecutor的一个实例,其内部维护一个双端队列(数组实现),执行execute(),会调用offer()将任务放入队列尾部, 然后判断mActive否为空,如果为null,则调用scheduleNext()从队列尾部取出一个任务,调用THREAD_POOL_EXECUTOR真正执行任务: + +```java + + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + private static final int KEEP_ALIVE_SECONDS = 30; + + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); + } + }; + + private static final BlockingQueue sPoolWorkQueue = + new LinkedBlockingQueue(128); + + public static final Executor THREAD_POOL_EXECUTOR; //可以并行执行的线程池 + + static { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, + sPoolWorkQueue, sThreadFactory); + threadPoolExecutor.allowCoreThreadTimeOut(true); + THREAD_POOL_EXECUTOR = threadPoolExecutor; + } + +``` + +线程池最大支持`CUP_COUNT*2+1`的线程并发,加上长度为128的阻塞队列,如果任务的数量为`CUP_COUNT*2+1+128`是否会因为任务数过多而抛出异常,实际上不可能,再来先看下SerialExecutor这个类: + +```java + private static class SerialExecutor implements Executor { + final ArrayDeque mTasks = new ArrayDeque(); + Runnable mActive; + + public synchronized void execute(final Runnable r) { + mTasks.offer(new Runnable() { + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (mActive == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((mActive = mTasks.poll()) != null) { + THREAD_POOL_EXECUTOR.execute(mActive); + } + } + } + + +``` +如果此时有10个任务同时调用execute()方法,第个任务加入队列,mActive=null,从队列尾部取出一个任务,然后交给线程池去执行,然后第二任务入队,但是此时mActive不为null,不会执行scheduleNext()方法,只能等待第一个任务执行完毕,再调用scheduleNext().内部虽然是个线程池,但是确实个串行的线程池. + +那么AysncTask是否就不能并发执行任务呢? 其实AsyncTask是可以并发执行任务的,不过要调用 + +```java + + public final AsyncTask executeOnExecutor(Executor exec, Params... params) { + .... + } + +``` +然后自己传入一个可以并发执行任务的线程池,比如AsyncTask提供的THREAD_POOL_EXECUTOR.在AsyncTaskCompat中: + +```java + + public static AsyncTask executeParallel( + AsyncTask task, + Params... params) { + if (task == null) { + throw new IllegalArgumentException("task can not be null"); + } + + if (Build.VERSION.SDK_INT>= 11) { + // From API 11 onwards, we need to manually select the THREAD_POOL_EXECUTOR + AsyncTaskCompatHoneycomb.executeParallel(task, params); + } else { + // Before API 11, all tasks were run in parallel + task.execute(params); + } + + return task; + } + + class AsyncTaskCompatHoneycomb { + + static void executeParallel( + AsyncTask task, + Params... params) { + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); + } + + } + +``` + +可以看出在API 11之前调用`execute()`就表示并发执行任务,在API需要调用`executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params)`来执行并发任务. diff --git "a/showdy_note/android/AsyncTask347円232円204円344円275円277円347円224円250円.md" "b/showdy_note/android/AsyncTask347円232円204円344円275円277円347円224円250円.md" deleted file mode 100644 index 4a20fb5..0000000 --- "a/showdy_note/android/AsyncTask347円232円204円344円275円277円347円224円250円.md" +++ /dev/null @@ -1,150 +0,0 @@ -## AsyncTask的基本使用 - -### AsyncTask使用注意事项: -* `AsyncTask`只能被执行(`execute`方法)一次,多次执行将会引发异常. -* 任务的取消只能打了一个标记,并不是真正取消,需要手动去掉用; - -### 构建AsyncTask抽象类的三个泛型参数; -* `AsyncTask`是一个抽象类,通常用于被继承.继承AsyncTask需要指定如下三个泛型参数: - * `Params`:启动任务时输入的参数类型. - * `Progress`:后台任务执行中返回进度值的类型. - * `Result`:后台任务执行完成后返回结果的类型. -### AsyncTask四个方法: -* `onPreExecute()`; - > 执行任务之前,一般做变量的初始化,或者ui的隐藏或者显示;该方法无参数 -* `doInBackground(T...params)`;//对应泛型参数params -> 后台执行任务,属于子线程,当调用publishProgress()方法时,会触发系统自动调用onProgressUpdate(); -* `onProgressUpdate(T... values)`;//对应泛型参数progress -> 用于更新进度 -* `onPostExecute(T...result)`;//对应泛型参数result -> 任务结束后调用,一般处理返回的结果,或者改变ui显示. - -### 加载网络图片的实例: - -```java - public class ImageActivity extends Activity { - private ImageView imageView ; - private ProgressBar progressBar ; - private static String URL = "http://pic3.zhongsou.com/image/38063b6d7defc892894.jpg"; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.image); - imageView = (ImageView) findViewById(R.id.image); - progressBar = (ProgressBar) findViewById(R.id.progressBar); - //通过调用execute方法开始处理异步任务.相当于线程中的start方法. - new MyAsyncTask().execute(URL); - } - - class MyAsyncTask extends AsyncTask { - - //onPreExecute用于异步处理前的操作 - @Override - protected void onPreExecute() { - super.onPreExecute(); - //此处将progressBar设置为可见. - progressBar.setVisibility(View.VISIBLE); - } - - //在doInBackground方法中进行异步任务的处理. - @Override - protected Bitmap doInBackground(String... params) { - //获取传进来的参数 - String url = params[0]; - Bitmap bitmap = null; - URLConnection connection ; - InputStream is ; - try { - connection = new URL(url).openConnection(); - is = connection.getInputStream(); - //为了更清楚的看到加载图片的等待操作,将线程休眠3秒钟. - Thread.sleep(3000); - BufferedInputStream bis = new BufferedInputStream(is); - //通过decodeStream方法解析输入流 - bitmap = BitmapFactory.decodeStream(bis); - is.close(); - bis.close(); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return bitmap; - } - - //onPostExecute用于UI的更新.此方法的参数为doInBackground方法返回的值. - @Override - protected void onPostExecute(Bitmap bitmap) { - super.onPostExecute(bitmap); - //隐藏progressBar - progressBar.setVisibility(View.GONE); - //更新imageView - imageView.setImageBitmap(bitmap); - } - } - } -``` - - - -### 模拟加载进度条: - -```java - - public class ProgressActivity extends Activity{ - private ProgressBar progressBar; - private MyAsyncTask myAsyncTask; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.progress); - progressBar = (ProgressBar) findViewById(R.id.progress); - myAsyncTask = new MyAsyncTask(); - //启动异步任务的处理 - myAsyncTask.execute(); - } - - //AsyncTask是基于线程池进行实现的,当一个线程没有结束时,后面的线程是不能执行的. - @Override - protected void onPause() { - super.onPause(); - if (myAsyncTask != null && myAsyncTask.getStatus() == Status.RUNNING) { - //cancel方法只是将对应的AsyncTask标记为cancelt状态,并不是真正的取消线程的执行. - myAsyncTask.cancel(true); - } - } - - class MyAsyncTask extends AsyncTask{ - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - //通过publishProgress方法传过来的值进行进度条的更新. - progressBar.setProgress(values[0]); - } - - @Override - protected Void doInBackground(Void... params) { - //使用for循环来模拟进度条的进度. - for (int i = 0;i < 100; i ++){ - //如果task是cancel状态,则终止for循环,以进行下个task的执行. - if (isCancelled()){ - break; - } - //调用publishProgress方法将自动触发onProgressUpdate方法来进行进度条的更新. - publishProgress(i); - try { - //通过线程休眠模拟耗时操作 - Thread.sleep(300); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - return null; - } - } - } -``` - ------------------------------------------------------------------------- - -### AsyncTask的进阶学习: diff --git a/showdy_note/android/BitmapFactory_Options.md b/showdy_note/android/BitmapFactory_Options.md index 1fa0ee7..2761f3e 100644 --- a/showdy_note/android/BitmapFactory_Options.md +++ b/showdy_note/android/BitmapFactory_Options.md @@ -1,9 +1,13 @@ -### inPreferredConfig(建议的配置参数) -**[位图解码](http://blog.csdn.net/ccpat/article/details/46834089)** +## BitmapFactory.options +BitmapFactory.Options类是BitmapFactory对图片进行解码时使用的一个配置参数类,其中定义了一系列的public成员变量,每个成员变量代表一个配置参数。 + +### 图片解码建议配置(inPreferredConfig) +* 参数inpreferredconfig表示图片解码时使用的颜色模式,也就是图片中每个像素颜色的表示方式 * 图片颜色: * 计算机表示一个颜色都需要将颜色对应到一个颜色空间中的某个颜色值,常见的颜色空间为RGB,CMYK等. * JPEG格式支持RGB,CMYK,而PNG支持RGB,此外绝大数显示器只支持RGB颜色的输入,计算机显示一张图片时,如果图片本身不是RGB颜色空间编码,需将其转化为RGB颜色空间的颜色后在显示,所以非RGB显示会有失真. + * 颜色透明度: * 图片包含颜色信息和透明信息,计算机中用一个单独的透明通道表示(Alpha通道),JPEG格式图片不支持透明度,PNG/GIT格式支持透明度. @@ -13,10 +17,10 @@ * inperferredConfig参数 * BitmapFactory.Options类是BitmapFractory对图片进行解码时使用的配置参数类, 其中定义一系列public的成员变量(配置参数),inperferredConfig表示图片解码时使用的颜色模式: * inpreferredConfig参数有四个值: - * ALPHA_8: 每个像素用8个byte存储,存储的是图片的透明值 - * RGB_565:每个像素用16个byte存储,分别为5-R,6-G,5-B通道. - * ARGB-4444:每个像素用16个byte存储,即每个通道用4位表示 - * ARGB_8888:每个像素用32个byte存储,每个通道用8位表示. + * ALPHA_8: 每个像素用占8位,存储的是图片的透明值,占1个字节 + * RGB_565:每个像素用占16位,分别为5-R,6-G,5-B通道,占2个字节 + * ARGB-4444:每个像素占16位,即每个通道用4位表示,占2个字节 + * ARGB_8888:每个像素占32位,每个通道用8位表示,占4个字节 * 图片解码时,默认使用ARGB_8888模式: @@ -25,9 +29,11 @@ * 使用inperferredConfig注意: - * 如果inPreferredConfig不为null,解码器会尝试使用此参数指定的颜色模式来对图片进行解码,如果inPreferredConfig为null或者在解码时无法满足此参数指定的颜色模式,解码器会自动根据原始图片的特征以及当前设备的屏幕位深,选取合适的颜色模式来解码,例如,如果图片中包含透明度,那么对该图片解码时使用的配置就需要支持透明度,默认会使用ARGB_8888来解码。 + * 如果inPreferredConfig不为null,解码器会尝试使用此参数指定的颜色模式来对图片进行解码,如果inPreferredConfig为null或者在解码时无法满足此参数指定的颜色模式,解码器会自动根据**原始图片的特征**以及**当前设备的屏幕位深**,选取合适的颜色模式来解码,例如,如果图片中包含透明度,那么对该图片解码时使用的配置就需要支持透明度,默认会使用ARGB_8888来解码。 * 根据inperferredConfig的解析,就会发现如下bm1,bm2,bm3结果会一样: + ```java + InputStream stream = getAssets().open(file); Options op1 = new Options(); op1.inPreferredConfig = Config.ALPHA_8; @@ -38,6 +44,8 @@ Options op3 = new Options(); op3.inPreferredConfig = Config.ARGB_8888; Bitmap bm3 = BitmapFactory.decodeStream(stream, null, op3); + ``` + * 疑点: 1. 当出现不满足情况时,使用的合适配置是如何选取的? * 1. 如果inPreferredConfig为null,解码时使用的颜色模式会根据图片源文件的类型进行选取,如果图片文件的颜色模式为CMYK,或RGB565,则选取RGB_565。如果是其他类型,则选取ARGB_8888。 * 2. 如果inPreferredConfig指定的选项在解码时无法满足,并不会再根据图片文件的类型来选取合适的选项,而是直接使用ARGB_8888选项来解码。例如,图片源文件为RGB566编码的BMP图片,使用ALPHA_8选项来解码时属于不满足的情况,这时会选取ARGB_8888选项来解码,而不是选取RGB565。和inPreferredConfig为null时选取的"合适的"选项并不相同。 @@ -47,10 +55,7 @@ * 所有情况下ALPHA_8配置都不满足 * 绝大多数情况下RGB565选项都不满足 -### inPremultiplied -### inBitmap -[**Bitmap的内存管理**](http://hukai.me/android-training-course-in-chinese/graphics/displaying-bitmaps/manage-memory.html) - +### 优化Bitmap的内存使用(inBitmap) * 在Android 2.2 (API level 8)以及之前,当垃圾回收发生时,应用的线程是会被暂停的,这会导致一个延迟滞后,并降低系统效率。 从Android 2.3开始,添加了并发垃圾回收的机制, 这意味着在**一个Bitmap不再被引用之后,它所占用的内存会被立即回收**。 * 在Android 2.3.3 (API level 10)以及之前, **一个Bitmap的像素级数据(pixel data)是存放在Native内存空间中的**。 这些数据与Bitmap本身所占内存是隔离的,**Bitmap本身被存放在Dalvik堆中**。我们无法预测在Native内存中的像素级数据何时会被释放,这意味着程序容易超过它的内存限制并且崩溃。 **自Android 3.0 (API Level 11)开始, 像素级数据则是与Bitmap本身一起存放在Dalvik堆中**。 @@ -58,22 +63,53 @@ * 在Android 2.3.3 (API level 10) 以及更低版本上,**推荐使用recycle()方法**。 如果在应用中显示了大量的Bitmap数据,我们很可能会遇到OutOfMemoryError的错误。 recycle()方法可以使得程序更快的释放内存。>Caution:只有当我们确定这个Bitmap不再需要用到的时候才应该使用recycle()。在执行recycle()方法之后,如果尝试绘制这个Bitmap, 我们将得到"Canvas: trying to use a recycled bitmap"的错误提示。 -* 从Android 3.0 (API Level 11)开始,引进了**BitmapFactory.Options.inBitmap**字段。 如果使用了这个设置字段,decode方法会在加载Bitmap数据的时候去重用已经存在的Bitmap。这意味着Bitmap的内存是被重新利用的,这样可以提升性能,并且减少了内存的分配与回收。 - * You should still always use the returned Bitmap of the decode method and not assume that reusing the bitmap worked, due to the constraints outlined above and failure situations that can occur. - - * 总是使用解码方法,因为不能保证重用的bitmap会起作用,(例如,位图大小不匹配就无法重用) - - * As of {@link android.os.Build.VERSION_CODES#KITKAT}, any mutable bitmap can be reused by {@link BitmapFactory} to decode any other bitmaps as long as the resulting {@link Bitmap#getByteCount() byte count} of the decoded bitmap is less than or equal to the {@link Bitmap#getAllocationByteCount() allocated byte count} of the reused bitmap. - * 版本4.4后,任何mutable的图片都可以被BitmapFactory重用成任何其他的位图,只要源位图的大小(Bitmap.getByteCount())比重用位图(Bitmap.getAllocateByteCount)小或者相等 - * 版本KITKAT前,重用的位图大小必须和源位图大小相同,而且位图格式必须是JPEG或者PNG(无论是流形式还是资源形式图片) - * 如果重用位图设置了Bitmap.Config.configuration将会覆盖inperferredConfig的设置,如果有的话. +* 从Android 3.0 (API Level 11)开始,引进了**BitmapFactory.Options.inBitmap**字段。如果这个值被设置了,decode方法会在加载内容的时候去reuse已经存在的bitmap. 这意味着bitmap的内存是被reused的,这样可以提升性能, 并且减少了内存的allocation与de-allocation. + * reused的bitmap必须和原数据内容大小一致, 并且是JPEG 或者 PNG 的格式 (或者是某个resource 与 stream). + * reused的bitmap的configuration值如果有设置,则会覆盖掉inPreferredConfig值. + * 你应该总是使用decode方法返回的bitmap, 因为你不可以假设reusing的bitmap是可用的(例如,大小不对). + + ```java -### inJustDecodeBounds / inSmapleSize -[**高效加载大图片**](http://hukai.me/android-training-course-in-chinese/graphics/displaying-bitmaps/load-bitmap.html ) + private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { + // inBitmap only works with mutable bitmaps, so force the decoder to + // return mutable bitmaps. + options.inMutable = true; + if (cache != null) { + // Try to find a bitmap to use for inBitmap. + Bitmap inBitmap = cache.getBitmapFromReusableSet(options); + if (inBitmap != null) { + // If a suitable bitmap has been found, + // set it as the value of inBitmap. + options.inBitmap = inBitmap; + } + } + } + + static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { + if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.KITKAT) { + // From Android 4.4 (KitKat) onward we can re-use + // if the byte size of the new bitmap is smaller than + // the reusable bitmap candidate + // allocation byte count. + int width = targetOptions.outWidth / targetOptions.inSampleSize; + int height = targetOptions.outHeight / targetOptions.inSampleSize; + int byteCount = width * height * getBytesPerPixel(candidate.getConfig()); + return byteCount <= candidate.getAllocationByteCount(); + } + // On earlier versions, + // the dimensions must match exactly and the inSampleSize must be 1 + return candidate.getWidth() == targetOptions.outWidth + && candidate.getHeight() == targetOptions.outHeight + && targetOptions.inSampleSize == 1; + } + ``` +### 高效加载大图片(inJustDecodeBounds / inSmapleSize) * 如果设置为true,将不返回bitmap, 但是Bitmap的outWidth,outHeight等属性将会赋值,允许调用查询Bitmap,而不需要为Bitmap分配内存. * 例如加载一张很大的位图, 如果直接解码会造成OOM,做法是: * 1.先拿到位图的尺寸后,进行放缩后再加载位图 + + ```java BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; @@ -81,8 +117,11 @@ int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType; + ``` * 2.计算inSampleSize + ```java + public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; @@ -104,9 +143,12 @@ return inSampleSize; } + ```>**设置inSampleSize为2的幂是因为解码器最终还是会对非2的幂的数进行向下处理,获取到最靠近2的幂的数。详情参考inSampleSize的文档** * 3.放缩后再加载小位图: - + + ```java + public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { @@ -122,13 +164,110 @@ options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } - + ``` +### inPremultiplied +* 如果设置了true(默认是true),那么返回的图片RGB都会预乘透明通道A后的颜色 +* 系统View或者Canvas绘制图片,不建议设置为fase,否则会抛出异常,这是因为系统会假定所有图像都预乘A通道的已简化绘制时间. +* 设置inPremultiplied的同时,设置inScale会导致绘制的颜色不正确. + +### inDither +设置是否抖动处理图片. + ### inMutable -> 如果设置为true,将返回一个mutable的bitmap,可用于修改BitmapFactory加载而来的bitmap's effects. + 如果设置为true,将返回一个mutable的bitmap,可用于修改BitmapFactory加载而来的bitmap. + +* `BitmapFactory.decodeResource(Resources res, int id)`获取到的bitmap是mutable的,而`BitmapFactory.decodeFile(String path)`获取到的是immutable的 +* 可以使用`Bitmap copy(Config config, boolean isMutable)`获取mutable位图用于修改位图pixels. + + +### inDesity + +* 设置位图的像素密度,即每英寸有多少个像素 +* 如果inScale设置了,同时inDensity的值和inTargetDensity不同时,这个时候图片将缩放位inTartgetDensity指定的值. +* 如果设置为0,则`BitmapFactory.decodeResource(Resources,int)`和`BitmapFactory.decodeResource(Resources, int,BitmapFactory.Options)`,`BitmapFactory.decodeResourceStream()` 将`inTargetDensity`用`DisplayMetrics.densityDpi`来设置,其它函数则不会对bitmap进行任何缩放。 + +###inTargetDensity: +* 设置绘制位图的屏幕密度,与inScale和inDesity一起使用,来对位图进行放缩. +* 如果设置为0, `BitmapFactory.decodeResource(Resources,int)`, `BitmapFactory.decodeResource(Resources, int, BitmapFactory.Options)`,`BitmapFactory.decodeResourceStream()`将按照DisplayMetrics的density处理. + +### inScreenDensity +* 表示正在使用的实际屏幕的像素密度.纯粹用于运行在兼容性代码中的应用程序,其中inTargetDensity实际上是看到的应用程序的密度,而非真正的屏幕密度. +* inDesity, inTargetDensity,inScreenDensity这三个参数主是确定是否需要对bitmap进行缩放处理,如果缩放,缩放后的W和H应该是多少,缩放比例主要是通过:InTargetDenisity/inDensity作为缩放比例。 + +### inScale +* 当inScale设置为true时,且inDenstiy和inTargetDensity也不为0时,位图将在加载时(解码)时放缩去匹配inTargetDensity,在绘制到canvas时不会依赖图像系统放缩. +* BitmapRegionDecoder会忽略这个标记. +* 此标记默认为true,如果需要非放缩的位图,可以设置为false,9-patch图片会忽略这标记而自动放缩适配. +* 如果inPremultipled设置为false,并且图片有A通道,设置这个标记为true,会导致位图出现不正确的颜色. + + + +### inTargetDensity,inScale,inDesity之间的关系: +说三者之间的关系前,先谈下系统位图放缩规则,做个试验(使用小米3作为测试机):将一张144*144的ic_lanucher.png(系统默认在xxhdpi包下)分别放置在hdpi,xhdpi,xxhdpi三个文件夹,打印出位图的大小. + + ```java + + Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.ic_launcher); + Log.d(TAG, "size:" + bitmap.getByteCount()); + ``` + + ```java + + hdpi包size:331776 + xhdpi包size:186624 + xxhdpi包size:82944 + + ``` + +我们知道一张`144*144`的 ic_lanucher.png所占的实际内存为 `144*144*4=82944`字节,那么为什么同一张图片放在不同包下表现不一样的大小? + +屏幕密度与Drawable目录有着如下的关系: + +|目录| 屏幕密度| +| :------------ |:---------------| +|drawable-ldpi| 120dpi| +|drawable-mdpi |160dpi| +|drawable-hdpi |240dpi| +|drawable-xhdpi|320dpi| +|drawable-xxhdpi|480dpi| + +当使用decodeResuore()解码drawable目录下的图片时, 会根据手机的屏幕密度,到对应的文件夹中查找图片,如果图片存在于其他目录,则会对该图片进行放缩处理在显示,放缩处理的规则: + +**`scale= 设备屏幕密度/drawable目录设定的屏幕密度`** + +**`图片内存=int(图片长度*scale+0.5f)* int(图片宽度*scale)*单位像素占字节数`** + +由于实验使用的小米3,屏幕密度为480,则当图片放入在hdpi时:`scale= 480/240;` +图片放入xhdpi:`scale=480/320`; +图片放入xxhdpi时:`scale= 480/480`; + +说完系统加载位图使用的放缩规则后,再来说说这三个标记之间的关系: + +inDesity: 位图使用的像素密度 +inTargetDesity: 设备的屏幕密度 +inScale: 是否需要放缩位图 + +清楚这三者的含义,就可以在加载图片时,根据图片在不同设备上的使用,可以放缩来加载位图: + +**`放缩规则 scale= inTargetDensity/inDesity;`** + +```java + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = true; + options.inDensity = getBitmapDensity(); + options.inTargetDensity =Resources.getSystem().getDisplayMetrics().densityDpi ; + Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher, options); + +``` + +### 开发中遇到的问题: -* BitmapFactory.decodeResource(Resources res, int id)获取到的bitmap是mutable的,而BitmapFactory.decodeFile(String path)获取到的是immutable的 -* 可以使用Bitmap copy(Config config, boolean isMutable)获取mutable位图用于修改位图pixels. +在手机上加载大图时根据屏幕的密度对图片进行缩放,因此我们使用最大的图片资源,这样的话对于任何的手机屏幕,都会对图像进行压缩,不会造成视觉上的问题.但Android系统升级到4.4之后,发现之前开发的App运行起来非常的卡,严重影响了用户体验。后来发现跟Bitmap.decodeByteArray的底层实现有关。而android4.4以前的BitmapFactory.cpp中nativeDecodeByteArray调用doDecode函数时不会根据density进行缩放处理.4.4后由于一张图片缩放加载后,在内存放大很多,导致内存占有量过大,造成卡顿. -### inDesity, inTargetDensity,inScreenDensity,inScale - \ No newline at end of file +###参考资料 +* [**Android inpreferredconfig参数分析**](http://blog.csdn.net/ccpat/article/details/46834089) +* [**Bitmap的内存管理**](http://hukai.me/android-training-course-in-chinese/graphics/displaying-bitmaps/manage-memory.html) +* [**高效加载大图片**](http://hukai.me/android-training-course-in-chinese/graphics/displaying-bitmaps/load-bitmap.html ) +* [**Android坑档案:你的Bitmap究竟占多大内存?**](https://zhuanlan.zhihu.com/p/20732309?refer=bennyhuo) diff --git "a/showdy_note/android/LocalBroadcastManager346円272円220円347円240円201円345円210円206円346円236円220円.md" "b/showdy_note/android/LocalBroadcastManager346円272円220円347円240円201円345円210円206円346円236円220円.md" new file mode 100644 index 0000000..6694254 --- /dev/null +++ "b/showdy_note/android/LocalBroadcastManager346円272円220円347円240円201円345円210円206円346円236円220円.md" @@ -0,0 +1,518 @@ +### LocalBroadcastManager优势: + + Helper to register for and send broadcasts of Intents to local objects within your process. This has a number of advantages over sending global broadcasts with {@link android.content.Context#sendBroadcast}: + + * You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data. + * It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit. + * It is more efficient than sending a global broadcast through the system. + +相对于BroadcastReceiver来说,LocalBroadcastManager有如下优势: + + * 发送的广播只会在当前APP中传播,不会泄露给其他APP,确保数据传输的安全性. + * 其他APP的广播无法发送到本地APP中,不用担心安全漏洞被其他APP利用. + * 比系统全局广播更加高效. + + +### LocalBraodcastManager源码如下: + +```java + + /** + * Helper to register for and send broadcasts of Intents to local objects + * within your process. This has a number of advantages over sending + * global broadcasts with {@link android.content.Context#sendBroadcast}: + *
    + *
  • You know that the data you are broadcasting won't leave your app, so + * don't need to worry about leaking private data. + *
  • It is not possible for other applications to send these broadcasts to + * your app, so you don't need to worry about having security holes they can + * exploit. + *
  • It is more efficient than sending a global broadcast through the + * system. + *
+ */ + public final class LocalBroadcastManager { + private static class ReceiverRecord { + final IntentFilter filter; + final BroadcastReceiver receiver; + boolean broadcasting; + + ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) { + filter = _filter; + receiver = _receiver; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(128); + builder.append("Receiver{"); + builder.append(receiver); + builder.append(" filter="); + builder.append(filter); + builder.append("}"); + return builder.toString(); + } + } + + private static class BroadcastRecord { + final Intent intent; + final ArrayList receivers; + + BroadcastRecord(Intent _intent, ArrayList _receivers) { + intent = _intent; + receivers = _receivers; + } + } + + private static final String TAG = "LocalBroadcastManager"; + private static final boolean DEBUG = false; + + private final Context mAppContext; + + private final HashMap> mReceivers + = new HashMap>(); + private final HashMap> mActions + = new HashMap>(); + + private final ArrayList mPendingBroadcasts + = new ArrayList(); + + static final int MSG_EXEC_PENDING_BROADCASTS = 1; + + private final Handler mHandler; + + private static final Object mLock = new Object(); + private static LocalBroadcastManager mInstance; + + public static LocalBroadcastManager getInstance(Context context) { + synchronized (mLock) { + if (mInstance == null) { + mInstance = new LocalBroadcastManager(context.getApplicationContext()); + } + return mInstance; + } + } + + private LocalBroadcastManager(Context context) { + mAppContext = context; + mHandler = new Handler(context.getMainLooper()) { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_EXEC_PENDING_BROADCASTS: + executePendingBroadcasts(); + break; + default: + super.handleMessage(msg); + } + } + }; + } + + /** + * Register a receive for any local broadcasts that match the given IntentFilter. + * + * @param receiver The BroadcastReceiver to handle the broadcast. + * @param filter Selects the Intent broadcasts to be received. + * + * @see #unregisterReceiver + */ + public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + synchronized (mReceivers) { + ReceiverRecord entry = new ReceiverRecord(filter, receiver); + ArrayList filters = mReceivers.get(receiver); + if (filters == null) { + filters = new ArrayList(1); + mReceivers.put(receiver, filters); + } + filters.add(filter); + for (int i=0; i entries = mActions.get(action); + if (entries == null) { + entries = new ArrayList(1); + mActions.put(action, entries); + } + entries.add(entry); + } + } + } + + /** + * Unregister a previously registered BroadcastReceiver. All + * filters that have been registered for this BroadcastReceiver will be + * removed. + * + * @param receiver The BroadcastReceiver to unregister. + * + * @see #registerReceiver + */ + public void unregisterReceiver(BroadcastReceiver receiver) { + synchronized (mReceivers) { + ArrayList filters = mReceivers.remove(receiver); + if (filters == null) { + return; + } + for (int i=0; i receivers = mActions.get(action); + if (receivers != null) { + for (int k=0; k categories = intent.getCategories(); + + final boolean debug = DEBUG || + ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); + if (debug) Log.v( + TAG, "Resolving type " + type + " scheme " + scheme + + " of intent " + intent); + + ArrayList entries = mActions.get(intent.getAction()); + if (entries != null) { + if (debug) Log.v(TAG, "Action list: " + entries); + + ArrayList receivers = null; + for (int i=0; i= 0) { + if (debug) Log.v(TAG, " Filter matched! match=0x" + + Integer.toHexString(match)); + if (receivers == null) { + receivers = new ArrayList(); + } + receivers.add(receiver); + receiver.broadcasting = true; + } else { + if (debug) { + String reason; + switch (match) { + case IntentFilter.NO_MATCH_ACTION: reason = "action"; break; + case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break; + case IntentFilter.NO_MATCH_DATA: reason = "data"; break; + case IntentFilter.NO_MATCH_TYPE: reason = "type"; break; + default: reason = "unknown reason"; break; + } + Log.v(TAG, " Filter did not match: " + reason); + } + } + } + + if (receivers != null) { + for (int i=0; i> mReceivers = new HashMap(); + private final HashMap> mActions = new HashMap(); + private final ArrayList mPendingBroadcasts = new ArrayList(); + + ``` + + `HashMap`对象的`mReceivers`存储广播和过滤信息集合,通过以`BroadcastReceiver`为Key,`ArrayList`为Value,这样做是为了方便注销. + + `HashMap`对象的`mActions`存储`Action`和`ReceiverRecord`,以`Action`为key,`ArrayList`为value,`mActions`的主要作用是方便在广播发送后快速得到可以接收他的`BroadcastReceiver`. + + `mPendingBroadcasts`就是发送广播的集合. + +* `LocalBroadcastManager`的构造函数: + + ```java + + private LocalBroadcastManager(Context context) { + mAppContext = context; + mHandler = new Handler(context.getMainLooper()) { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_EXEC_PENDING_BROADCASTS: + executePendingBroadcasts(); + break; + default: + super.handleMessage(msg); + } + } + }; + } + + ``` + + `LocalBroadcastManager`的构造函数很简单,就做了一件事,创建一个基于主线程Looper的Handler,并接收`msg.what`为`MSG_EXEC_PENDING_BROADCASTS`的消息,并调用 `executePendingBroadcasts()`发送广播. + + +* `LocalBroadcastManager注册` + + ```java + + public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + synchronized (mReceivers) { + ReceiverRecord entry = new ReceiverRecord(filter, receiver); + ArrayList filters = mReceivers.get(receiver); + if (filters == null) { + filters = new ArrayList(1); + mReceivers.put(receiver, filters); + } + filters.add(filter); + for (int i=0; i entries = mActions.get(action); + if (entries == null) { + entries = new ArrayList(1); + mActions.put(action, entries); + } + entries.add(entry); + } + } + } + + ``` + 基于要注册的BroadReceiver和IntentFilter,在mReceivers中查找,如果为null,便把传过来的receiver和filter参数存储到mReceivers中,同时将IntentFilter中的Action存储到mActions中.方便后面注销广播,对元素的移除. + +* `LocalBroadcastManager`的注销 + + ```java + + public void unregisterReceiver(BroadcastReceiver receiver) { + synchronized (mReceivers) { + ArrayList filters = mReceivers.remove(receiver); + if (filters == null) { + return; + } + for (int i=0; i receivers = mActions.get(action); + if (receivers != null) { + for (int k=0; k categories = intent.getCategories(); + + final boolean debug = DEBUG || + ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); + if (debug) Log.v( + TAG, "Resolving type " + type + " scheme " + scheme + + " of intent " + intent); + + ArrayList entries = mActions.get(intent.getAction()); + if (entries != null) { + if (debug) Log.v(TAG, "Action list: " + entries); + + ArrayList receivers = null; + for (int i=0; i= 0) { + if (debug) Log.v(TAG, " Filter matched! match=0x" + + Integer.toHexString(match)); + if (receivers == null) { + receivers = new ArrayList(); + } + receivers.add(receiver); + receiver.broadcasting = true; + } else { + if (debug) { + String reason; + switch (match) { + case IntentFilter.NO_MATCH_ACTION: reason = "action"; break; + case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break; + case IntentFilter.NO_MATCH_DATA: reason = "data"; break; + case IntentFilter.NO_MATCH_TYPE: reason = "type"; break; + default: reason = "unknown reason"; break; + } + Log.v(TAG, " Filter did not match: " + reason); + } + } + } + + if (receivers != null) { + for (int i=0; i getParams() throws AuthFailureError { Map map = new HashMap(); @@ -48,10 +54,12 @@ return map; } }; +``` * 第二种方法: - + ```java + String url = "http://m.weather.com.cn/atad/101010100.html"; RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext()); //封装请求参数 @@ -76,10 +84,14 @@ jsonObjectRequest.setTag("jsonObjectGET"); requestQueue.add(jsonObjectRequest); + ``` + #### 与Activity生命周期联动,取消请求 * 1.定义tag,绑定请求 + ```java + public static final String TAG = "MyTag"; StringRequest stringRequest; // Assume this exists. RequestQueue mRequestQueue; // Assume this exists. @@ -89,9 +101,13 @@ // Add the request to the RequestQueue. mRequestQueue.add(stringRequest); + ``` + * 2.与activity联动,在onstop()时,取消请求: + ```java + protected void onStop () { super.onStop(); if (mRequestQueue != null) { @@ -99,6 +115,8 @@ } } + ``` + ### 自定义RequestQueue > 使用Volley.newRequestQueue创建,可以充分利用Volley默认队列的优势.当也可以自定义一个RequestQueue,创建一个单例对象,贯穿整个app生命周期. @@ -110,6 +128,7 @@ RequsetQueue需要network传输request,cache处理caching. * DiskBaseCache * BaseNetwork--基于HttpClient或者HttpurlConnection * api<9 使用HttpClient, api>9后使用HttpUrlConnection +```java HttpStack stack; ... @@ -124,7 +143,11 @@ RequsetQueue需要network传输request,cache处理caching. RequestQueue mRequestQueue; +``` + * 自定义一个RequestQueue + ```java + // Instantiate the cache Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap @@ -158,12 +181,14 @@ RequsetQueue需要network传输request,cache处理caching. // Add the request to the RequestQueue. mRequestQueue.add(stringRequest); ... - + ``` ### 使用单例模式创建RequestQueue * 第一种方式: 使用单例模式创建RequestQueue,提供静态方法,注意要使用Application context,not Activity context,保证生命周期和app一样长. * 第二种方式: 创建Application的之类,在onCreate()中初始化,但不推荐. * 下面是一段单例提供RequestQueue和ImageLoader相关功能的代码: + ```java + public class MySingleton { private static MySingleton mInstance; private RequestQueue mRequestQueue; @@ -216,7 +241,12 @@ RequsetQueue需要network传输request,cache处理caching. } } +``` + * 下面是一段使用单例RequestQueue运用的代码片段: + +```java + // Get a RequestQueue RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()). getRequestQueue(); @@ -224,7 +254,8 @@ RequsetQueue需要network传输request,cache处理caching. // Add a request (in this example, called stringRequest) to your RequestQueue. MySingleton.getInstance(this).addToRequestQueue(stringRequest); - +``` + ### 使用Volley提供的Request类型: * StringRequest @@ -245,6 +276,7 @@ RequsetQueue需要network传输request,cache处理caching. #### 使用ImageRequest * 使用单例模式下RequsetQueue的一段ImageRequset代码: +```java ImageView mImageView; String url = "http://i.imgur.com/7spzG.png"; @@ -266,17 +298,22 @@ RequsetQueue需要network传输request,cache处理caching. }); // Access the RequestQueue through your singleton class. MySingleton.getInstance(this).addToRequestQueue(request); +``` #### 使用ImageLoader和NetworkImageView * 联合使用ImageLoader和NetworkImageView可以有效管理多图片显示,在xml可以类似ImageView布局: +```mxl + + +``` - * 也可以使用ImageLoader加载图片: + ```java ImageLoader mImageLoader; ImageView mImageView; @@ -290,9 +327,11 @@ RequsetQueue需要network传输request,cache处理caching. mImageLoader = MySingleton.getInstance(this).getImageLoader(); mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView, R.drawable.def_image, R.drawable.err_image)); - + ``` + * 也可以使用NetworkImageView构建ImageView视图 - + ```java + ImageLoader mImageLoader; NetworkImageView mNetworkImageView; private static final String IMAGE_URL = @@ -308,11 +347,13 @@ RequsetQueue需要network传输request,cache处理caching. // Set the URL of the image that should be loaded into this view, and // specify the ImageLoader that will be used to make the request. mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader); - + ``` + #### 使用LRU cache * Volley实现了标准缓存类DiskBasedCache,该类直接将文件写入到硬盘中 * 使用ImageLoader,需要实现ImageLoader.ImageCache接口,构建自定义的LRU bitmap cache * 下面是LruBitmapCache继承自LruCache,实现了ImageLoader.ImageCache接口. + ```java public class LruBitmapCache extends LruCacheimplements ImageCache { @@ -351,20 +392,23 @@ RequsetQueue需要network传输request,cache处理caching. return screenBytes * 3; } } - + ``` + * 下面展示使用LruBitmapCache实例化ImageLoader - + ```java + RequestQueue mRequestQueue; // assume this exists. ImageLoader mImageLoader = new ImageLoader(mRequestQueue, new LruBitmapCache( LruBitmapCache.getCacheSize())); - + ``` #### Requset Json * Volley提供了两个类做Json Request,此二者都是继承JsonRequest: * JsonArrayRequest * JsonObjectRequest * 下面一段如何使用的例子: +```java TextView mTxtDisplay; ImageView mImageView; @@ -390,7 +434,7 @@ RequsetQueue需要network传输request,cache处理caching. // Access the RequestQueue through your singleton class. MySingleton.getInstance(this).addToRequestQueue(jsObjRequest); For an example of implementing a custom JSON request based on Gson, see the next lesson, Implementing a Custom Request. - +``` ### 实现自定义请求(Custom Requset) * reponse如果不是json,String,image,则需要自定义Requset: @@ -399,6 +443,7 @@ RequsetQueue需要network传输request,cache处理caching. #### parseNetworkResponse * 属于Response对象,但是封装了resposne的解析,下面是一个parseNetWorkResponse(); +```java @Override protected Response parseNetworkResponse( @@ -412,6 +457,8 @@ RequsetQueue需要network传输request,cache处理caching. // handle errors ... } +``` + * parseNetworkResponse()其中参数为NetworkResponse,包含 a byte[], HTTP status code, and response headers * 返回值必须为Response,其中包含response对象,cache metadata,or an error. * 如果协议没有标准的cache semantics,必须要构建Cache.Entry,但多数请求如下定义: @@ -422,12 +469,13 @@ RequsetQueue需要network传输request,cache处理caching. #### deliverResponse * 在主线程会回调返回调用parseNetworkResponse()后的结果,多数回调接口如下: - + ```java protected void deliverResponse(T response) { listener.onResponse(response); + ``` #### GsonRequest例子 - +```java public class GsonRequest extends Request { private final Gson gson = new Gson(); private final Class clazz; @@ -474,4 +522,5 @@ RequsetQueue需要network传输request,cache处理caching. return Response.error(new ParseError(e)); } } - } \ No newline at end of file + } +``` diff --git "a/showdy_note/android/view/Android345円274円200円345円217円221円344円270円255円351円201円207円345円210円260円347円232円204円344円270円200円344円272円233円345円260円217円351円227円256円351円242円230円.md" "b/showdy_note/android/view/Android345円274円200円345円217円221円344円270円255円351円201円207円345円210円260円347円232円204円344円270円200円344円272円233円345円260円217円351円227円256円351円242円230円.md" new file mode 100644 index 0000000..bbb6c22 --- /dev/null +++ "b/showdy_note/android/view/Android345円274円200円345円217円221円344円270円255円351円201円207円345円210円260円347円232円204円344円270円200円344円272円233円345円260円217円351円227円256円351円242円230円.md" @@ -0,0 +1,62 @@ +### Android 开发中的一个小问题: + +#### RadioGroup#checked(id)调用会多次触发onCheckedChanged()监听. + +查看RadioGroup#check(id)源码: + +```java + + public void check(@IdRes int id) { + // don't even bother + if (id != -1 && (id == mCheckedId)) { + return; + } + + if (mCheckedId != -1) { + setCheckedStateForView(mCheckedId, false); + } + + if (id != -1) { + setCheckedStateForView(id, true); + } + + setCheckedId(id); + } + +``` + +从源码可以看到onCheckedChanged()监听会触发三次: + +* 当前RadioButton状态置为unChecked; +* 已选RadioButton状态置为checked; +* RadioGroup保存哪个RadioButton已经checked. + +解决此问题,可以选择另一个方法: + +```java + + private void setCheckedId(@IdRes int id) { + mCheckedId = id; + if (mOnCheckedChangeListener != null) { + mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId); + } + } +``` + +### TextView设置setClickable(false)时,TextView还是能响应点击事件: + +查看View的源码: + +```java + + public void setOnClickListener(@Nullable OnClickListener l) { + if (!isClickable()) { + setClickable(true); + } + getListenerInfo().mOnClickListener = l; + } +``` + +发现在setOnClickListener中会将view设置可点击,这也就是设置setclickable(false),然后调用调用监听无效的原因. + +解决办法很简单: 将setclickable(false)位置放在setOnClickListener()后面. diff --git "a/showdy_note/android/widget/344円275円277円347円224円250円DialogFragment345円210円233円345円273円272円345円257円271円350円257円235円346円241円206円.md" "b/showdy_note/android/widget/344円275円277円347円224円250円DialogFragment345円210円233円345円273円272円345円257円271円350円257円235円346円241円206円.md" new file mode 100644 index 0000000..bd59873 --- /dev/null +++ "b/showdy_note/android/widget/344円275円277円347円224円250円DialogFragment345円210円233円345円273円272円345円257円271円350円257円235円346円241円206円.md" @@ -0,0 +1,155 @@ +### DialogFragment的优势: + + 创建对话框的样式和结构,应该使用DialogFragment 用作对话框的容器,DialogFragment 类提供您创建对话框和管理其外观所需的所有 + 控件,而不是调用 Dialog 对象上的方法。使用 DialogFragment 管理对话框可确保它能正确处理生命周期事件,如用户按"返回"按钮或旋 + 转屏幕时。此外,DialogFragment 类还允许您将对话框的 UI 作为嵌入式组件在较大 UI 中重复使用,就像传统 Fragment 一样(例如, + 当您想让对话框 UI 在大屏幕和小屏幕上具有不同外观时)。 + + ### DialogFragment与AlertDialog结合创建对话框 + + 完成各种对话框设计—包括自定义布局以及对话框设计指南中描述的布局—通过扩展 DialogFragment 并在 onCreateDialog() 回调方法中创建 AlertDialog。 + + ```java + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setMessage("创建一个AlertDialog!") + .setPositiveButton("Ok", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //user clicked Ok button; + } + }) + .setNegativeButton("Cancel", null) + .setOnCancelListener(null) + .setOnDismissListener(null) + .show(); + } + + ``` + + ### 创建自定义布局的对话框: + + 您想让一部分 UI 在某些情况下显示为对话框,但在其他情况下全屏显示或显示为嵌入式片段(也许取决于设备使用大屏幕还是小屏幕)。 + DialogFragment 类便具有这种灵活性,因为它仍然可以充当嵌入式 Fragment。但在这种情况下,您不能使用 AlertDialog.Builder + 或其他 Dialog 对象来构建对话框。如果您想让 DialogFragment 具有嵌入能力,则必须在布局中定义对话框的 UI, + 然后在 onCreateView() 回调中加载布局。 + + ```java + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.dialog_guide, container, false); + ImageView ivDismiss = (ImageView) view.findViewById(R.id.iv_dismiss); + ivDismiss.setOnClickListener(this); + setupDialog(getDialog(), dialog); + setWindowAttributes(); + return view; + } + + private void setWindowAttributes() { + //设置window全屏 + Window window = getDialog().getWindow(); + WindowManager manager = window.getWindowManager(); + Display display = manager.getDefaultDisplay(); + WindowManager.LayoutParams params = window.getAttributes(); + params.width = display.getWidth(); + window.setAttributes(params); + } + ``` + ### DialogFragment将事件传递给对话框的宿主 + + 在Dialog中定义接口,使用接口回调的方式,传递事件 + + ```java + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + listener = (PositiveListener) activity; + } catch (ClassCastException exception) { + throw new ClassCastException(activity.toString() + + " must implement PositiveListener"); + } + } + + @Override + public void onClick(View v) { + if (listener != null) { + listener.onPositive(this); + } + } + + public PositiveListener listener; + + public interface PositiveListener { + void onPositive(DialogFragment dialog); + } + ``` + 然后, 宿主activity实现自定义的回到接口: + + ```java + public class DailogActivity extends AppCompatActivity implements + GuideDialogFragment.PositiveListener { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_dialog); + final GuideDialogFragment dialog = new GuideDialogFragment(); + dialog.show(getSupportFragmentManager(), "GuideDialog"); + } + + @Override + public void onPositive(DialogFragment dialog) { + dialog.dismissAllowingStateLoss(); + Toast.makeText(this, "dialog消失", Toast.LENGTH_SHORT).show(); + } +} + ``` + ### 定义Dialog的样式style + + ```java + + setupDialog(getDialog(), R.style.dialog); + + ``` + + +```xml + +``` + +### 设置dialog的宽高 + +```java + private void setWindowAttributes() { + //设置window全屏 + Window window = getDialog().getWindow(); + WindowManager manager = window.getWindowManager(); + Display display = manager.getDefaultDisplay(); + WindowManager.LayoutParams params = window.getAttributes(); + params.width = display.getWidth(); + window.setAttributes(params); + } + +``` + + diff --git "a/showdy_note/android/346円222円270円344円270円200円344円270円252円350円207円252円345円267円261円347円232円204円Handler345円274円202円346円255円245円346円266円210円346円201円257円346円234円272円345円210円266円.md" "b/showdy_note/android/346円222円270円344円270円200円344円270円252円350円207円252円345円267円261円347円232円204円Handler345円274円202円346円255円245円346円266円210円346円201円257円346円234円272円345円210円266円.md" new file mode 100644 index 0000000..8fe3d81 --- /dev/null +++ "b/showdy_note/android/346円222円270円344円270円200円344円270円252円350円207円252円345円267円261円347円232円204円Handler345円274円202円346円255円245円346円266円210円346円201円257円346円234円272円345円210円266円.md" @@ -0,0 +1,258 @@ +### Android Handler消息机制原理概述: + +Android的消息机制主要是指Handler运行机制,Handler运行机制需要底层的MessageQueue和Looper的支持,MessageQueue内部采用单链表的数据结构,提供队列的形式对外提供插入和删除的功能.MessageQueue只是一个存储单元,不能去处理消息,那么Looper便实现了这个功能.Looper会以无限循环的形式去查找是否有新消息,有就去处理,没有就一直阻塞等待.另外一点值得注意,Looper会存储在每个线程的ThreadLocal中,实现线程之间互不干扰的存储并提供数据. + +经过对Android消息机制的简单概述,知道Handler消息需要: + +* Handler +* MessageQueue +* Looper +* Message + +简单的流程图如下(图片来自网络): + +![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1495004303177&di=7bff27d4beb0cd1b3f23704097b1bedf&imgtype=jpg&src=http%3A%2F%2Fimg4.imgtn.bdimg.com%2Fit%2Fu%3D3731823991%2C444840863%26fm%3D214%26gp%3D0.jpg) + + +### 实现自己的Handler异步消息机制: + +* Handler: + + Handler对象提供两个方法一个是sendMessage();一个是handMessage();当然我们还需要dispatchMessage()也就是供Looper调度消息. + +```java + + public class Handler { + + private MessageQueue mMessageQueue; + private Looper mLooper; + + public Handler() { + mLooper = Looper.myLooper(); + mMessageQueue = mLooper.mQueue; + } + + public void sendMessage(Message msg) { + msg.target= this; + mMessageQueue.enqueueMessage(msg); + } + + + + public void handleMessage(Message msg) { + + } + + public void dispatchMessage(Message msg) { + handleMessage(msg); + } + } + + +``` + +* Message: + + Message对象很简单, 提供一个int类型的消息编号,一个Object类型的消息内容obj,还有就一个与Handler绑定的target. + + ```java + + public class Message { + + public int what; + public Object obj; + Handler target; + + @Override + public String toString() { + return obj.toString(); + } + } + + + ``` + +* Looper: + + Looper轮询器,主要作用: 处理消息; 从MessageQueue中拿到消息并调度给Handler.值得注意的是Looper对象存储于ThreadLocal中,所以提供一个prepare()来初始化当前线程的Looper对象,另外提供一个loop()来轮询消息. + +```java + + public class Looper { + + public MessageQueue mQueue; + private static ThreadLocal sThreadLocal = new ThreadLocal(); + + private Looper() { + mQueue = new MessageQueue(); + } + + public static void prepare() { + if (sThreadLocal.get() != null) { + throw new RuntimeException("Only one Looper may be created per thread"); + } + sThreadLocal.set(new Looper()); + } + + public static Looper myLooper() { + + return sThreadLocal.get(); + } + + public static void loop() { + Looper looper = myLooper(); + + if (looper==null){ + throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); + } + + MessageQueue queue = looper.mQueue; + for (; ; ) { + Message msg = queue.next(); + if (msg==null){ + continue; + } + msg.target.dispatchMessage(msg); + } + } + } + +``` + +* MessageQueue: + + MessageQueue作为消息的存储单元,必定提供对消息的插入和删除, 就是enqueueMessage()和next(); 但是要注意的是在多线程中消息的插入和删除就是一个生产者和消费者模型,这里采用ArrayBlockingQueue阻塞队列的实现方式(可重入锁Lock和Condition)来实现: + +```java + + public class MessageQueue { + + Message[] msgs; //采用的是ArrayBlockingQueue的实现方式. + + int putIndex; + int takeIndex; + int count; + private Lock mLock; + private Condition notEmpty; + private Condition notFull; + + public MessageQueue() { + msgs = new Message[50]; + mLock = new ReentrantLock(); + notEmpty = mLock.newCondition(); + notFull = mLock.newCondition(); + } + + public void enqueueMessage(Message msg) { + + try { + mLock.lock(); + if (count == msgs.length) { + try { + notFull.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + msgs[putIndex] = msg; + putIndex = (++putIndex == msgs.length) ? 0 : putIndex; + count++; + notEmpty.signal(); + } finally { + mLock.unlock(); + } + + } + + public Message next() { + + try { + mLock.lock(); + if (msgs.length == 0) { + try { + notEmpty.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + Message msg = msgs[takeIndex]; + msgs[takeIndex] = null; + takeIndex = (++takeIndex == msgs.length) ? 0 : takeIndex; + count--; + notFull.signal(); + return msg; + } finally { + mLock.unlock(); + + } + } + } + +``` + +### 测试自己的Handler异步消息机制: + +```java + + public class HandlerTest { + + public static void main(String... args) { + + for (int i = 0; i < 10; i++) { + new Thread(new Runnable() { + @Override + public void run() { + Looper.prepare(); + + final Handler handler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + String message = (String) msg.obj; + System.out.println("receive: " + Thread.currentThread().getName() + message); + } + }; + + Message msg = new Message(); + msg.what = 1; + msg.obj = Thread.currentThread().getName() + "--------" + System.currentTimeMillis(); + System.out.println("send: " + msg.obj); + handler.sendMessage(msg); + Looper.loop(); + } + }).start(); + } + + } + } + +``` + +执行结果如下: + +```java + + send: Thread-1--------1494995896691 + receive: Thread-1Thread-1--------1494995896691 + send: Thread-3--------1494995896708 + receive: Thread-3Thread-3--------1494995896708 + send: Thread-0--------1494995896708 + receive: Thread-0Thread-0--------1494995896708 + send: Thread-4--------1494995897043 + receive: Thread-4Thread-4--------1494995897043 + send: Thread-5--------1494995897080 + receive: Thread-5Thread-5--------1494995897080 + send: Thread-9--------1494995897118 + receive: Thread-9Thread-9--------1494995897118 + send: Thread-2--------1494995897154 + receive: Thread-2Thread-2--------1494995897154 + send: Thread-6--------1494995897186 + receive: Thread-6Thread-6--------1494995897186 + send: Thread-7--------1494995897188 + receive: Thread-7Thread-7--------1494995897188 + send: Thread-8--------1494995897234 + receive: Thread-8Thread-8--------1494995897234 + + Process finished with exit code -1 + +``` diff --git "a/showdy_note/java/Java344円271円213円344円275円215円350円277円220円347円256円227円347円254円246円.md" "b/showdy_note/java/Java344円271円213円344円275円215円350円277円220円347円256円227円347円254円246円.md" new file mode 100644 index 0000000..7c42b37 --- /dev/null +++ "b/showdy_note/java/Java344円271円213円344円275円215円350円277円220円347円256円227円347円254円246円.md" @@ -0,0 +1,134 @@ +### Java位运算符 + +| & | 与运算 | 1600ドル任何二进制位和0进行&运算,结果是0;和1进行&运算结果是原值。| +| :------------- |:-------------| :-----| +|\| | 或运算 | 任何二进制位和0进行 或 运算,结果是原值;和1进行 或运算结果是1。| +| ^|异或运算 | 任何相同二进制位进行 ^ 运算,结果是0;不相同二进制位 ^ 运算结果是1。| +|~|反码|计算机存储是补码,呈现出来的是原码| +|<<|左移|空位补0,被移除的高位丢弃。| +|>>|右移|被移位的二进制最高位是0,右移后,空缺位补0;最高位是1,最高位补1。| +|>>>|无符号右移|被移位二进制最高位无论是0或者是1,空缺位都用0补。| + + +### 实例说明 + +```java + + class Operator { + public static void main(String[] args) { + int a = 3; + int b = 4; + + System.out.println(a & b);//0 + System.out.println(a | b);//7 + System.out.println(a ^ b);//7 + System.out.println(~b);//-5 + System.out.println(~a);//-4 + System.out.println(16<<2);//左移64 + System.out.println(16>>2);//右移4 + System.out.println(16>>>2);//无符号右移4 + } + } + +``` +运算过程如下: + +a=3 => 00000000 00000000 00000000 00000011 + +b=4 => 00000000 00000000 00000000 00000100 + +```java + + 00000000 00000000 00000000 00000011 + & 00000000 00000000 00000000 00000100 + ------------------------------------ + 00000000 00000000 00000000 00000000 + + 00000000 00000000 00000000 00000011 + | 00000000 00000000 00000000 00000100 + ------------------------------------ + 00000000 00000000 00000000 00000111 + + 00000000 00000000 00000000 00000011 + ^ 00000000 00000000 00000000 00000100 + ------------------------------------ + 00000000 00000000 00000000 00000111 + + b:00000000 00000000 00000000 00000100 + ~ 按位取反,就是针对b这个二进制数据,所有的0变1,1变0。 + 补码 11111111 11111111 11111111 11111011 + 反码 11111111 11111111 11111111 11111010 + 原码 10000000 00000000 00000000 00000101 + +``` + +对于反码时有一个规律: ~a=-(a+1); + +### 进制之间的互相转换 + +对于右移>>n表示数字除以2^n,而左移则表示数字乘以2^n,知道这个规律后, 将十进制数字转换为2^n进制时就可以使用位运算了;下面是一段将进制转为2^n进制的代码,当然了2^n有限制. + +```java + + private static String toUnsignedString(int i, int shift) { + char[] buf = new char[32]; + int charPos = 32; + int radix = 1 << shift; + int mask = radix - 1; + do { + buf[--charPos] = digits[i & mask]; + i>>>= shift; + } while (i != 0); + return new String(buf, charPos, (32 - charPos)); + } + +``` + +### 任意进制之间的转换 + +由于任意进制的转换,并非所有都满足2^n,此处就不太适合使用位运算了. + +```java + + final static char[] digits = { + '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' + }; + + public int toCustomNumeric(String s, int system) { + char[] buf = new char[s.length()]; + s.getChars(0, s.length(), buf, 0); + long num = 0; + for (int i = 0; i < buf.length; i++) { + for (int j = 0; j < digits.length; j++) { + if (digits[j] == buf[i]) { + num += j * Math.pow(system, buf.length - i - 1); + break; + } + } + } + return (int) num; + } + + public String toCustomNumericString(int i, int system) { + long num = 0; + if (i < 0) { + num = ((long) 2 * 0x7fffffff) + i + 2; + } else { + num = i; + } + char[] buf = new char[32]; + int charPos = 32; + while ((num / system)> 0) { + buf[--charPos] = digits[(int) (num % system)]; + num /= system; + } + buf[--charPos] = digits[(int) (num % system)]; + return new String(buf, charPos, (32 - charPos)); + } + +``` diff --git "a/showdy_note/java/Java345円244円232円347円272円277円347円250円213円344円271円213円344円270円255円346円226円255円347円272円277円347円250円213円.md" "b/showdy_note/java/Java345円244円232円347円272円277円347円250円213円344円271円213円344円270円255円346円226円255円347円272円277円347円250円213円.md" new file mode 100644 index 0000000..763482d --- /dev/null +++ "b/showdy_note/java/Java345円244円232円347円272円277円347円250円213円344円271円213円344円270円255円346円226円255円347円272円277円347円250円213円.md" @@ -0,0 +1,140 @@ +### 取消任务的方式 + +Java中没有提供任何机制来**安全**地终止线程,但是提供了中断(Interruption)协作机制,能够使一个线程终止另一个线程的当前工作. 一般取消或停止某个任务,很少采用立即停止,因为立即停止会使得共享数据结构出于不一致的状态.这也是Thread.stop(),Thread.suspend()以及Thread.resume()不安全的原因而废弃. + +Java中有三种方式可以终止当前运行的线程: + +* 设置某个"已请求取消(Cancellation Requested)"标记,而任务将定期查看该标记的协作机制来中断线程. +* 使用Thread.stop()强制终止线程,但是因为这个方法"解锁"导致共享数据结构处于不一致而不安全被废弃. +* 使用Interruption中断机制. + + + +### 使用中断标记来中断线程. + +```java + + public class PrimeGenerator implements Runnable { + private static ExecutorService exec = Executors.newCachedThreadPool(); + + @GuardedBy("this") private final List primes + = new ArrayList(); + private volatile boolean cancelled; + + public void run() { + BigInteger p = BigInteger.ONE; + while (!cancelled) { + p = p.nextProbablePrime(); + synchronized (this) { + primes.add(p); + } + } + } + + public void cancel() { + cancelled = true; + } + + public synchronized List get() { + return new ArrayList(primes); + } + + static List aSecondOfPrimes() throws InterruptedException { + PrimeGenerator generator = new PrimeGenerator(); + exec.execute(generator); + try { + SECONDS.sleep(1); + } finally { + generator.cancel(); + } + return generator.get(); + } + } + +``` + +设置标记的中断策略: PrimeGenerator使用一种简单取消策略,客户端代码通过调研cancel来请求取消,PrimeGenerator在每次搜索素数时前先检查是否存在取消请求,如果不存在就退出. + +但是使用设置标记的中断策略有一问题: **如果任务调用调用阻塞的方法,比如BlockingQueue.put,那么可能任务永远不会检查取消标记而不会结束.** + +```java + + class BrokenPrimeProducer extends Thread { + private final BlockingQueue queue; + private volatile boolean cancelled = false; + + BrokenPrimeProducer(BlockingQueue queue) { + this.queue = queue; + } + + public void run() { + try { + BigInteger p = BigInteger.ONE; + while (!cancelled) + //此处阻塞,可能永远无法检测到结束的标记 + queue.put(p = p.nextProbablePrime()); + } catch (InterruptedException consumed) { + } + } + + public void cancel() { + cancelled = true; + } + } + +``` + +解决办法也很简单: **使用中断而不是使用boolean标记来请求取消** + +### 使用中断(Interruption)请求取消 + +* Thread类中的中断方法: + * `public void interrupt()` + + 请求中断,设置中断标记,而并不是真正中断一个正在运行的线程,只是发出了一个请求中断的请求,由线程在合适的时候中断自己. + * `public static native boolean interrupted()`; + + 判断线程是否中断,会擦除中断标记(判断的是当前运行的线程),另外若调用Thread.interrupted()返回为true时,必须要处理,可以抛出中断异常或者再次调用interrupt()来恢复中断. + * `public native boolean isInterrupted()`; + + 判断线程是否中断,不会擦除中断标记 + +故而上面问题的解决方案如下: + +```java + + public class PrimeProducer extends Thread { + private final BlockingQueue queue; + + PrimeProducer(BlockingQueue queue) { + this.queue = queue; + } + + public void run() { + try { + BigInteger p = BigInteger.ONE; + while (!Thread.currentThread().isInterrupted()) + queue.put(p = p.nextProbablePrime()); + } catch (InterruptedException consumed) { + /* Allow thread to exit */ + } + } + + public void cancel() { + interrupt(); + } + } + +``` + +* 那么thread.interrupt()调用后到底意味着什么? + + 首先一个线程不应该由其他线程来强制中断或者停止,而应该由线程自己停止,所以Thread.stop(),Thread.suspend(),Thread.resume()都已被废弃.而{@link Thread#interrupt}作用其实不是中断线程,而请求线程中断.具体来说,当调用interrupt()方法时: + + * 如果线程处于阻塞状态时(例如处于sleep,wait,join等状态时)那么线程将立即退出阻塞状态而抛出InterruptedException异常. + * 如果 线程处于正常活动状态,那么会将线程的中断标记设置为true,仅此而已.被设置中断标记的线程将继续运行而不受影响. + + interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行: + + * 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。 + * 在调用阻塞方法时正确处理InterruptedException异常。 diff --git "a/showdy_note/java/Java345円244円232円347円272円277円347円250円213円344円271円213円345円206円205円345円255円230円345円217円257円350円247円201円346円200円247円.md" "b/showdy_note/java/Java345円244円232円347円272円277円347円250円213円344円271円213円345円206円205円345円255円230円345円217円257円350円247円201円346円200円247円.md" new file mode 100644 index 0000000..f9415fd --- /dev/null +++ "b/showdy_note/java/Java345円244円232円347円272円277円347円250円213円344円271円213円345円206円205円345円255円230円345円217円257円350円247円201円346円200円247円.md" @@ -0,0 +1,161 @@ +### 可见性 + +* 可见性: 一个线程对共享变量值的修改,能够及时地被其他线程看到. +* 共享变量: 如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量. + +### Java 内存模型(JMM) + +Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节. + +![](http://img.blog.csdn.net/20170306104808451?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2hvd2R5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) + +* 所有变量都存储在主内存中 + +* 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量副本(主内存中该变量的拷贝) + + +* **线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写**. + +* **不同线程池之间无法直接访问其他线程工作内存中的变量,线程间的变量值传递需要主内存来完成**. + +### 共享变量可见性实现的原理 + +线程1对共享变量的修改要想被线程2及时看到,必须要经过如下2步骤: + +* 把工作内存1中更新过的共享变量刷新到主内存中, +* 将主内存中最新的共享变量的值更新到工作内存2中. + + +### Synchronized实现可见性: + +Java**语言层面**支持的可见性实现方式: + +* synchronized +* volatile + +JMM关于synchronized的两条规定: + +* 线程解锁前,必须要共享变量的最新值刷新到主内存中, +* 线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值. + +两条规定能保证,线程解锁前对共享变量的修改在下次加锁时对其他线程的可见性. + +线程执行互斥代码的过程: + +1. 获取互斥锁 +2. 清空工作内存 +3. 从主内存中拷贝变量的最新副本到工作内存 +4. 执行代码 +5. 将更改后的共享变量值刷新到主内存中. +6. 释放互斥锁. + +**指令重排序**:代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或处理为了提高程序性能而做的优化,分为三种: + +1. 编译器优化的重排序 +2. 指令级并行重排序 +3. 内存系统的重排序 + +**as-if-serial**:无论如何重排序,程序执行的结果都应该与代码顺序执行的结果一致(Java编译器,运行时和处理器都会保证java在单线程下遵循as-if-serial语义). + +重排序不会给单线程带来内存可见性的问题,但是在多线程中程序交错执行时,重排序可能会造成内存可见性的问题. + +```java + + public class NoVisibility { + private static boolean ready; + private static int number; + + private static class ReaderThread extends Thread { + @Override + public void run() { + while (!ready) { + Thread.yield(); + System.out.println(number); + } + } + } + + public static void main(String[] args) { + new ReaderThread().start(); + number = 42; + ready = true; + } + } + +``` +上面的程序可能会一直运行下去,因为线程可能永远读取不到ready的值.也可能输出为0,因为线程可能看到写入了ready的值,但是却没有看到number之后写入的值,这种现象称为"重排序". + +### Volatile实现可见性: + +**volatile关键字**: + +* 能够保证volatile变量的可见性 +* 不能保证volatile变量复合操作的原子性 + +**volatile如何实现内存可见性**: + +深入来说: 通过加入**内存屏障**(8条)和**禁止重排序**优化来实现. + +* 对volatile变量执行写操作时,会在操作后加入一条store屏障指令(强制将变量值刷新到主内存中去) +* 对volatile变量执行读操作时,会在操作前加入一条load屏障指令(强制从主内存中读取变量的值) + +线程写volatile变量的过程: + +1. 改变线程工作内存中的volatile变量副本的值 +2. 将改变后副本值从工作内存刷新到主内存中 + +线程读volatile变量的过程 + +1. 从主内存中读取volatile变量的最新值到线程的工作内存中 +2. 从工作内存中读取volatile变量的副本 + + +要在多线程中安全使用volatile变量,必须满足: + +1. 对变量的写入操作不能依赖当前值,或者你能确保只有一个单线程更新变量的值. +2. 该变量不会与其他状态变量一起纳入不变性条件中 +3. 在访问变量时不需要加锁. + + +```java + + public class VolatileDemo { + private volatile int number = 0; + + public int getNumber() { + return number; + } + + public void increase() { + try { + TimeUnit.MILLISECONDS.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + this.number++; //非原子操作导致结果可能不为500 + } + + public static void main(String... args) { + final VolatileDemo volatileDemo = new VolatileDemo(); + for (int i = 0; i < 500; i++) { + new Thread(new Runnable() { + @Override + public void run() { + volatileDemo.increase(); + } + }).start(); + } + //主线程给子线程让出资源 + while (Thread.activeCount()> 1) { + Thread.yield(); + } + System.out.println("number:" + volatileDemo.getNumber()); + } + } +``` + +想要保证`this.number++`的原子性操作,有三种方式: + +* synchronized同步关键字 +* Lock +* AotomicInteger diff --git "a/showdy_note/java/Java345円244円232円347円272円277円347円250円213円344円271円213円347円272円277円347円250円213円347円212円266円346円200円201円345円217円212円346円226円271円346円263円225円.md" "b/showdy_note/java/Java345円244円232円347円272円277円347円250円213円344円271円213円347円272円277円347円250円213円347円212円266円346円200円201円345円217円212円346円226円271円346円263円225円.md" new file mode 100644 index 0000000..bd4c71e --- /dev/null +++ "b/showdy_note/java/Java345円244円232円347円272円277円347円250円213円344円271円213円347円272円277円347円250円213円347円212円266円346円200円201円345円217円212円346円226円271円346円263円225円.md" @@ -0,0 +1,42 @@ +### 线程的生命周期(状态) + +![](http://www.uml-diagrams.org/examples/state-machine-example-java-6-thread-states.png) + +Java线程运行的生命周期处于图中的6中不同的状态,一个时刻,只能是一种状态: + +* NEW: 初始状态,线程被构建,但是还没有调用start(). + +* RUNNABLE: 运行状态,Java线程将被操作系统中的就绪和运行状态统称"运行中". + +* BLOCKED: 阻塞状态,表示线程阻塞于锁,一般由调用以下方法造成: + * object.wait(). + +* WAITING: 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或者中断).可能由以下方法造成: + * Object.wait() + * Thread.join() + * LockSupport.park() + +* TIME_WAITING: 超时等待状态,该状态不同于WAITING,他可在指定的时间自行返回的.可能由调用一下方法造成: + * Thread.sleep(sleeptime) + * Object.wait(timeout) + * Thread.join(timeout) + * LockSupport.parkNanos(timeout) + * LockSupport.parkUntil(timeout) +* TERMINATED: 终止状态,表示当前线程已经执行完毕. + + +### 线程让步 + + `public static native void yield();` + + +### 线程守护 + +`public final void setDaemon(boolean on)` + + +### 线程Jion + +`public final void join(long millis) throws InterruptedException` + + diff --git "a/showdy_note/java/Java345円271円266円345円217円221円344円271円213円345円220円214円346円255円245円345円267円245円345円205円267円347円261円273円.md" "b/showdy_note/java/Java345円271円266円345円217円221円344円271円213円345円220円214円346円255円245円345円267円245円345円205円267円347円261円273円.md" new file mode 100644 index 0000000..71eef96 --- /dev/null +++ "b/showdy_note/java/Java345円271円266円345円217円221円344円271円213円345円220円214円346円255円245円345円267円245円345円205円267円347円261円273円.md" @@ -0,0 +1,395 @@ +### 同步工具类 +* 同步工具类可以是任何一个对象,只要他根据自身的状态来协调线程的控制流.阻塞队列可以作为同步工具类,其他类型的同步工具类有: 信号量(semaphore),栅栏(Barrier),闭锁(Latch). +* 所有的同步工具类都有一些特定的结构化属性,封装了一些状态,这些状态决定执行同步工具类的线程是继续还是等待. + + +#### 闭锁(CountDownLatch) +#### CountDownLatch 方法解析 +CountDownLatch可以使一个或者多个线程等待一组事件的发生.闭锁状态包括一个计数器,该计数器初始化为一个正数,表示需要等待的事件数量.countDown()表示递减计数器,表示有个一个事件发生,而await()等待计数器达到零,这表示所有等待的事件都已经发生.如果计数器值非0,那么await()则会阻塞直到计数器为0,或者线程中断,或者等待超时. + + +* `public CountDownLatch(int count)` + + 用一个给定的数值初始化CountDownLatch,之后计数器就从这个值开始倒计数,直到计数值达到零。 + +* `public void countDown()` + + 这个函数用来将CountDownLatch的计数值减一 + +* `public void await() throws InterruptedException` + `public boolean await(long timeout, TimeUnit unit) throws InterruptedException` + + 这两个函数的作用都是让线程阻塞等待其他线程,直到CountDownLatch的计数值变为0才继续执行之后的操作。区别在于第一个函数没有等待时间限制,第二个函数给定一个等待超时时间,超过该时间就直接放弃了,并且第二个函数具有返回值,超时时间之内CountDownLatch的值达到0就返回true,等待时间结束计数值都还没达到0就返回false。这两个操作在等待过程中如果等待的线程被中断,则会抛出InterruptedException异常。 + +##### 测试例子 + +创建一定数量的线程,利用他们并发执行指定的任务,计算出总共花了多少时间. + +```java + + public class TestHarness { + public long timeTasks(int nThreads, final Runnable task) + throws InterruptedException { + final CountDownLatch startGate = new CountDownLatch(1); + final CountDownLatch endGate = new CountDownLatch(nThreads); + + for (int i = 0; i < nThreads; i++) { + Thread t = new Thread() { + public void run() { + try { + startGate.await(); //等待直到所有线程准备就绪,实现真正的并发执行任务 + try { + task.run(); + } finally { + endGate.countDown();//任务执行完毕,计数器减1; + } + } catch (InterruptedException ignored) { + } + } + }; + t.start(); + } + + long start = System.nanoTime(); + startGate.countDown(); //到此处,说明所有线程准备就绪,可以开始执行任务 + endGate.await();// 阻塞等待所有线程执行完毕. + long end = System.nanoTime(); + return end - start; + } + } + +``` + +#### 栅栏(Barrier) + +栅栏类似闭锁,能阻塞一组线程直到某个事件发生.栅栏与闭锁的区别关键在于:**闭锁用于等待事件,而栅栏用于等待其他线程**. + +CyclicBarrier可以使得一定数量的参与反复在栅栏位置汇聚,在并行迭代算法中非常有用:将一个问题拆分成一系列相互独立的子问题.当线程达到栅栏位置时调用await()阻塞直到所有线程到达栅栏位置,如果所有线程到达,栅栏打开,线程唤醒,而栅栏被重置下次使用.如果await()调用超时,或者阻塞的线程被中断,那么栅栏就算是被打破,所有await()调用会抛出BrokenBrrierException. + +CyclicBarrier还可以在构造函数中传入一个Runnable,当成功通过栅栏时会(在一个子线程中)执行他,但是阻塞线程被释放之前是不能执行的. + +构造函数 +##### 循环栅栏函数解析 + +* `public CyclicBarrier(int parties, Runnable barrierAction)` + `public CyclicBarrier(int parties)` + + 参数parties表示一共有多少线程参与这次"活动",参数barrierAction是可选的,用来指定当所有线程都完成这些必须的"神秘任务"之后需要干的事情,所以barrierAction这里的动作在一个相互等待的循环内只会执行一次。 + +* `blic int getParties()` + + 用来获取当前的CyclicBarrier一共有多少线程参数与. + + +* `public int await() throws InterruptedException, BrokenBarrierException` + `public int await(long timeout, TimeUnit unit)throws InterruptedException, BrokenBarrierException, TimeoutException` + + await函数用来执行等待操作,一个函数是一个无参函数,第二个函数可以指定等待的超时时间。它们的作用是:一直等待知道所有参与"活动"的线程都调用过await函数,如果当前线程不是即将调用await函数的的最后一个线程,当前线程将会被挂起,直到下列某一种情况发生: + + * 最后一个线程调用了await函数; + * 某个线程打断了当前线程; + * 某个线程打断了其他某个正在等待的线程; + * 其他某个线程等待时间超过给定的超时时间; + * 其他某个线程调用了reset函数。 + + 如果等待过程中线程被打断了,则会抛出InterruptedException异常; + 如果等待过程中出现下列情况中的某一种情况,则会抛出BrokenBarrierException异常: + + * 其他线程被打断了; + * 当前线程等待超时了; + * 当前CyclicBarrier被reset了; + * 等待过程中CyclicBarrier损坏了; + * 构造函数中指定的barrierAction在执行过程中发生了异常。 + + 如果等待时间超过给定的最大等待时间,则会抛出TimeoutException异常,并且这个时候其他已经嗲用过await函数的线程将会继续后续的动作。 + + 返回值:返回当前线程在调用过await函数的所以线程中的编号,编号为parties-1的表示第一个调用await函数,编号为0表示是最后一个调用await函数。 + + +* `public boolean isBroken()` + + 用来判断barrier是否已经损坏,如果因为任何原因被损坏返回true,否则返回false。 + +* `public void reset()` + + 这个函数用来重置barrier,如果调用了该函数,则在等待的线程将会抛出BrokenBarrierException异常。 + +* `public int getNumberWaiting()` + + 该函数用来获取当前正在等待该barrier的线程数 + +##### 实例展示: + +```java + + public class CellularAutomata { + private final Board mainBoard; + private final CyclicBarrier barrier; + private final Worker[] workers; + + public CellularAutomata(Board board) { + this.mainBoard = board; + int count = Runtime.getRuntime().availableProcessors(); + this.barrier = new CyclicBarrier(count, + new Runnable() { + public void run() { + mainBoard.commitNewValues();//栅栏打开,计算值 + }}); + this.workers = new Worker[count]; + for (int i = 0; i < count; i++) + workers[i] = new Worker(mainBoard.getSubBoard(count, i)); + } + + private class Worker implements Runnable { + private final Board board; + + public Worker(Board board) { this.board = board; } + public void run() { + while (!board.hasConverged()) { + for (int x = 0; x < board.getMaxX(); x++) + for (int y = 0; y < board.getMaxY(); y++) + board.setNewValue(x, y, computeValue(x, y)); + try { + barrier.await();//线程计算完毕等待其他线程 + } catch (InterruptedException ex) { + return; + } catch (BrokenBarrierException ex) { + return; + } + } + } + + private int computeValue(int x, int y) { + // Compute the new value that goes in (x,y) + return 0; + } + } + + public void start() { + for (int i = 0; i < workers.length; i++) + new Thread(workers[i]).start(); + mainBoard.waitForConvergence(); + } + + interface Board { + int getMaxX(); + int getMaxY(); + int getValue(int x, int y); + int setNewValue(int x, int y, int value); + void commitNewValues(); + boolean hasConverged(); + void waitForConvergence(); + Board getSubBoard(int numPartitions, int index); + } + } + + +``` + +#### 信号量(Semaphore) + +计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者执行某个指定操作的数量.还可以用来实现某种连接池,或者对容器加边界. + +Semaphore管理着一组虚拟的许可(permit),许可的初始数量可通过构造函数指定,在执行操作时先要获得许可,并在使用后释放许可.如果没有许可,accquire将阻塞到有许可为止(或者被中断,或者操作超时),release()将返回一个许可信号量. + +Semaphore类只是一个资源数量的抽象表示,并不负责管理资源对象本身,可能有多个线程同时获取到资源使用许可,因此需要使**用同步机制避免数据竞争**. + +#### Semaphore函数解析 + +* `public Semaphore(int permits)` + `public Semaphore(int permits, boolean fair)` + + 其中permits参数表示初始的可用资源数量,fair参数表示是否使用公平策略选择正在等候的使用者,fair为true表示公平策略,采用先来先用的算法,为false表示非公平策略,完全随机,默认使用非公平策略。 + + +* `public void acquire() throws InterruptedException` + `public void acquire(int permits) throws InterruptedException` + + acquire函数用来申请资源,第一个函数用来申请一个资源,第二个函数用来申请permits个资源,当没有需要申请的数量这么多个资源时,申请线程会被阻塞,直到有可用资源或者申请线程被打断,如果申请线程被打断,则抛出InterruptedException异常。 + +* `public void acquireUninterruptibly()` + `public void acquireUninterruptibly(int permits)` + + 该函数用来申请可用资源,并且不会被打断,第一个函数用来申请一个资源,第二个函数用来申请permits个资源。就算线程在申请资源过程中被打断,依然会继续申请,只不过获取资源的时间可能会有所变化。 + + +* `public boolean tryAcquire()` + `public boolean tryAcquire(long timeout, TimeUnit unit)throws InterruptedException` + `public boolean tryAcquire(int permits)` + `public boolean tryAcquire(int permits, long timeout, TimeUnit unit)throws InterruptedException` + + tryAcquire函数用来获取可用资源,但是这类函数能够有时间的限制,如果超时,立即返回, + + 第一个函数用来申请一个资源,如果当前有可用资源,立即返回true,否则立即返回false; + 第二个函数用来申请一个资源,指定一个超时时间,如果当前可以资源数量足够,立即返回true,否则最多等待给定的时间,如果时间到还是未能获取资源,则返回false;如果等待过程中线程被打断,抛出InterruptedException异常; + 和1一样,只是申请permits个资源; + 和2一样,只是申请permits个资源。 + + +* `public void release()` + `public void release(int permits)` + + 第一个函数释放一个资源,第二个函数释放permits个资源。 + + +* `public int availablePermits()` +* + availablePermits函数用来获取当前可用的资源数量 + + +* `public int drainPermits()` + drainPermits函数用来申请当前所有 +可用的资源, + + +* `protected void reducePermits(int reduction)` + + 用来禁止某些资源不可用 + reduction表示禁止的数量,比如由于厕所马桶坏了,有一个坑位不能用,此时就可以调用该函数禁止一个资源不可用。如果reduction小于零,则抛出IllegalArgumentException异常。 + + + +* `public boolean isFair()` + + 函数返回true表示采用的是公平策略,返回false表示采用非公平策略。 + + +* `public final boolean hasQueuedThreads()` + + 用来判断是否有现成正在等待申请资源,返回true表示有现成正在等待申请资源,false表示没有,需要注意的是:因为申请过程是可以取消的,函数返回true并不表示肯定会申请资源,该函数设计的初衷是用来做系统监控的。 + + +* `public final int getQueueLength()` + + 返回当前正在等待申请资源的线程数。 + +* `protected Collection getQueuedThreads()` + + 返回当前正在等待申请资源的线程集合 + + +##### 多线程同时操作特定资源例子 + +```java + + public class SemaphoreDemo { + + private final ReentrantLock lock = new ReentrantLock(); + private final Semaphore semaphore; + private final LinkedList resourceList = new LinkedList(); + private static CountDownLatch mCountDownLatch = new CountDownLatch(9); + + public SemaphoreDemo(Collection resourceList) { + this.resourceList.addAll(resourceList); + //公平模式 + this.semaphore = new Semaphore(resourceList.size(), true); + } + + + public Object acquire() throws InterruptedException { + semaphore.acquire(); + + lock.lock(); + try { + return resourceList.pollFirst(); + } finally { + lock.unlock(); + } + } + + public void release(Object resource) { + lock.lock(); + try { + resourceList.addLast(resource); + } finally { + lock.unlock(); + } + + semaphore.release(); + } + + public static void main(String[] args) { + //准备2个可用资源 + List resourceList = new ArrayList(); + resourceList.add("Resource1"); + resourceList.add("Resource2"); + + //准备工作任务 + final SemaphoreDemo demo = new SemaphoreDemo(resourceList); + Runnable worker = new Runnable() { + @Override + public void run() { + Object resource = null; + try { + //获取资源 + resource = demo.acquire(); + System.out.println(Thread.currentThread().getName() + "\twork on\t" + resource); + //用resource做工作 + Thread.sleep(1000); + System.out.println(Thread.currentThread().getName() + "\tfinish on\t" + resource); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + //归还资源 + if (resource != null) { + demo.release(resource); + mCountDownLatch.countDown(); + + } + } + } + }; + + //启动9个任务 + ExecutorService service = Executors.newCachedThreadPool(); + for (int i = 0; i < 9; i++) { + service.submit(worker); + } + + + try { + mCountDownLatch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + service.shutdown(); + } + } + +``` + + +##### 将任何一种容器变成有界阻塞容器 + +```java + + public class BoundedHashSet { + private final Set set; + private final Semaphore sem; + + public BoundedHashSet(int bound) { + this.set = Collections.synchronizedSet(new HashSet()); + sem = new Semaphore(bound); + } + + public boolean add(T o) throws InterruptedException { + sem.acquire(); + boolean wasAdded = false; + try { + wasAdded = set.add(o); + return wasAdded; + } finally { + if (!wasAdded) + sem.release(); + } + } + + public boolean remove(Object o) { + boolean wasRemoved = set.remove(o); + if (wasRemoved) + sem.release(); + return wasRemoved; + } + } + +``` diff --git "a/showdy_note/java/Java345円271円266円345円217円221円344円271円213円351円230円273円345円241円236円351円230円237円345円210円227円.md" "b/showdy_note/java/Java345円271円266円345円217円221円344円271円213円351円230円273円345円241円236円351円230円237円345円210円227円.md" new file mode 100644 index 0000000..175d8de --- /dev/null +++ "b/showdy_note/java/Java345円271円266円345円217円221円344円271円213円351円230円273円345円241円236円351円230円237円345円210円227円.md" @@ -0,0 +1,242 @@ +### 队列 + +队列是先进先出(FIFO)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。 + +|操作|抛出异常|有返回值| +| ------------- |:-------------:| -----:| +|Insert| add(e)| offer(e)| +|Remove| remove()| poll()| +|Examine| element()| peek()| + +![](http://www.uml-diagrams.org/examples/java-7-concurrent-collections-uml-class-diagram-example.png) + + +### 阻塞队列 + +阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加操作支持阻塞的插入和移除方法. + +* 支持阻塞的插入方法: 当队列满时,队列会阻塞插入元素的线程,直到队列不满为止. +* 支持阻塞的移除方法: 当队列为空时,获取元素的线程阻塞等待线程非空. + +阻塞队列通常用于生产者和消费者的场景,生产者就是向队列里添加元素,而消费者就是从队列里取出元素. 阻塞队列就是生产者存储元素而消费者用来获取元素的容器. + +|操作方式|抛出异常|返回特殊值|一直阻塞| 超时退出| +| ------- |:-------:| -----:|-----:|-----:| +|插入| add(e)| offer(e)|put(e)|offer(e,time,unit)| +|移除| remove()| poll()|take()|poll(time,unit)| +|检查| element()| peek()|不可用|不可用| + +注意: 如果是无界阻塞队列,队列永远都不会出现满的情况,所以使用put或者take方法永远都不会被阻塞,而且使用put方法时,该方法永远返回为true. + + +### JDK提供的阻塞队列 + +从上面的UML图可以看到,JKD7提供了7个阻塞队列: + +* `ArrayBlockingQueue`: 由数组结构组成的有界阻塞队列 +* `LinkedBlockingQueue`: 由链表结构组成的有界阻塞队列 +* `PriorityBlockingQueue`: 支持优先级排序的无界阻塞队列 +* `DelayQueue`: 使用优先级队列队列实现的无界阻塞队列 +* `SynchronousQueue`: 不存储元素的阻塞队列 +* `LinkedTransferQueue`: 由链表结构组成的无界阻塞队列 +* `LinkedBlockingDeque`: 由链表结构组成的双向阻塞队列 + +#### `ArrayBlockingQueue` + +`ArrayBlockingQueue`是一个用数组实现的有界队列,此队列按照先进先出的原则对元素进行排序. + +默认情况下不保证线程公平的访问队列,所谓公平访问队列是指阻塞的线程,可以按照阻塞的先后顺序访问队列,即先阻塞线程先访问队列.非公平性对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问队列. + +为了保证公平性,通常会降低吞吐量,可以使用以下代码创建一个公平的阻塞队列. + +```java + + ArrayBlockingQueue fairQueue= new ArrayBlockingQueue(1000,true); + +``` + +访问者的公平性是使用可重入锁实现的,代码如下: + +```java + + public ArrayBlockingQueue(int capacity, boolean fair) { + if (capacity <= 0) + throw new IllegalArgumentException(); + this.items = new Object[capacity]; + lock = new ReentrantLock(fair); + notEmpty = lock.newCondition(); + notFull = lock.newCondition(); + } + +``` + + +#### `LinkedBlockingQueue` + +`LinkedBlockingQueue`是一个用链表实现的有界阻塞队列,此队列默认最大长度为Integer.MAX_VALUE,按照先进先出(FIFO)的原则对元素进行排序 + + +#### `PriorityBlockingQueue` + +`PriorityBlockingQueue`是一个支持优先级的无界阻塞队列,默认情况下元素采用自然排序升序排列,也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化`PriorityBlockingQueue`时,指定构造参数Comparator来对元素进行排序,需要注意的是不能保证同优先级的元素排序. + +#### `DelayQueue` + +`DelayQueue`是一个支持延时获取元素的无界阻塞队列,队列使用PriorityQueue来实现. 队列中元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素.只有延迟期满时才能从队列中提出元素. + +`DelayQueue`非常有用,可以将`DelayQueue`运用在一下场景: + +* 缓存系统的设计: 可以送`DelayQueue`保存缓存元素的有效期,使用一个线程循环查询`DelayQueue`,一旦从`DelayQueue`获取元素,就表示缓存到期了. +* 定时任务调度:使用`DelayQueue`保存当前将会执行的任务和执行时间,一旦从`DelayQueue`中获取到任务就开始执行,比如TimeQueue就是使用`DelayQueue`实现的. + +#### `SynchronousQueue` +`SynchronousQueue`是一个不存储元素的阻塞队列,每个put操作必须等待一个take操作,否则不能继续添加元素. + +支持公平访问队列,默认情况下线程采用非公平性策略,使用带boolean参数的构造方法可以实现等待线程采用先进先出(FIFO)的顺序访问队列. + +```java + + public SynchronousQueue(boolean fair) { + transferer = fair ? new TransferQueue() : new TransferStack(); + } + +``` +#### `LinkedTransferQueue` + +`LinkedTransferQueue`是一个由链表结构组成的无界阻塞TransferQueue队列,相当于其他阻塞队列,LinkedTransferQueue多了一tryTransfer和transfer方法. + +* transfer方法 + + 如果当前有消费者正在等待接收元素(消费者使用take()方法或者带时间限制的poll方式时)transfer()方法可以吧生产者传入的元素立即transfer(传输)给消费者,如果没有消费者在等待接收元素,transfer方法将元素存放在队列的tail节点,并等待该元素被消费者消费了才返回. + +* tryTransfer方法 + + tryTransfer方法用来试探生产者传入元素是否能够直接传递给消费者,如果没有消费者等待接收元素.则返回false, 和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回,而transfer需要等待消费者消费了才返回. + +#### `LinkedBlockingDeque` + +`LinkedBlockingDeque`是一由链表结构组成的双向阻塞队列,所谓双向队列指的是可以从队列两端插入和移除元素,双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一般竞争.相比其他阻塞队列,`LinkedBlockingDeque`多了addFirst, addLast,offerFirst,offerLast,peekFirst,peekLast等方法. + +在初始化`LinkedBlockingDeque`时可以设置容量防止其过渡膨胀, 另外,双向阻塞队列可以运行在"工作窃取"模式中. + + +### 阻塞队列实现的原理 + +**通知模式实现**: 所谓通知模式,就是当生产者从满的队列里添加元素时会阻塞生产者,而当消费者消费了一个队列中的元素后,就会通知生产者当前队列可用. **ArrayBlockingQueue使用ReentrantLock和Condition实现**. + +```java + + /** Main lock guarding all access */ + final ReentrantLock lock; + /** Condition for waiting takes */ + private final Condition notEmpty; + /** Condition for waiting puts */ + private final Condition notFull; + + public ArrayBlockingQueue(int capacity, boolean fair) { + if (capacity <= 0) + throw new IllegalArgumentException(); + this.items = new Object[capacity]; + lock = new ReentrantLock(fair); + notEmpty = lock.newCondition(); + notFull = lock.newCondition(); + } + + public void put(E e) throws InterruptedException { + Objects.requireNonNull(e); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + while (count == items.length) + notFull.await(); + enqueue(e); + } finally { + lock.unlock(); + } + } + + public E take() throws InterruptedException { + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + while (count == 0) + notEmpty.await(); + return dequeue(); + } finally { + lock.unlock(); + } + } + + private void enqueue(E x) { + // assert lock.getHoldCount() == 1; + // assert items[putIndex] == null; + final Object[] items = this.items; + items[putIndex] = x; + if (++putIndex == items.length) putIndex = 0; + count++; + notEmpty.signal(); + } + + /** + * Extracts element at current take position, advances, and signals. + * Call only when holding lock. + */ + private E dequeue() { + // assert lock.getHoldCount() == 1; + // assert items[takeIndex] != null; + final Object[] items = this.items; + @SuppressWarnings("unchecked") + E x = (E) items[takeIndex]; + items[takeIndex] = null; + if (++takeIndex == items.length) takeIndex = 0; + count--; + if (itrs != null) + itrs.elementDequeued(); + notFull.signal(); + return x; + } +``` + +当往队列里插入一个元素时,如果队列不可用,那么阻塞生产者主要通过LockSupport.part(this)实现: + +```java + + public final void await() throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + Node node = addConditionWaiter(); + int savedState = fullyRelease(node); + int interruptMode = 0; + while (!isOnSyncQueue(node)) { + LockSupport.park(this); + if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) + break; + } + if (acquireQueued(node, savedState) && interruptMode != THROW_IE) + interruptMode = REINTERRUPT; + if (node.nextWaiter != null) // clean up if cancelled + unlinkCancelledWaiters(); + if (interruptMode != 0) + reportInterruptAfterWait(interruptMode); + } + +``` + +然后看看LockSupport的源码:发现调研setBlocker先保存一下将要阻塞的线程,然后代用unsafe.park阻塞当前线程: + +```java + + public static void park(Object blocker) { + Thread t = Thread.currentThread(); + setBlocker(t, blocker); + U.park(false, 0L); + setBlocker(t, null); + } +``` + +park是个native方法,会阻塞当前线程,只有以下四种情况中一种发生时,该返回才会返回. + +* 与park相对的unpark执行或者已经执行. "已经执行"是指执行unpark,再执行park的情况 +* 线程被中断时 +* 等待完time参数指定的毫秒数时 +* 异常现象发生时,这个异常现象没有任何原因 diff --git "a/showdy_note/java/Java345円271円266円345円217円221円345円244円232円347円272円277円347円250円213円(344円270円200円)344円271円213円ThreadPoolExecutor.md" "b/showdy_note/java/Java345円271円266円345円217円221円345円244円232円347円272円277円347円250円213円(344円270円200円)344円271円213円ThreadPoolExecutor.md" new file mode 100644 index 0000000..b47ac58 --- /dev/null +++ "b/showdy_note/java/Java345円271円266円345円217円221円345円244円232円347円272円277円347円250円213円(344円270円200円)344円271円213円ThreadPoolExecutor.md" @@ -0,0 +1,297 @@ +### 线程池的优势: + +Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks,due to reduced per-task invocation overhead,and they provide a means of bounding and managing the resources, including threads, consumed when executing a collection of tasks. + + +线程池主要解决了两个方面的问题: + +* 在执行大量异步任务时,线程池由于减少每次任务调用开销而提高了性能. +* 在执行大量任务时,线程池提供了可限制和管理资源(比如线程的消耗)的方法. + + +### 线程池的创建: + +可以通过ThreadPoolExecutor构造函数来创建一个线程池: + +```java + + public ThreadPoolExecutor(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory, + RejectedExecutionHandler handler) + +``` + +创建一个线程池需要输入几个参数: + +* `corePoolSize`(**核心线程数**):,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等需要执行的任务数大于线程池的基本大小时就不会创建. 如果任务数大于corePoolSize,小于maximumPoolSize时,线程也仅仅在队列满了的情况下才会创建. + +* `maximumPoolSize`(**线程池最大大小**):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了**无界的任务队**列这个参数就没什么效果。 + +* `keepAliveTime`(**线程活动保持时间**):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。 + +* `TimeUnit`(**线程活动保持时间的单位**):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。 + +* `BlockingQueue`(**任务队列**):用于保存等待执行的任务的阻塞队列。 + * ArrayBlockingQueue:是一个基于数组结构的**有界阻塞队列(bounded queue)**,此队列按 FIFO(先进先出)原则对元素进行排序。 + * LinkedBlockingQueue:一个基于链表结构的**阻塞队列(unbounded queue)**,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。 + * SynchronousQueue:一个**不存储元素的阻塞队列(handsoff)**。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。 + * PriorityBlockingQueue:一个具有优先级的无界阻塞队列。 + +* `ThreadFactory`(**线程工厂**):用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字. + +* `RejectedExecutionHandler`(**饱和策略**):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。 + * AbortPolicy:直接抛出异常。 + * CallerRunsPolicy:只用调用者所在线程来运行任务。 + * DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 + * DiscardPolicy:不处理,丢弃掉。 + * 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。 + +### 向线程池提交任务: + +* `public void execute(Runnable command)`: 执行一个Runnable任务,没有返回值,无法判断线程池执行是否成功,主要分三步: + * 活动线程小于corePoolSize的时候就创建新线程池 + * 活动线程大于corePoolSize时就想加入到任务队列中 + * 任务队列满了再去启动新线程,如果线程数达到最大值就拒绝任务. + + ```java + + public void execute(Runnable command) { + if (command == null) + throw new NullPointerException(); + + int c = ctl.get(); + // 活动线程数 < corePoolSize + if (workerCountOf(c) < corePoolSize) { + // 直接启动新的线程。第二个参数true:addWorker中会重新检查workerCount是否小于corePoolSize + if (addWorker(command, true)) + // 添加成功返回 + return; + c = ctl.get(); + } + // 活动线程数>= corePoolSize + // runState为RUNNING && 队列未满 + if (isRunning(c) && workQueue.offer(command)) { + int recheck = ctl.get(); + // double check + // 非RUNNING状态 则从workQueue中移除任务并拒绝 + if (!isRunning(recheck) && remove(command)) + reject(command);// 采用线程池指定的策略拒绝任务 + // 线程池处于RUNNING状态 || 线程池处于非RUNNING状态但是任务移除失败 + else if (workerCountOf(recheck) == 0) + // 这行代码是为了SHUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。 + // 添加一个null任务是因为SHUTDOWN状态下,线程池不再接受新任务 + addWorker(null, false); + + // 两种情况: + // 1.非RUNNING状态拒绝新的任务 + // 2.队列满了启动新的线程失败(workCount> maximumPoolSize) + } else if (!addWorker(command, false)) + reject(command); + } + + ``` + +* ` Future submit(Callable task)`: + + ****执行一个Runnable或者一个Callable任务,返回一个Future来判断任务否执行成功,通过{@Link Funtrure#get()}获取执行的结果,get()方法会阻塞直到任务完成或者失败. + + ```java + + Future future = executor.submit(harReturnValuetask); + try { + Object s = future.get(); + } catch (InterruptedException e) { + // 处理中断异常 + } catch (ExecutionException e) { + // 处理无法执行任务异常 + } finally { + // 关闭线程池 + executor.shutdown(); + } + + ``` + + + +### 线程池的关闭 + +* `shutdown()` 会将runState设置为SHUTDOWN,会**终止所有的空闲线程**. + +```java + + public void shutdown() { + final ReentrantLock mainLock = this.mainLock; + mainLock.lock(); + try { + checkShutdownAccess(); + //将线程池状态设置为SHUTDOWN + advanceRunState(SHUTDOWN); + //注意这里是中断所有空闲的线程:runWorker中等待的线程被中断 → 进入processWorkerExit → + // tryTerminate方法中会保证队列中剩余的任务得到执行。 + interruptIdleWorkers(); + onShutdown(); // hook for ScheduledThreadPoolExecutor + } finally { + mainLock.unlock(); + } + tryTerminate(); + } + +``` + +* `shutdownNow()`将runState设置为STOP,和shutdown()区别是**这个方法终止所有线程**. + +```java + + public List shutdownNow() { + List tasks; + final ReentrantLock mainLock = this.mainLock; + mainLock.lock(); + try { + checkShutdownAccess(); + // STOP状态:不再接受新任务且不再执行队列中的任务。 + advanceRunState(STOP); + // 中断所有线程 + interruptWorkers(); + // 返回队列中还没有被执行的任务。 + tasks = drainQueue(); + } finally { + mainLock.unlock(); + } + tryTerminate(); + return tasks; + } + +``` + +* `boolean isShutdwon()` + +```java + + public boolean isShutdown() { + //说明只要调用了shutdown()或者shutdwonNow()之一,此方法就会返回ture. + return ! isRunning(ctl.get()); + } + + private static boolean isRunning(int c) { + return c < SHUTDOWN; + } + +``` + +* `boolean isTerminated()` 当所有线程都终止时此方法才返回true. + +```java + + public boolean isTerminated() { + return runStateAtLeast(ctl.get(), TERMINATED); + } + + private static boolean runStateAtLeast(int c, int s) { + return c>= s; + } + +``` + +我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。 + +只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。 + + +### AtomicInteger ctl + +`private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));` + +AtomicInteger保证了对这个变量的操作是原子的,通过巧妙的操作,ThreadPoolExecutor用这一个变量保存了两个内容: + +* 所有有效线程的数量 +* 各个线程的状态(runState) + +低29位存线程数,高3位存runState,这样runState有5个值: + +```java + + private static final int COUNT_BITS = Integer.SIZE - 3; + private static final int CAPACITY = (1 << COUNT_BITS) - 1; + + private static final int RUNNING = -1 << COUNT_BITS; + private static final int SHUTDOWN = 0 << COUNT_BITS; + private static final int STOP = 1 << COUNT_BITS; + private static final int TIDYING = 2 << COUNT_BITS; + private static final int TERMINATED = 3 << COUNT_BITS; +``` + +线程池中各个状态间的转换比较复杂: + +* RUNNING状态:线程池正常运行,可以接受新的任务并处理队列中的任务; +* SHUTDOWN状态:不再接受新的任务,但是会执行队列中的任务; +* STOP状态:不再接受新任务,不处理队列中的任务 + +围绕ctl变量操作如下: + +```java + + /* + * 该方法用于取出runstate的值,因为CAPACTIY值为:00011111111111111111111111111111 + * ~为按位取反操作,则~CAPACITY值为:11100000000000000000000000000000 + * 再同参数做&操作,就将低29位置0了,而高3位还是保持原先的值,也就是runState的值 + * / + private static int runStateOf(int c) { + return c & ~CAPACITY; + } + + /** + * 这个方法用于取出workerCount的值 + * 因为CAPACITY值为:00011111111111111111111111111111,所以&操作将参数的高3位置0了 + * 保留参数的低29位,也就是workerCount的值 + * + * @param c ctl, 存储runState和workerCount的int值 + * @return workerCount的值 + */ + private static int workerCountOf(int c) { + return c & CAPACITY; + } + + /** + * 将runState和workerCount存到同一个int中 + * "|"运算的意思是,假设rs的值是101000,wc的值是000111,则他们位或运算的值为101111 + * + * @param rs runState移位过后的值,负责填充返回值的高3位 + * @param wc workerCount移位过后的值,负责填充返回值的低29位 + * @return 两者或运算过后的值 + */ + private static int ctlOf(int rs, int wc) { + return rs | wc; + } + + // 只有RUNNING状态会小于0 + private static boolean isRunning(int c) { + return c < SHUTDOWN; + } + +``` + + +### 线程池配置策略: + +要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析: + +1. 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。 +2. 任务的优先级:高,中和低。 +3. 任务的执行时间:长,中和短。 +4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。 + +任务性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能小的线程,如配置Ncpu+1个线程的线程池。IO密集型任务则由于线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。 + +优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。 + +执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。 + +依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。 + +**建议使用有界队列**,有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点,比如几千。有一次我们组使用的后台任务线程池的队列和线程池全满了,不断的抛出抛弃任务的异常,通过排查发现是数据库出现了问题,导致执行SQL变得非常缓慢,因为后台任务线程池里的任务全是需要向数据库查询和插入数据的,所以导致线程池里的工作线程全部阻塞住,任务积压在线程池里。如果当时我们设置成无界队列,线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用,而不只是后台任务出现问题。当然我们的系统所有的任务是用的单独的服务器部署的,而我们使用不同规模的线程池跑不同类型的任务,但是出现这样问题时也会影响到其他任务 + + diff --git "a/showdy_note/java/Java350円256円276円350円256円241円346円250円241円345円274円217円344円271円213円Singleton.md" "b/showdy_note/java/Java350円256円276円350円256円241円346円250円241円345円274円217円344円271円213円Singleton.md" new file mode 100644 index 0000000..7d33be5 --- /dev/null +++ "b/showdy_note/java/Java350円256円276円350円256円241円346円250円241円345円274円217円344円271円213円Singleton.md" @@ -0,0 +1,193 @@ +### Singleton单例模式简介 + +在应用单例模式时,单例对象的类必须保证只有一个实例存在.许多时候整个系统只需要一个全局对象, 这样有利于协调系统整体行为,如在一个应用中,应该只有ImageLoader实例,这个ImageLoader中又含有线程池,缓存系统,网络请求等, 很消耗资源,所以不能多次构建实例. + +构建单例模式要保证几点: + +* 构造方法不对外开放,一般为private +* 通过一个静态方法或者枚举返回单例类对象 +* 确保单例对象有且只有一个,尤其在多线程的环境下. +* 确保单例对象在反序列化时不会重新构建对象. + + +### 构建单例模式的方式: + +* 懒汉式: 指全局的单例实例在第一次被使用时构建 +* 饿汉式: 指全局的单例实例在类转载时构建. + + +#### 懒汉式单例 + +单线程的懒汉式单例: + +```java + + public class Singleton { + + private static Singleton sInstance; + + private Singleton() { + } + + public Singleton getInstance() { + if (sInstance == null) { + sInstance = new Singleton(); + } + return sInstance; + } + } + +``` +这种最简单的懒汉式单例只有在单线程中才有作用, 如果在多线程中由于多线程执行的问题的会因为线程并发的问题产生多个实例.所以我们需要同步即: + +```java + + public class Singleton { + + private static Singleton sInstance; + + private Singleton() { + } + + public Singleton getInstance() { + if (sInstance == null) { + synchronized (Singleton.class) { + sInstance = new Singleton(); + } + } + return sInstance; + } + } + +``` +但这种线程同步还是不能做到线程安全的问题, 还是会产生多个实例对象, 所以我们再一次进行判断nul: + +```java + + public class Singleton { + + private static Singleton sInstance; + + private Singleton() { + } + + public Singleton getInstance() { + if (sInstance == null) { + synchronized (Singleton.class) { + if (sInstance == null) { + sInstance = new Singleton(); + } + } + } + return sInstance; + } + } + +``` + +这种双重判断DCL单例在JDK小于1.5时还是会为因为构造对象出现指令重排序的问题, 故而给单例对象添加volatile关键字修饰, 禁止指令重排序; 所以最终版为: + +```java + + public class Singleton { + + private static volatile Singleton sInstance; + + private Singleton() { + } + + public Singleton getInstance() { + if (sInstance == null) { + synchronized (Singleton.class) { + if (sInstance == null) { + sInstance = new Singleton(); + } + } + } + return sInstance; + } + } + +``` + +从上面代码中可以看到: + +* 构造方法私有化; +* 单例对象使用volatile修饰; +* 使用静态方法getInstance()返回单例对象; +* 在构建单例对象使用Double-CheckLock(DCL); + +使用DLC双重检查,第一次判断sInstance=null,是为了避免不必要的同步问题,第二次判断sInstance=null是为了避免多次创建实例对象.其实在构建实例sInstance=new Singleton()时, 这句代码并不是一个原子操作,可以分为三步: + +* 给Singleton实例分配内存 +* 调用Singleton()构造函数,初始化成员变量字段 +* 将sInstance对象指向分配的内存空间. + +但是由于**指令重排序**的原因,可能不保证上述三点不按照顺序执行,可能是1-2-3,也可能是2-1-3或者1-3-2,如果是第三点先执行,第二点还未执行,就切换到其他线程,sInstance已经非空,就不会构建实例,使用时就会崩溃. + +知道是指令重排序造成的问题后,只要禁止指令重排序既可, 就可以使用**volatile关键字**修饰sInstance,保证单例对象内存可见性. + +#### 饿汉式单例 + +```java + + public class Singleton{ + + private static final Singleton sInstance= new Singleton(); + + private Singleton(){}; + + public static Singleton getInstance(){ + return sInstance; + } + + } + +``` + +饿汉式单例存在的特点也很明显: 由于sInstance实例在类加载时进行的,而类的加载是由ClassLoader来进行,故而实例的初始化时机比较难把握,可能由于初始化时机太早,造成资源浪费,如果初始化本身依赖一些其他数据,那么很难保证其他数据在这之前已经准备就绪. + +那么什么时候会类加载? 不太严格的说,类的加载一般会出现在一下几个时机: + +* new一个对象是; +* 使用反射创建实例时; +* 子类被加载时,如果父类还未加载,就先加载父类 +* JVM启动执行的主类会首先被加载. + +### 静态内部类单例模式 + +DCL双重检查单例模式虽然在一定程度上解决了资源消耗,多余的同步,线程安全问题,但是他还是会出现失效问题, 这种问题被称为双重检查锁定(DCL)失效,建议使用如下代码: + +```java + + public class Singleton { + + private Singleton(){}; + + public static Singleton getInstance(){ + return SingletonHolder.sInstance; + } + + private static class SingletonHolder{ + private static final Singleton sInstance= new Singleton(); + } + } + +``` +当第一次加载Singleton类并不会初始化sInstance,只有在第一次调用Singleton的getInstance()才会初始化sInstance. + + +### 枚举单例 + +```java + + public enum SingletonEnum{ + INSTANCE; + public void doSomething(){ + //... + } + } + +``` + +枚举单例的最大优势在于,无偿提供了序列化机制,绝对防止对象实例化,即使在面对复杂的序列化或者反射攻击的时候. diff --git "a/showdy_note/java/344円275円215円350円277円220円347円256円227円347円232円204円345円272円224円347円224円250円.md" "b/showdy_note/java/344円275円215円350円277円220円347円256円227円347円232円204円345円272円224円347円224円250円.md" new file mode 100644 index 0000000..91a4863 --- /dev/null +++ "b/showdy_note/java/344円275円215円350円277円220円347円256円227円347円232円204円345円272円224円347円224円250円.md" @@ -0,0 +1,164 @@ +### 使用位运算进行基本数据类型之间转换 + +```java + + //long--64位 + public static long bytesToLong(byte[] array) { + return ((long) array[0] & 0xff) << 56 + | ((long) array[1] & 0xff) << 48 + | ((long) array[2] & 0xff) << 40 + | ((long) array[3] & 0xff) << 32 + | ((long) array[4] & 0xff) << 24 + | ((long) array[5] & 0xff) << 16 + | ((long) array[6] & 0xff) << 8 + | ((long) array[7] & 0xff); + } + + public static byte[] longToBytes(long n) { + byte[] b = new byte[8]; + b[7] = (byte) (n & 0xff); + b[6] = (byte) (n>> 8 & 0xff); + b[5] = (byte) (n>> 16 & 0xff); + b[4] = (byte) (n>> 24 & 0xff); + b[3] = (byte) (n>> 32 & 0xff); + b[2] = (byte) (n>> 40 & 0xff); + b[1] = (byte) (n>> 48 & 0xff); + b[0] = (byte) (n>> 56 & 0xff); + return b; + } + + public static byte[] intToBytes(int n) { + byte[] b = new byte[4]; + b[3] = (byte) (n & 0xff); + b[2] = (byte) (n>> 8 & 0xff); + b[1] = (byte) (n>> 16 & 0xff); + b[0] = (byte) (n>> 24 & 0xff); + return b; + } + + public static int bytesToInt32(byte[] b) { + return b[3] & 0xff + | (b[2] & 0xff) << 8 + | (b[1] & 0xff) << 16 + | (b[0] & 0xff) << 24; + } + + public static byte[] shortToBytes(short n) { + byte[] b = new byte[2]; + b[1] = (byte) (n & 0xff); + b[0] = (byte) ((n>> 8) & 0xff); + return b; + } + + public static short bytesToShort(byte[] b) { + return (short) (b[1] & 0xff | (b[0] & 0xff) << 8); + } +``` + +### 解析接收的数据: + +例如在做TCP通讯时,需要解析接收到的字段,查看接口文档如下: + +|序号| 字段名称| 属性| 类型| 长度| 说明| +| :----- |:-----| :-----| :-----| :-----| :-----| +|1 |固件版本| 必选| Bytes| 4| 固件版本,版本号:a.b.c.d, 其中a,b分别存储在第一字节高、低半字节;c存储在第二字节,d存储在第三、四字节(大端字节序)。| + +```java + + public static String decodeVerToStr(byte[] bytes) { + int a = (bytes[0] & 0xf0)>> 4; + int b = (bytes[0] & 0x0f); + int c = (bytes[1]) & 0x00ff; + long d = ((bytes[2]<<4)| bytes[3] ; + + return String.format("%d.%d.%d.%d",a,b,c,d); + } + +``` + +### Integer源码解析: + +1. 将一个10进制的Int值,转为为2,8,16,32进制数. + + ```java + + final static char[] digits = { + '0' , '1' , '2' , '3' , '4' , '5' , + '6' , '7' , '8' , '9' , 'a' , 'b' , + 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , + 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , + 'o' , 'p' , 'q' , 'r' , 's' , 't' , + 'u' , 'v' , 'w' , 'x' , 'y' , 'z' + }; + + //将一个int转为无符号string + private static String toUnsignedString(int i, int shift) { + char[] buf = new char[32]; + int charPos = 32; + //进制 + int radix = 1 << shift; + //掩码 + int mask = radix - 1; + do { + buf[--charPos] = digits[i & mask]; + i>>>= shift; + } while (i != 0); + + return new String(buf, charPos, (32 - charPos)); + } + + public static String toBinaryString(int i) { + return toUnsignedString(i, 1); + } + + public static String toOctalString(int i) { + return toUnsignedString(i, 3); + } + + public static String toHexString(int i) { + return toUnsignedString(i, 4); + } + ``` + + 1.掩码mask计算: shift=1(二进制),shift=3(八进制), shift=4(十六进制),因为1-bit可以表示一位二进制数, 3-bit表示一为八进制数,4-bit表示一位十六进制数. + + 2.右移使用的是>>>而不是>>。位运算中的右移分为算术右移(>>)和逻辑右移(>>>)。在进行算术右移时,最高位补符号位;而在进行逻辑右移时,最高位补0。如果这里使用的算术右移,那么对于像-1这样的负数,不论进行多少次右移操作都不可能变成0,所以会造成死循环。 + + 3.使用的是do-while而不是while,是为了匹配int=0的情况. + +2. 获取一个Int高位1-bit和低位1-bit对应的值. + + ```java + + //一个int数,高位首次出现1-bit的int数. + public static int highestOneBit(int i) { + // HD, Figure 3-1 + i |= (i>> 1);// ... 0001 + i |= (i>> 2);// ... 0011 + i |= (i>> 4);// ... 1111 1111(16个1) + i |= (i>> 8);// 1111 ... 1111 (32个1) + i |= (i>> 16); + return i - (i>>> 1); + } + + //获取一个int数地位首次出现1-bit的int数. + public static int lowestOneBit(int i) { + // HD, Section 2-1 + return i & -i; + } + + Integer.toBinaryString(Integer.highestOneBit(Integer.MAX_VALUE))); + //01000000000000000000000000000000 + Integer.toBinaryString(Integer.highestOneBit(Integer.MIN_VALUE))); + //10000000000000000000000000000000 + Integer.toBinaryString(Integer.highestOneBit(-1))); + //10000000000000000000000000000000 + Integer.toBinaryString(Integer.lowestOneBit(Integer.MAX_VALUE))); + //0000....0001 + Integer.toBinaryString(Integer.lowestOneBit(Integer.MIN_VALUE))); + //10000000000000000000000000000000 + Integer.toBinaryString( Integer.lowestOneBit(-1))); + //0000...0001 + ``` + + * 对于1<

AltStyle によって変換されたページ (->オリジナル) /