-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 164 KB
/
content.json
1
{"meta":{"title":"We're here to put a dent in the universe","subtitle":"爱生活,爱编程","description":"YasinYao的博客","author":"Yaisn Yao","url":"https://www.jianshu.com/u/3e759a2c717f"},"pages":[],"posts":[{"title":"Android消息机制知识点总结","slug":"Android消息机制 知识点总结","date":"2019-03-13T08:07:36.000Z","updated":"2019-04-17T02:54:58.142Z","comments":true,"path":"2019/03/13/Android消息机制 知识点总结/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2019/03/13/Android消息机制 知识点总结/","excerpt":"这篇文章主要介绍Android的消息机制的几个组成部分以及重点要理解的点","text":"这篇文章主要介绍Android的消息机制的几个组成部分以及重点要理解的点 Android消息机制 知识点总结1. ThreadLocal总结ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据;不同线程对应不同的值。 用法简介123456//创建ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();//set方法mStringThreadLocal.set("yasinyao.com");//get方法mStringThreadLocal.get(); 总结:实际上ThreadLocal的值是放入了当前线程的一个ThreadLocalMap实例中,所以只能在本线程中访问,其他线程无法访问。 注意:Android的ThreadLocal与Java实现略有不同,但是原理是一致的。 2. MessageQueue总结Android消息队列在Android中是指MessageQueue,MessageQueue主要包括插入(enqueueMessage方法)和读取(next方法)消息队列是一个单链表来维护消息列表,便于删除和添加数据 3.Looper总结Looper在消息机制中扮演消息循环的角色,他会不同的从MessageQueue中查看是否有新消息,有旧处理,否则就一直阻塞在那里Looper通过Looper.prepare()方法来初始化Looper,通过Looper.loop()方法开始处理消息在Activity的中因为会默认初始化mainLooper 执行过loop()方法所以可以直接使用Looper Looper提供了quit和quitSafely来退出Looper quit 是直接退出Looper quitSafely 是添加推出的标记,等待消息都处理完成后再安全的推出 4.Handle总结handle的工作主要是消息的发送和处理过程 消息的发送 send方法 post方法 post的一系列方法最终都转化成send的一系列方法来实现的 消息的处理 核心方法dispatchMessage(Message msg) 钩子方法handleMessage(Message msg) 5.主线程的消息循环Android的主线程就是ActivityThread,主线程的入口为main()方法。在main()中系统会通过Looper.perpareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程消息循环 Android中为什么主线程不会因为Looper.loop()的循环而卡死? Looper上的阻塞,前提是没有输入事件,MsgQ为空,Looper空闲状态,线程进入阻塞,释放CPU执行权,等待唤醒。 UI耗时导致卡死,前提是要有输入事件,MsgQ不为空,Looper正常轮询,线程并没有阻塞,但是该事件执行时间过长(5秒?),而且与此期间其他的事件(按键按下,屏幕点击..)都没办法处理(卡死),然后就ANR异常了","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"ThreadLocal","slug":"ThreadLocal","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/ThreadLocal/"},{"name":"Handler","slug":"Handler","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Handler/"}]},{"title":"Java 线程的状态及生命周期","slug":"Java 线程的状态及生命周期","date":"2019-02-10T08:07:36.000Z","updated":"2019-04-17T02:49:33.719Z","comments":true,"path":"2019/02/10/Java 线程的状态及生命周期/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2019/02/10/Java 线程的状态及生命周期/","excerpt":"本文介绍Java的线程状态及他的六种生命周期切换","text":"本文介绍Java的线程状态及他的六种生命周期切换 Java 线程的状态及生命周期一.生命周期流程图根据Java API文档将Java线程运行在JVM中的状态分成六个状态,废话不多说,先上图 image 二.Thread的几个状态说明这些状态的枚举值都定义在java.lang.Thread.State下12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ RUNNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; } 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 阻塞(BLOCKED):表示线程阻塞于锁。 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。 终止(TERMINATED):表示该线程已经执行完毕。三.java之sleep(),join(),yield(),wait()区别 sleep() 使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。 sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。 sleep()方法会使线程进入TIMED_WATING状态 作用:给其它线程执行机会的最佳方式。 join() join()方法使调用该方法的线程在此之前执行完毕,也就是等待该方法的线程执行完毕后再往下继续执行。注意该方法也需要捕捉异常。(相当于调用join()的线程插队到当前正在运行的线程) yield() yield() 方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同,另外yeild()方法会使方法进入RUNABLE状态这点也和sleep()方法不同。 作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。 wait()和notify()、notifyAll() 这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制 wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。 notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。 ==注意 这三个方法都是java.lang.Object的方法。== 四.其他参考Java 线程 - 线程的生命周期 Java线程的6种状态及切换(透彻讲解)","categories":[{"name":"Java","slug":"Java","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Java/"},{"name":"Thread","slug":"Thread","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Thread/"}]},{"title":"几个Exception分析","slug":"java_lang_IllegalStateException","date":"2019-01-17T08:07:36.000Z","updated":"2019-01-24T09:11:04.847Z","comments":true,"path":"2019/01/17/java_lang_IllegalStateException/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2019/01/17/java_lang_IllegalStateException/","excerpt":"本文主要介绍几个Exception的产生原因及解决方案","text":"本文主要介绍几个Exception的产生原因及解决方案 java.lang.RuntimeException今天在bugly意外发现一个非法状态的异常1java.lang.RuntimeException:Unable to destroy activity {com.xxx.xxx/com.xxx.xxx.mvp.orderinfo.ui.OrderInfoActivity}: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState 他的意思是 这个动作不能在onSaveInstanceState之后进行操作定位到代码的具体位置是123456@Overridepublic void hideProgress() { if (progressDialog != null) { progressDialog.dismiss(); }} 是上面代码中的progressDialog.dismiss()方法有问题(这是一个DialogFragment) 也就是系统不允许dialog关闭在onSaveInstanceState之后查了一些资料得到一些信息 java.lang.IllegalStateException异常产生的原因及解决办法错误类型大致为以下几种: java.lang.IllegalStateException:Cannot forward a response that is already committed IllegalStateException:response already commited IllegalStateException:getOutputStream() has already been called for this request IllegalStateException: Can not perform this action after onSaveInstanceState: == 解决办法==:onSaveInstanceState方法是在该Activity即将被销毁前调用,来保存Activity数据的,如果在保存玩状态后 再给它销毁就会出错。解决办法就是把dismiss()方法替换成 dismissAllowingStateLoss()错误原因: 该异常表示,当前对客户端的响应已经结束,不能在响应已经结束(或说消亡)后再向客户端(实际上是缓冲区)输出任何内容。 Object is no longer valid to operate on. Was it deleted by another thread? 该异常表示,realmObject对象在其他线程已被删除,在这个线程中使用的时候抛出的异常。 补充另一种异常情况:这里的异常是:java.lang.IllegalStateExceptionCan’t change tag of fragment d{e183845 #0 d{e183845}}: was d{e183845} now d{e183845 #0 d{e183845}}经查,在显示fragment的代码中使用了:fragment.show(getSupportFragmentManager, fragment.toString());而这里是因为两次toString()结果不同,导致不同的tag指向的是同一个fragment。获取fragment的tag的正确方法应该是使用其提供的fragment.getTag()方法。","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"RuntimeException","slug":"RuntimeException","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/RuntimeException/"}]},{"title":"【推荐】好用强大的Android路由框架--Rudolph","slug":"【推荐】好用强大的Android路由框架--Rudolph","date":"2019-01-04T08:07:36.000Z","updated":"2019-01-24T09:59:15.966Z","comments":true,"path":"2019/01/04/【推荐】好用强大的Android路由框架--Rudolph/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2019/01/04/【推荐】好用强大的Android路由框架--Rudolph/","excerpt":"本文主要说一个好用强大的Android路由框架–Rudolph","text":"本文主要说一个好用强大的Android路由框架–Rudolph 【推荐】好用强大的Android路由框架–Rudolph Rudolph Android Router Framework(鲁道夫安卓路由框架组件)github上查看 目录 1.框架特性 2.依赖方式 3.代码混淆 4.调用方式 5.注解说明 6.组件化 7.常见问题 1.框架特性 支持组件API模块自动生成 自动生成路由Builder类与服务类的接口层; 加载更快,更稳定,无需dex扫描方式加载; 无需指定模块名,接入更简单; 显式跳转与URL路由地址跳转融为一体,更方便快捷; 通过Builder方式传参,无需手动写参数名,从而减少参数传错和修改带来的Bug隐患; 支持所有Intent的参数类型; 支持Activity 、Fragment、Service、Method四种路由类型 支持Instant Run 支持AndroidX 支持Kotlin 2.依赖方式Build.gradle 1234repositories { jcenter() ...} Java: 1234dependencies { implementation 'cn.wzbos.rudolph:rudolph:1.0.1' annotationProcessor 'cn.wzbos.rudolph:rudolph-compiler:1.0.1'} Kotlin: 123456789apply plugin: 'kotlin-android'apply plugin: 'kotlin-kapt'...dependencies { implementation 'cn.wzbos.rudolph:rudolph:1.0.1' kapt 'cn.wzbos.rudolph:rudolph-compiler:1.0.1'} 3.代码混淆如果开启了代码混淆,只需要在混淆配置文件中添加如下配置 12345-keep class * implements cn.wzbos.android.rudolph.IRouteTable{*;}-keep class * implements cn.wzbos.android.rudolph.IRouteBinder{*;}-keepclassmembers class ** { @cn.wzbos.android.rudolph.annotations.Route <methods>;} 4.调用方式 Activity Fragment Service Method Activity定义一个Activity路由,如果不需要用url方式调用可以不写路由地址 123456789101112131415@Route(\"/activity/test\")public class TestActivity extends AppCompatActivity { @Arg(\"userId\") int userId; @Arg(\"userName\") String userName; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Rudolph.bind(this); } } 调用Activity 1UserActivityRouter.builder().userId(11).userName(\"John\").build().start(context); 或者 1Rudolph.builder(\"/user?userId=11&userName=John\").build().open(context); Fragment创建一个Fragment路由 123456789101112131415@Route(\"/fragment/test\")public class TestFragment extends Fragment { @Arg(\"userId\") int userId; @Arg(\"userName\") String userName; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); Rudolph.bind(this); }} 调用Fragment 1Fragment fragment = TestFragmentRouter.builder().userId(11).userName(\"John\").build().open(); 或者 1Rudolph.builder(\"/fragment/user?userId=11&userName=John\").build().open(); Service创建一个服务(适用于跨module调用) 1234567891011121314151617181920@Route(vaule="/service/test",export = true)public class TestService implements IRouteService{ @Arg int userId; @Arg String userName; @Override public void init(Bundle bundle) { Log.d("TestService", "afterInject"); rudolph.bind(TestService.this, bundle); } @Export public void showMessage(Context context, String msg) { Toast.makeText(context, msg + "\\nuserId:" + userId + ",userName:" + userName, Toast.LENGTH_SHORT).show(); }} 注意:服务类必须实现IRouteService接口 调用服务 12ITestService service = TestServiceRouter.builder().userId(1).userName(\"Tom\").build().open();service.showMessage(MainActivity.this, \"Hello Provider!\"); 或者 12ITestService service = (ITestService)Rudolph.builder(\"/service/test?userId=11&userName=John\").build().open();service.showMessage(MainActivity.this, \"Hello Provider!\"); Method123456public class TestMethod { @Route("/method/test") public static void Test(@Arg Context context, @Arg int userId, @Arg String userName) { Toast.makeText(context, "Hello Method!\\nuserId:" + userId + ",userName:" + userName, Toast.LENGTH_SHORT).show(); }} 调用方式: 1Rudolph.builder(\"/method/test?userId=11&userName=John\").build().open(context); 注意:1.方法必须为静态方法2.context是获取open(context)传的上下文,如果调用的时候没context值则接收的值为ApplicationContext 5.注解说明 @Route @Arg @Component @Export @Route此注解为标识一个路由; 参数: value:路由地址,可为空,例如@Route(“/room”) export:是否导出API,一般组件化刚才才会用 123@Route(value = \"/user\",export = true)public class UserActivity extends AppCompatActivity {} @Arg此注解为标识路由的参数(注意如果注解到字段上,此字段不能为private)。 参数: value:路由地址,可为空(默认取字段名),例如@Arg(“userId”),@Arg(RAW_URI) base64:标识此参数是否为base64方式编码 json:标识此参数是否为json格式 123456789@Route(value = \"/user\",export = true)public class UserActivity extends AppCompatActivity { @Arg(\"userId\") int userId; @Arg String userName; @Arg(value=\"userInfo\",base64=true, json=true) String userInfo;} @Component此注解为组件化所需要的注解,主要为提供组件初始化操作; 参数:无 12345678@Componentpublic class TestComponent implements IRouteTable { @Override public void init(Application application) { Toast.makeText(application.getApplicationContext(), \"组件xxx初始化啦!\", Toast.LENGTH_SHORT).show(); }} @Export导出注解的方法,此注解只能用在Method上,且此方法必须为非静态(static)的Public方法; 参数:无 12345678@Routepublic class TestService implements IRouteService{ @Export public void showMessage(String message) { }} 6.组件化场景:A模块需要调用B模块 实现方式:需要导出B模块的API(当然如果想用纯URL的方式调用可以不导出),然后A、B 模块都依赖B模块的API 操作步骤:第一步:在B模块的的build.gradle中增加如下配置,其中export_api_name为导出的API模块名,export_api_package为导出的API包名 123456789101112defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [ export_api_name : project.getName() + "_api", export_api_package: "com.xxxx.module_b_api" ] includeCompileClasspath = true } }} 第二步:点击重新编译,然后就能看到生成的API工程,看到API工程后再到settings.gradle中增加以下依赖 1include ':module_b_api' 第三步:需要在A模块和B模块中增加依赖 123dependencies { implementation project(':module_b_api')} 初始化组件通过以下代码可以初始化每个组件 123456789@Componentpublic class TestComponent implements IRouteTable { @Override public void init(Application application) { Toast.makeText(application.getApplicationContext(), \"组件xxx初始化啦!\", Toast.LENGTH_SHORT).show(); }} (注意:@Component注解的类,每个module中只能存在一个) 7.常见问题 路由参数支持哪些数据类型? 如何初始化组件? 路由参数支持的数据类型123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596@Route(value = \"/activity/test\",export = true)public class TestActivity extends AppCompatActivity { @Arg(RAW_URI) String routeUri; @Arg(\"stringArg\") String stringArg; @Arg(value = \"string64\", base64 = true) String string64Arg; @Arg(\"stringArray\") String[] stringArrayArg; @Arg(\"boolArg\") boolean boolArg; @Arg(\"booleanArray\") boolean[] booleanArrayArg; @Arg(\"byteArg\") byte byteArg; @Arg(\"byteArray\") byte[] byteArrayArg; @Arg(\"shortArg\") short shortArg; @Arg(\"shortArray\") short[] shortArrayArg; @Arg(\"intArg\") int intArg; @Arg(\"intArrayArg\") int[] intArrayArg; @Arg(\"longArg\") long longArg; @Arg(\"longArray\") long[] longArrayArg; @Arg(\"charArg\") char charArg; @Arg(\"charArray\") char[] charArrayArg; @Arg(\"floatArg\") float floatArg; @Arg(\"floatArray\") float[] floatArrayArg; @Arg(\"doubleArg\") double doubleArg; @Arg(\"doubleArray\") double[] doubleArrayArg; @Arg(\"characterArg\") Character characterArg; //ArrayList @Arg ArrayList<String> stringArrayListArg; @Arg ArrayList<Integer> integerArrayListArg; @Arg(value = \"charSequenceArrayList\") ArrayList<CharSequence> charSequenceArrayListArg; @Arg(value = \"parcelableArrayList\") ArrayList<Broker> parcelableArrayListArg; @Arg(value = \"serialized\") Broker serializedParam; //json @Arg(value = \"json\", json = true) User<Broker> jsonParam; //encode:json->base64,decode:base64->json @Arg(value = \"base64json\", json = true, base64 = true) User<Broker> base64jsonParam; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Rudolph.bind(this); }} github上查看","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"路由","slug":"路由","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/路由/"}]},{"title":"6条给React开发者的专业建议","slug":"6条给React开发者的专业建议","date":"2018-09-07T08:07:36.000Z","updated":"2019-03-13T09:02:22.951Z","comments":true,"path":"2018/09/07/6条给React开发者的专业建议/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2018/09/07/6条给React开发者的专业建议/","excerpt":"本文主要说6条给React开发者的专业建议,让你写出更好的React代码","text":"本文主要说6条给React开发者的专业建议,让你写出更好的React代码 1. 使用方法组件在你不需要使用组建的内部状态和生命周期方法你可以使用123function welcome(props){ return <h1>Hello,{props.name}</h1>;} 来代替12345class Welcome extends Component{ render(){ return <h1>Hello,{this.props.name}</h1> }} 使用这种方法的好处: 更少的代码, 更容易理解, 不用处理状态机, 测试简单, 不需要绑定this, 更容易提取较小的组件 2.尽可能的让你的组件小巧使用这种方法的好处: 便于阅读 便于测试 方便维护 方便复用例如:下面我们把个人中心资料模块进行细分 个人用户信息部分被拆分123456789101112class Comment extends Compont{ render() <div className="Comment"> <UserInfo user={this.props.user}/> <div className='Comment-text'> {this.props.text} </div> <div className="comment-text"> {formatDate(this.props.date)} </div> </div>} 在用户信息部分中,头像部分也被拆分成更小的组件12345678910function UserInfo(props){ renturn ( <div className="UserInfo"> <Avatar user={props.name}/> <div classNmae='UserInfo-name'> {props.user.name} </div> </div> );} 个人头像部分12345678function Avatar(props){ return( <img className='Avatar' src={props.user.avatarUrl} alt={props.user.name} </img> );} 3.了解并知道怎样处理’this’1. 在render中绑定这种方式虽然简单明了但是有性能问题,因为点击后会调用一个新的函数,每次这个组件都要重新渲染1234567891011121314class HelloWorld extend component{ contructor(props){ super(props); this.state={message:'Hi'}; } logMessage(){ console.log(this.state.message); } render(){ return { <input type='button' value='Log' onClick={this.logMessage.bind(this)}/> } }} 2. 在render中使用箭头函数 这种方式和方法1同样会有相同的性能问题1234567891011121314class HelloWorld extend component{ contructor(props){ super(props); this.state={message:'Hi'}; } logMessage(){ console.log(this.state.message); } render(){ return { <input type='button' value='Log' onClick={()=>this.logMessage}/> } }} 3. 在构造方法中绑定 这种方式需要记得使用super(props) 123456789101112131415class HelloWorld extend component{ contructor(props){ super(props); this.state={message:'Hi'}; this.logMessage= this.logMessage.bind(this) } logMessage(){ console.log(this.state.message); } render(){ return { <input type='button' value='Log' onClick={this.logMessage}/> } }} 4. 在属性中使用箭头函数有方法1和2的性能问题 12345678910111213class HelloWorld extend component{ this.state={message:'Hi'}; logMessage=()=>{ console.log(this.state.message); } render(){ return { <input type='button' value='Log' onClick={this.logMessage}/> } }} 4.在更新状态时使用方法而不是对象user:123this.setState((prevState.props)=>{ renturn {correctData:!prevState.correctData}}); not1this.setState({correctData:!this.state.correctData}); 5.开发lib时使用’prop-types’1234567891011import PropTypes from 'pros-types';class Welcome extends Component{ render(){ return <h1>Hello,{this.props.name}</h1> }}Welcome.propTypes={ name:ProsTypes.string.isRequired} 6.使用必要的React 开发工具来帮助我们","categories":[{"name":"React","slug":"React","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/React/"}],"tags":[{"name":"React","slug":"React","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/React/"}]},{"title":"APP线上bug处理","slug":"APP 线上bug处理","date":"2018-09-06T08:07:36.000Z","updated":"2019-01-24T11:07:40.173Z","comments":true,"path":"2018/09/06/APP 线上bug处理/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2018/09/06/APP 线上bug处理/","excerpt":"本文主要说APP在线上bug的处理","text":"本文主要说APP在线上bug的处理 修复的bug1. #157 java.lang.NullPointerExceptionandroid.os.Parcel.readException(Parcel.java:1690) -问题分析:这是一个系统内部的空指针,报错是因为MediaScannerConnection.scanFile方法(刷新文件目录的图片显示到相册);原因是传入的数组中包含一个空路径(删除文件夹导致路径不存在),只用在坚果PRO和坚果PRO2会出错;- 问题处理删除空路径 2. #141 java.lang.RuntimeExceptioncom.mgzf.widget.mgbottomwheel.MGBottomWheel$a.a(MGBottomWheel.java:72) -问题分析:这个问题实质上是一个空指针异常,报错的是一个build对象为空导致取字段时空指针异常;字段为空的原因是在Activity的configChanges发生变化时会导致界面重绘,而build字段丢失并没有进行保存 -问题处理:-方案一: 在这个MGBottomWheel(Fragment)中增加数据保存(onSaveInstanceState)和恢复的方法(onViewStateRestored),处理configChanges发生变化产生的问题(我采用的此方法) -方案二: 参考dialog的处理方案直接关闭这个弹框;然后在Activity中自行处理是否显示 3.#138 java.lang.RuntimeExceptioncom.mogoroom.broker.user.view.BusinessAreaActivity.a(BusinessAreaActivity.java:157) - 问题分析:这个实际上是一个空指针异常,报错的字段原因是一个配置文件的对象为空,导致取值时报错。经查该对象为网络请求后获取服务器返回对象时才会初始化,问题极大可能出在网络较差时没有能够成功获取后端数据- 处理方案:无论如何都初始化数据并对对象的字段设置默认值4.#166 java.lang.IllegalStateExceptioncom.mogoroom.broker.room.poster.view.BrokerPosterActivity$a.a(BrokerPosterActivity.java:444) -问题分析:这个是一个Activity被销毁后仍然被调用的问题,具体原因是BrokerPosterActivity页面关闭没有销毁网络请求的disposable 导致回调成功后更新UI导致出错;- 问题处理关闭页面时关闭disposable","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Bug","slug":"Bug","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Bug/"}]},{"title":"CoordinatorLayout使用解析","slug":"CoordinatorLayout使用解析","date":"2018-09-03T08:07:36.000Z","updated":"2019-01-24T10:08:24.360Z","comments":true,"path":"2018/09/03/CoordinatorLayout使用解析/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2018/09/03/CoordinatorLayout使用解析/","excerpt":"本文主要说CoordinatorLayout使用","text":"本文主要说CoordinatorLayout使用 CoordinatorLayout使用解析一. CoordinatorLayout介绍1. CoordinatorLayout是一个“加强版”FrameLayout,它主要有两个用途: 用作应用的顶层布局管理器,也就是作为用户界面中所有UI控件的容器 用作相互之间距有特定交互行为的UI控件的容器通过为CoordinatorLayout的子View指定Behavior,就可以实现它们之间的交互行为。Behavior可以用来实现一系列的交互行为和布局变化,比如说侧滑菜单、可滑动删除的UI元素,以及跟随着其他UI控件移动的按钮等。2.文字不够形象, 直接来欣赏一下facebook的效果 image 3. CoordinatorLayout的使用使用CoordinatorLayout需要在Gradle加入Support Design Library:1compile 'com.android.support:appcompat-v7:26.1.0' 二.AppBarLayout, CollapsingToolbarLayout的使用1.AppBarLayout,CollapsingToolbarLayout是为了配合CoordinatorLayout使用而简单实现相互关系的控件2.AppBarLayout 官方文档1). AppBarLayout 介绍:1234- 实现了material designs 滑动手势- AppBarLayout的子View应该通过{setScrollFlags(int)}或者相关的布局xml属性{app:layout_scrollFlags}提供他们想要的滚动行为。- 最好作为CoordinatorLayou的直接子View使用- 要拥有一个可滚动的兄弟View并且通过为可滚动的兄弟View设置ScrollingViewBehavior实例来实现绑定。 2).AppBarLayout滑动区域 查看当设计滚动行为时,App bar包含构成滚动结构的四个主要区域(称为块): Status bar Tool bar Tab bar/search bar Flexible space: 用来容纳图像或者扩展app bar的期望宽高比3) 滑动类型AppBarLayout里面的View都是通过设置app:layout_scrollFlags属性控制滑动,Google提供了5种滑动表现类型,分别是1234567- scroll:表示向下滚动的时候,设置了这个属性的View会被滚出屏幕范围,直到消失- enterAlways:表示向上滚动的时候,设置了这个属性的View会随着滚动手势逐渐出现,直到恢复原来设置的位置(需搭配scroll使用)- enterAlwaysCollapsed:是enterAlways的附加选项,一般跟enterAlways一起使用,它是指,View在往下“出现”的时候,首先是enterAlways效果,当View的高度达到最小高度时,View就暂时不去往下滚动,直到ScrollView滑动到顶部不再滑动时,View再继续往下滑动,直到滑到View的顶部结束。- exitUntilCollapsed:值设为exitUntilCollapsed的View,当这个View要往上逐渐“消逝”时,会一直往上滑动,直到剩下的的高度达到它的最小高度后,再响应ScrollView的内部滑动事件(需搭配scroll使用)- snap 表示在滑动过程中如果停止滑动,则头部会就近折叠(要么恢复原状,要么折叠成一个Toolbar或者不显示)(需搭配scroll使用) 4)重要的监听方法addOnOffsetChangedListener()123456appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { // TODO }}); 3.CollapsingToolbarLayout 官方文档1.)CollapsingToolbarLayout 介绍:CollapsingToolbarLayout是实现一个可以折叠的Toolbar,作为AppbarLayout的直接子View使用,CollapsingToolbarLayout控件提供了一下功能:123456789- 折叠标题:当标题栏较大的时候,在布局完全显示的情况下可以显示标题栏,但是标题栏折叠、变小或者布局滚动出屏幕的时候,可以通过setTitle(CharSequence)设置标题显示,通过设置collapsedTextAppearance和expandedTextAppearance属性可以调整标题栏的外观。- 内容遮罩:通过 setContentScrim(Drawable)更改当滚动到临界点的时候显示或者隐藏- 状态栏遮罩:可以通过setStatusBarScrim(Drawable)设置遮罩在滚动到临界点之后是显示还是隐藏,必须要在5.0以后设置了fitSystemWindows属性才可以使用- 视差滚动子视图:子视图可以在这个视差范围内滚动,See COLLAPSE_MODE_PARALLAX and setParallaxMultiplier(float).- 寄托子视图:子视图可以选择在全局范围内被固定,当实现一个折叠的时候,允许Toolbar被固定在合适的位置,详细见COLLAPSE_MODE_PIN 2) 折叠塌陷的属性collapseModeapp:layout_collapseMode=”pin”属性:这个属性是设置折叠的模式,Android提供有两个值,分别是:123- pin:设置这个值,当CollapsingToolbarLayout完全折叠之后,View还会显示在屏幕上- parallax:设置这个值,在内容滚动时,CollapsingToolbarLayout中的View(比如我们这里的ImageView)也会同时滚动,实现视差滚动效果,通常和layout_collapseParallaxMultiplier(设置视差因子)搭配使用。 app:layout_collapseParallaxMultiplier=”0.7”属性:设置视差滚动因子,值得范围是0~1. 视差滚动因子数值越大,视觉差越大 123- 如果这里的值为0,则在头部折叠的过程中,ImageView的顶部在慢慢隐藏,底部不动- 如果这里的值为1,ImageView的顶部不懂,底部慢慢隐藏,- 如果这里的取值为0~1之间,则在折叠的过程中,ImageView的顶部和底部都会隐藏,但是头部和底部隐藏的快慢是不一样的,具体速度和视觉乘数有关 简单来说 0~1代表上下方向折叠的快慢 0上部折叠速度快 1下部折叠速度快 三.Behavior1.Behavior介绍1.作用于CoordinatorLayout的子View的交互行为插件。一个Behavior 实现了用户的一个或者多个交互行为,它们可能包括拖拽、滑动、快滑或者其他一些手势。 2. Behavior 是一个顶层抽象类,其他的一些具体行为的Behavior 都是继承自这个类。123456789public static abstract class Behavior<V extends View> { public Behavior() { } public Behavior(Context context, AttributeSet attrs) { } //省略了若干方法} 其中有一个泛型,它的作用是指定要使用这个Behavior的View的类型,可以是Button、TextView等等。 3.自定义Behavior可以选择重写以下的几个重要方法:- - **layoutDependsOn()**:确定使用Behavior的View要依赖的View的类型 - **onDependentViewChanged()**:当被依赖的View状态改变时调用 - **onDependentViewRemoved()**:当被依赖的View移除时调用 - **onStartNestedScroll()**:嵌套滑动开始(ACTION_DOWN),确定Behavior是否要监听此次事件 - **onNestedScrollAccepted()** : onStartNestedScroll返回true才会触发这个方法,接受滚动处理后回调,可以在这个方法里做一些准备工作,如一些状态的重置等。 - **onStopNestedScroll()**:嵌套滑动结束(ACTION_UP或ACTION_CANCEL) - **onNestedPreScroll()**:嵌套滑动进行中,要监听的子 View将要滑动,滑动事件即将被消费(但最终被谁消费,可以通过代码控制) - **onNestedScroll()**:嵌套滑动进行中,要监听的子 View的滑动事件已经被消费 - **onNestedFling()**:要监听的子 View在快速滑动中 - **onNestedPreFling()**:要监听的子View即将快速滑动 - **onLayoutChild** 确定使用Behavior的View位置 2.绑定Behavior的三种方式Behavior无法独立完成工作,必须与实际调用的CoordinatorLayout子视图相绑定。具体有三种方式:通过代码绑定、在XML中绑定或者通过注释实现自动绑定。 1. 通过代码绑定Behavior如果将Behavior当作绑定到CoordinatorLayout中每个视图的附加数据,那么发现Behavior实际上是存储在各个视图的LayoutParams中也就不足为奇了(之前有关于布局的博文)。也是因此,Behavior需要绑定到CoordinatorLayout的直接子项中,因为只有那些子项会包含LayoutParams的特定Behavior子类 123TitleBehavior titleBehavior = new TitleBehavior();CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) yourView.getLayoutParams();params.setBehavior(titleBehavior); 2. app:layout_behavior布局属性在布局中设置,值为自定义 Behavior类的名字字符串(包含路径) 有两种写法,包含包名的全路径和以”.”开头的省略项目包名的路径:123app:layout_behavior="@string/appbar_scrolling_view_behavior"app:layout_behavior="com.yasin.coordinatorlayoutdemo.TitleBehavior"app:layout_behavior=".TitleBehavior" 3. @CoordinatorLayout.DefaultBehavior类注解在需要使用 Behavior的控件源码定义中添加该注解,然后通过反射机制获取。系统的 AppBarLayout、 FloatingActionButton都采用了这种方式,所以无需在布局中重复设置。[email protected](TitleBehavior.class)public class TitleLayout extends FrameLayout {} 3.Behavior实现View间交互的原理behavior像是view的一个属性,其实它是view的LayoutParam的一个属性,就像宽高一样。当然不是任何一个view的LayoutParam都有这个属性的,只有LayoutParam为android.support.design.widget.CoordinatorLayout.LayoutParams才有这个属性. 所以,就是只有CoordinatorLayout的子view的LayoutParam可以设置behavior。 我们可以在CoordinatorLayout.LayoutParams中找到Behavior属性.12345678910111213public static class LayoutParams extends ViewGroup.MarginLayoutParams { Behavior mBehavior; boolean mBehaviorResolved = false; ... final Rect mLastChildRect = new Rect(); } ``` ##### 1.Behavior初始化过程 1. xml方式:app:layout_behavior=”com.yasin.coordinatorlayoutdemo.TitleBehavior” infate的时候,会根据xml去构造LayoutParams,所以我们可以在CoordinatorLayout.LayoutParams看到behavior的初始化过程 LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout_LayoutParams); ... mBehaviorResolved = a.hasValue( R.styleable.CoordinatorLayout_LayoutParams_layout_behavior); if (mBehaviorResolved) { mBehavior = parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_LayoutParams_layout_behavior)); } a.recycle(); }1232.注解方式:@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)在CoordinatorLayout的onMeasure的时候会调用prepareChildren,进而调用getResolvedLayoutParams,在getResolvedLayoutParams里会把注解里的默认Behavior赋值给mBehavior,主要代码如下: LayoutParams getResolvedLayoutParams(View child) { final LayoutParams result = (LayoutParams) child.getLayoutParams(); //如果xml内写了behavior,此时result.mBehaviorResolved就为true,不会进去 if (!result.mBehaviorResolved) { Class<?> childClass = child.getClass(); DefaultBehavior defaultBehavior = null; while (childClass != null && (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) { childClass = childClass.getSuperclass(); } if (defaultBehavior != null) { try { result.setBehavior(defaultBehavior.value().newInstance()); } catch (Exception e) { Log.e(TAG, “Default behavior class “ + defaultBehavior.value().getName() + “ could not be instantiated. Did you forget a default constructor?”, e); } } result.mBehaviorResolved = true; } return result; }12345##### 2.Behavior是如何发挥作用的measure和layout是Android绘制视图的关键组件,因此Behavior只有在onMeasureChild()和onLayoutChild()回调前拦截父视图的measure和layout,才能达到预计的效果。我们再来看看onMeasure的代码,和Behavior相关的主要看prepareChildren和ensurePreDrawListener两个方法 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { prepareChildren(); ensurePreDrawListener(); 。。。1**先看 preareChildren()** private void prepareChildren() { //清空mDependencySortedChildren mDependencySortedChildren.clear(); for (int i = 0, count = getChildCount(); i < count; i++) { final View child = getChildAt(i); final LayoutParams lp = getResolvedLayoutParams(child); lp.findAnchorView(this, child); //加入child mDependencySortedChildren.add(child); } // Finally add the sorted graph list to our list mDependencySortedChildren.addAll(mChildDag.getSortedList()); // 我们还需要反转结果,因为我们希望列表的开头包含没有依赖项的视图,然后在此之后依赖视图。 Collections.reverse(mDependencySortedChildren); } 12345prepareChildren内主要是搞出来一个mDependencySortedChildren,根据依赖关系对child进行排序。先把mDependencySortedChildren clear,然后遍历子view,全部加入到mDependencySortedChildren内,最后对mDependencySortedChildren进行排序排序这块没看懂.看了翻译和别的文档大概意思是:被依赖的view放前面,依赖的view放后面. 比如我们fab依赖于snackbar,那么snackbar必然放在fab的前边。这么排序有什么用?其实是提高一点效率,后文会说的。**次看ensurePreDrawListener()** void ensurePreDrawListener() { //判断是否存在依赖关系 boolean hasDependencies = false; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (hasDependencies(child)) { hasDependencies = true; break; } } if (hasDependencies != mNeedsPreDrawListener) { if (hasDependencies) { //加入PreDrawListener addPreDrawListener(); } else { removePreDrawListener(); } } } 1234在prepareChildren确定mDependencySortedChildren之后,会执行ensurePreDrawListener,在这里写判断下CoordinatorLayout的子view是否存在依赖关系,如果存在的话就hasDependencies为true,后边会加入OnPreDrawListener,也就是监听依赖View的布局变化就是在重绘之前,会调用OnPreDrawListener的onPreDraw方法。再在onPreDraw里面调用了onChildViewsChanged。**再看onChildViewsChanged()** final void onChildViewsChanged(@DispatchChangeEvent final int type) { …….. final int childCount = mDependencySortedChildren.size(); for (int i = 0; i < childCount; i++) { …. // Update any behavior-dependent views for the change for (int j = i + 1; j < childCount; j++) { final View checkChild = mDependencySortedChildren.get(j); final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams(); final Behavior b = checkLp.getBehavior(); if (b != null && b.layoutDependsOn(this, checkChild, child)) { if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) { // If this is from a pre-draw and we have already been changed // from a nested scroll, skip the dispatch and reset the flag checkLp.resetChangedAfterNestedScroll(); continue; } final boolean handled; switch (type) { case EVENT_VIEW_REMOVED: // EVENT_VIEW_REMOVED means that we need to dispatch // onDependentViewRemoved() instead b.onDependentViewRemoved(this, checkChild, child); handled = true; break; default: // Otherwise we dispatch onDependentViewChanged() handled = b.onDependentViewChanged(this, checkChild, child); break; } } ....... } ` 显然从这里就开始触发View的交互了 onDependentViewRemoved()和onDependentViewChanged()方法 四.自定义Behavior通常自定义Behavior分为两种情况: 1.通过监听一个View的状态,如位置、大小的变化,来改变其他View的行为,这种只需要重写2个方法就可以了,分别是layoutDependsOn 和onDependentViewChanged, layoutDependsOn方法判断是指定依赖的View时,返回true,然后在onDependentViewChanged 里,被依赖的View做需要的行为动作。 2.是重写onStartNestedScroll、onNestedPreScroll、onNestedScroll等一系列方法.可以实现比较复杂的方法 相关链接 Android 详细分析AppBarLayout的五种ScrollFlags链接","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"CoordinatorLayout","slug":"CoordinatorLayout","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/CoordinatorLayout/"}]},{"title":"React Native 入门","slug":" React Native 入门","date":"2018-08-10T08:07:36.000Z","updated":"2019-01-24T11:06:24.532Z","comments":true,"path":"2018/08/10/ React Native 入门/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2018/08/10/ React Native 入门/","excerpt":"本文主要说React Native 入门的一些东西,帮助新手快速入门","text":"本文主要说React Native 入门的一些东西,帮助新手快速入门 React Native 入门一. 背景为什么需要React-Native?在React-Native出现之前移动端主流的开发模式是原生开发和Hybrid开发(H5混合原生开发),Hybrid app相较于native app的优势是开发成本低开发速度快(H5页面开发跨平台,无需重新写web、android、ios代码),尽管native app在开发上需要更多时间,但却带来了更好的用户体验(页面渲染、手势操作的流畅性),也正是基于这两点Facebook在2015年推出了React-Native React-Native的开发既保留了React的开发效率又拥有媲美原生的用户体验,其运行原理并非使用webview所以不属于Hybrid开发,想了解的可以查看React Native运行原理解析这篇文章。React-Native提出的理念是‘learn once,write every where’,之所以不是‘learn once, run every where’,是因为不同平台的用户体验有所不同,因此要运行全平台仍需要一些额外的适配 二. 搭建环境更多更详细搭建流程 https://reactnative.cn/docs/getting-started.html 安装依赖必须安装的依赖有:Node、Watchman 和 React Native 命令行工具以及 JDK 和 Android Studio。 1.Node, Watchman我们推荐使用Homebrew来安装 Node 和 Watchman。在命令行中执行下列命令安装: 12brew install nodebrew install watchman Node,需要在 v8.3 以上 Watchman则是由 Facebook 提供的监视文件系统变更的工具。安装此工具可以提高开发时的性能(packager 可以快速捕捉文件的变化从而实现实时刷新)。 2.Yarn、React Native 的命令行工具(react-native-cli)Yarn是 Facebook 提供的替代 npm 的工具,可以加速 node 模块的下载。React Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务1npm install -g yarn react-native-cli 3.JDK、Android Studio的安装略 4.创建新项目使用 React Native 命令行工具来创建一个名为”AwesomeProject”的新项目: 1react-native init AwesomeProject 提示:你可以使用–version参数(注意是两个杠)创建指定版本的项目。例如react-native init MyApp –version 0.44.3。注意版本号必须精确到两个小数点。 三.编译并运行 React Native 应用1.启动项目确保你先运行了模拟器或者连接了真机,然后在你的项目目录中运行react-native run-android: 123cd AwesomeProjectreact-native run-android 如果配置没有问题,你应该可以看到应用自动安装到设备上并开始运行。注意第一次运行时需要下载大量编译依赖,耗时可能数十分钟。此过程严重依赖稳定的翻墙工具,否则将频繁遭遇链接超时和断开,导致无法运行。 2.修改项目现在你已经成功运行了项目,我们可以开始尝试动手改一改了: 使用你喜欢的文本编辑器打开App.js并随便改上几行按两下 R 键,或是用 Menu 键(通常是 F2,在 Genymotion 模拟器中是⌘+M)打开开发者菜单,然后选择 Reload JS 就可以看到你的最新修改。 四.集成RN到现有的应用集成方式 https://reactnative.cn/docs/integration-with-existing-apps/ 五.基本的JSX和ES6语法先看一下运行成功后的界面代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061/** * Sample React Native App * https://github.com/facebook/react-native * @flow */import React, {Component} from 'react';import { Platform, StyleSheet, Text, View} from 'react-native';const instructions = Platform.select({ ios: 'Press Cmd+R to reload,\\n' + 'Cmd+D or shake for dev menu', android: 'Double tap R on your keyboard to reload,\\n' + 'Shake or press menu button for dev menu',});//noinspection BadExpressionStatementJStypeProps = {};//noinspection JSAnnotatorexport default class App extends Component<Props> { render() { return ( <View style={styles.container}> <Text style={styles.welcome}> Welcome to React Native! </Text> <Text style={styles.instructions}> To get started, edit App.js </Text> <Text style={styles.instructions}> {instructions} </Text> </View> ); }}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, },}); 代码中出现的123````<Text style={styles.welcome}>Welcome to React Native!</Text>```这段代码是JSX语法使用方式,和html标记语言一样,只不过这里引用的是React-Native的组件,Text是一个显示文本的组件,可以看到```style={styles.welcome}```这是JSX的另一个语法可以将有效的js表示式放入大括号内,Welcome to React Native!为其内容文本.## 六.组件的属性和状态在了解了一些基本的JSX和ES6语法后,我们还需要了解两个比较重要的概念即```props```和```state 1234## 七.组件生命周期![image](https://upload-images.jianshu.io/upload_images/11716283-7bb3a070d2406452.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/740)组件的生命周期会经历三个阶段 Mounting:挂载Updating:更新Unmounting:移除1对应的生命周期回调方法为 componentWillMount()//组件将要挂载时调用render()//组件渲染时调用componentDidMount()//组件挂载完成时调用componentWillReceiveProps(object nextProps)//组件props和state改变时调用shouldComponentUpdate(object nextProps,object nextState)//返回false不更新组件,一下两个方法不执行componentWillUpdate(object nextProps,object nextState)//组件将要更新时调用componentDidUpdate(object nextProps,object nextState)//组件完成更新时调用componentWillUnmount()//组件销毁时调用12345这里我们需要重点关注的地方在于组件运行的阶段,组件每一次状态收到更新都会调用 ```render()```方法,除非```shouldComponentUpdate```方法返回```false```,可以通过此方法对组件做一些优化避免重复渲染带来的性能消耗。## 八.样式React-Native样式实现了CSS的一个子集,样式的属性与CSS稍有不同,其命名采用驼峰命名,对前端开发者来说基本没差。使用方式也很简单,首先使用StyleSheet创建一个styles const styles = StyleSheet.create({ container:{ flex:1 }})12然后将对应的style传给组件的style属性,例如```<View style={styles.container}/> 九.常用组件在日常开发中最常使用的组件莫过于12345- ```View```基本上作为容器布局,在里面可以放置各种各样的控件,一般只需要为其设置一个style属性即可,常用的样式属性有flex,width,height,backgroundColor,flexDirector,margin,padding更多可以查看[Layout Props](https://facebook.github.io/react-native/docs/layout-props.html#docsNav)。- ```Text```是一个显示文本的控件,只需要在组件的内容区填写文字内容即可,例如```<Text>Hello world</Text>```,可以为设置字体大小和颜色```<Text style={fontSize:14,color:'red'}>Hello world</Text>```,同时也支持嵌套Text,例如 <Text style={fontWeight: ‘bold’}> I am bold <Text style={color: ‘red’}> and red 1- ```TextInput```是文本输入框控件,其使用方式也很简单 console.log(text)}/>12```style```设置了他的样式,```onChangeText```传入一个方法,该方法会在输入框文字发生变化时调用,这里我们使用```console.log(text)```打印输入框的文字。- ```Image```是一个图片控件,几乎所有的app都会使用图片作为他们的个性化展示,Image可以加载本地和网络上的图片,当加载网络图片时必须设定控件的大小,否则图片将无法展示 //加载本地图片,图片地址为相对地址 <Image style={width:100,height:100} source={require(‘../images/img001.png’)}/>//加载网络图片 <Image style={width:100,height:100}source={uri:’https://facebook.github.io/react-native/docs/assets/favicon.png'}/> `","categories":[{"name":"React","slug":"React","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/React/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"React Native","slug":"React-Native","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/React-Native/"}]},{"title":"Android Pie(9.0)适配","slug":"Android Pie(9.0)适配","date":"2018-07-10T08:07:36.000Z","updated":"2019-01-24T10:04:44.842Z","comments":true,"path":"2018/07/10/Android Pie(9.0)适配/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2018/07/10/Android Pie(9.0)适配/","excerpt":"本文主要说Android Pie(9.0)的适配","text":"本文主要说Android Pie(9.0)的适配 Android Pie(9.0)适配查看新功能###显示屏缺口支持 使用getDisplayCutout()函数获取缺口屏(刘海屏)的参数 可以通过谷歌提供的适配方案,使用挖孔区全屏显示解决: 123WindowManager.LayoutParams lp = getWindow().getAttributes();lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;getWindow().setAttributes(lp); ###多个通知增强功能 支持图像:现在,Android 9 可在手机的“短信通知”中显示图像。 您可以使用对短信使用 setData()来显示图像。 以下代码段演示了如何创建 Person 和包含图像的短信。 123456789101112// Create new Person.Person sender = new Person() .setName(name) .setUri(uri) .setIcon(null) .build();// Create image message.Message message = new Message("Picture", time, sender) .setData("image/", imageUri);Notification.MessagingStyle style = new Notification.MessagingStyle(getUser()) .addMessage("Check this out!", 0, sender) .addMessage(message); [图片上传失败…(image-b7321d-1544772143959)] ###适用于可绘制对象和位图的 ImageDecoderAndroid 9 引入了 ImageDecoder 类,可提供现代化的图像解码方法。 使用该类取代 BitmapFactory 和 BitmapFactory.Options API。 ###动画Android 9 引入了 AnimatedImageDrawable 类,用于绘制和显示 GIF 和 WebP 动画图像。 ###自动填充框架Android 9 引入了多项改进,自动填充服务可以利用这些改进进一步增强用户填写表单时的体验。 如需详细了解如何在您的应用中使用自动填充功能,请参阅自动填充框架指南。 更多新功能 ##行为变更比较多,建议直接看文档 所有应用 以 API 级别 28+ 为目标的应用 我们公司的app需要注意的几个点,可能对我们有影响 限制访问电话号码 使用非 SDK 接口的限制 传输层安全协议 (TLS) 实现变更 Android 9 完全取消了对 Android 安全加密文件 (ASEC) 的支持。 现在强制执行 FLAG_ACTIVITY_NEW_TASK 要求 屏幕旋转变更 构建序列号弃用(在 Android 9 中,Build.SERIAL 始终设置为 “UNKNOWN” 以保护用户的隐私。) 无法再让多个进程共用同一 WebView 数据目录 在 Android 9 之前,暂停的应用发出的通知会被取消。 从 Android 9 开始,暂停的应用发出的通知将被隐藏,直至应用继续运行。 ###对于非 SDK 接口的限制无论是直接使用还是通过反射或 JNI 间接使用。 无论应用是引用非 SDK 接口还是尝试使用反射或 JNI 获取其句柄,均适用这些限制。 下面是名单类型: 白名单:SDK 浅灰名单:仍可以访问的非 SDK 函数/字段。 深灰名单: 对于目标 SDK 低于 API 级别 28 的应用,允许使用深灰名单接口。 对于目标 SDK 为 API 28 或更高级别的应用:行为与黑名单相同 黑名单:受限,无论目标 SDK 如何。 平台将表现为似乎接口并不存在。 例如,无论应用何时尝试使用接口,平台都会引发 NoSuchMethodError/NoSuchFieldException,即使应用想要了解某个特殊类别的字段/函数名单,平台也不会包含接口。 名单类型 下表详细说明了各种访问方式及其相应的结果。 名单类型 讲解视频 veridex检测,需要翻墙 ###扩展阅读AndroidX了解一下 Android Jetpack","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Android 9.0","slug":"Android-9-0","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android-9-0/"}]},{"title":"Android 组件化规范","slug":"Android 组件化规范","date":"2018-03-19T08:07:36.000Z","updated":"2019-01-24T10:01:54.452Z","comments":true,"path":"2018/03/19/Android 组件化规范/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2018/03/19/Android 组件化规范/","excerpt":"本文主要说 Android的组件化的一个规范","text":"本文主要说 Android的组件化的一个规范 Android 组件化规范修订记录 Date Author Version Description 2017-11-13 wuzongbo 0.0.1 create 2018-03-19 YasinYao 0.0.2 update 目录 组件设计 组件使用 组件升级 组件设计设计要求 组件必须支持以application方式运行 每个组件最好能写写支持单元测试 一定要考虑多端通用(APP1、APP2、APP3、……) 命名规则 所有module包名必须为com.xxx.widge.mg{组件名}格式 所有的资源必须以“{组件名}_{资源名}”命名,防止资源ID冲突,例如mgbutton_activity_home 组件版本 每个组件统一使用gradle.properties统一管理版本 初始版本为:0.0.1,不要以1.0.0开始 测试版本格式:0.0.1-SNAPSHOT(SNAPSHOT 为快照版本) 正式版本格式:0.0.1 组件依赖 依赖第三方组件,如果不必暴露第三方库,必须使用implementation依赖 禁止组件之间的依赖(很重要!!!),如果有这种需求那你该考虑设计成interface啦 build.gradle 实例1234567891011121314151617181920212223242526272829303132333435apply plugin: 'com.android.library'android { compileSdkVersion ANDROID_BUILD_SDK_VERSION as int defaultConfig { minSdkVersion ANDROID_BUILD_MIN_SDK_VERSION as int targetSdkVersion ANDROID_BUILD_TARGET_SDK_VERSION as int //单个组件版本号,递增 versionCode 1 //组件版本名称,不可修改 versionName VERSION testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } //防止资源ID冲突 resourcePrefix project.name }}dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation deps.supportAppcompatV7 testImplementation deps.junit}//maven发布脚本apply from: '../maven.gradle' 组件发布 配置Maven 单个组件发布命令:./gradlew :{组件名}:uploadArchives 全部组件发布命令:./gradlew uploadArchives 组件使用组件依赖 在工程级的build.gradle中增加如下代码 1234567891011121314allprojects { repositories { maven { url 'http://192.168.60.96:8082/nexus/content/repositories/android-snapshots/' } maven { url 'http://192.168.60.96:8082/nexus/content/repositories/android/' } ... } configurations.all { //每隔10分钟检查动态版本的依赖是否更新 resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes' //每隔24小时检查远程依赖是否存在更新 resolutionStrategy.cacheChangingModulesFor 24, 'hours' }} 依赖开发阶段的组件,版本号必须为快照版本(SNAPSHOT),这样有利于快速发布 1implementation \"com.xxx.widget:mgbanner:0.0.1-SNAPSHOT\" 正式版依赖 1implementation \"com.xxx.widget:mgbanner:0.0.1\" 常见问题 如何删除Gradle缓存 删除”/Users/wuzongbo/.gradle/caches/modules-2/files-2.1/{package}”目录,再同步Gradle文件(Sync Project with Gradle Files) 如何刷新Gradle版本 在Android studio中Terminal中输入命令:./gradlew –refresh-dependencies 页面跳转 组件之间使用路由地址跳转 1MogoRouter.getInstance().build("mogopartner:///home").open(context); 组件内部跳转Builder跳转 1RoomDetailsActivity_Router.intent(getContext()).roomId(123).start(); 组件升级因为此工程为所有android项目所引用,所以最好能兼容老的版本调用方式就兼容方法与样式 修改方法比如在有一个方法A(int arg1,int arg2),我们要增加一个参数arg3,我们是不能再原来的方法是直接增加必须要新增一个方法A(int arg1,int arg2,int arg3),然后再用这个老的方法去调用新的方法,并且给一个默认值 修改前12int a(int arg1,int arg2){ return arg1+arg2; 修改后12345678}int a(int arg1,int arg2){ return a(arg1,arg2,0);}int a(int arg1,int arg2,int arg3){ return arg1+arg2+arg3;} 修改样式比如在有一个组件原来只是伙伴用的,颜色为蓝色, 现在要改为绿色,假设我们把这个颜色做活那么我们做兼容的时候就需要指定一个默认颜色为蓝色,这样就做到兼容伙伴","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"组件化","slug":"组件化","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/组件化/"}]},{"title":"Android 8.0适配","slug":"Android 8.0适配","date":"2018-03-10T08:07:36.000Z","updated":"2019-01-24T10:09:25.702Z","comments":true,"path":"2018/03/10/Android 8.0适配/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2018/03/10/Android 8.0适配/","excerpt":"本文主要说Android 8.0适配","text":"本文主要说Android 8.0适配 Android 8.0适配准备工作将我们项目中的targetSdkVersion改为 26(8.0) 或者 27(8.1),记住不要超过27,毕竟我还没有告诉你Android P怎么适配(/滑稽)。 Android 8.0 行为变更1.提醒窗口如果应用使用 权限的应用无法再使用以下窗口类型来在其他应用和系统窗口上方显示提醒窗口:123456```- TYPE_PHONE- TYPE_PRIORITY_PHONE- TYPE_SYSTEM_ALERT- TYPE_SYSTEM_OVERLAY- TYPE_SYSTEM_ERROR 相反,应用必须使用名为 的新窗口类型。1也就是说需要在之前的基础上判断一下: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mWindowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY}else { mWindowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT}1当然记得需要有权限 12使用 ```TYPE_APPLICATION_OVERLAY``` 窗口类型显示应用的提醒窗口时,请记住新窗口类型的以下特性: 应用的提醒窗口始终显示在状态栏和输入法等关键系统窗口的下面。 系统可以移动使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口或调整其大小,以改善屏幕显示效果。 通过打开通知栏,用户可以访问设置来阻止应用显示使用 TYPE_APPLICATION_OVERLAY 窗口类型显示的提醒窗口。12### 2.网页表单自动填充现在,Android 自动填充框架提供对自动填充功能的内置支持,对于安装到运行 Android 8.0 的设备上的应用,与 WebView 对象相关的下列函数已经发生变化: WebSettings getSaveFormData() 函数现在返回 false。之前,此函数返回 true。 调用 setSaveFormData() 不再有任何效果。WebViewDatabase 调用 clearFormData() 不再有任何效果。 hasFormData() 函数现在返回 false。之前,当表单包含数据时,此函数返回 true。 12345678910111213141516171819202122232425262728293031### 3.集合的处理在 Android 8.0 中,Collections.sort() 是在 List.sort() 的基础上实现的。在 Android 7.x(API 级别 24 和 25)中,则恰恰相反。在过去,List.sort() 的默认实现会调用 Collections.sort()。- List.sort() 的实现不能调用 Collections.sort(),因为这会导致堆栈因无限递归而溢出。相反,如果您需要 List 实现的默认行为,应避免重写 sort()。此项变更使 Collections.sort() 可以利用优化的 List.sort() 实现,但具有以下限制: - 如果父类以不适当的方法实现 sort() ,通常最好使用在 List.toArray()、Arrays.sort() 和 ListIterator.set() 的基础上构建的实现重写 List.sort()。 - 如果您选择后者只是因为您希望开发一种适用于所有 API 级别的 sort() 函数,可以考虑赋予其一个唯一的名称,例如 sortCompat(),而不是重写 sort()。- 现在,Collections.sort() 只是对调用 sort() 的 List 实现进行的一项结构性修改。例如,在 Android 8.0 之前的平台版本中,如果通过调用 List.sort() 进行排序,则当迭代处理 ArrayList 以及在迭代过程中调用 sort() 时,会引发 ConcurrentModificationException。而 Collections.sort() 则不会引发异常。 此项变更使平台行为更加一致:现在,两种方法都会引发 ConcurrentModificationException。现在,```AbstractCollection.removeAll() ```和 ```AbstractCollection.retainAll()``` 始终引发 ```NullPointerException```;之前,当集合为空时不会引发 ```NullPointerException``` 所以我们需要做判空处理。。### 4.运行时权限在 Android 8.0 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。#### 所以总结下来,如果你之前是用什么权限就去申请什么权限,那么恭喜你,这个变化不会影响到你。#### 如果你只申请了权限组中的某些权限,却用了同组的其他权限,那么你就需要去适配一下了。那么怎么适配呢,如果你去检查之前每个申请权限的地方,未免太过麻烦。那么你可以根据你项目中的Manifest文件中需要的权限与权限组去对比,整理出你需要申请的各个权限组,然后申请该权限组### 5.通知适配8.0在通知这里变化还挺多的,比如通知渠道、通知标志、通知超时、背景颜色的等,详细的说明可以去看官方的Android 8.0 [功能和 API](https://developer.android.google.cn/guide/topics/ui/notifiers/notifications#ManageChannels)。#### - 通知渠道:Android 8.0 引入了通知渠道,其允许您为要显示的每种通知类型创建用户可自定义的渠道。用户界面将通知渠道称之为通知类别。[详见 https://developer.android.com/training/notify-user/channels](https://developer.android.google.cn/training/notify-user/channels ) ![image](http://upload-images.jianshu.io/upload_images/4974296-689527687f761e84.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)可以看到高德地图分的很细致,分为四个组共13个类别(华为貌似对组不生效)。这样有个好处,我们可以控制我们想收到的通知,比如我不喜欢运营活动通知,那我就可以把它关闭。这样避免大量的不必要通知,否则使得用户觉得烦,一棒子打死。直接关闭你的允许通知。当然了,大量app都还没有适配,适配的也都分的不是很细致.当然更重要的问题是,如果不去适配,可能通知都不会弹出来。那么适配的方法如下: private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //分组(可选) //groupId要唯一 String groupId = "group_001"; NotificationChannelGroup group = new NotificationChannelGroup(groupId, "广告"); //创建group notificationManager.createNotificationChannelGroup(group); //channelId要唯一 String channelId = "channel_001"; NotificationChannel adChannel = new NotificationChannel(channelId, "推广信息", NotificationManager.IMPORTANCE_DEFAULT); //补充channel的含义(可选) adChannel.setDescription("推广信息"); //将渠道添加进组(先创建组才能添加) adChannel.setGroup(groupId); //创建channel notificationManager.createNotificationChannel(adChannel); //创建通知时,标记你的渠道id Notification notification = new Notification.Builder(MainActivity.this, channelId) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .setContentTitle("一条新通知") .setContentText("这是一条测试消息") .setAutoCancel(true) .build(); notificationManager.notify(1, notification); } }12345678效果如下:![image](http://upload-images.jianshu.io/upload_images/4974296-247c2245c0bc42f6.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)华为手机当只有一个渠道时,不会显示,会当做默认通知处理,除非一个以上。注意:当Channel已经存在时,后面的createNotificationChannel方法仅能更新其name/description,以及对importance进行降级,其余配置均无法更新。所以如果有必要的修改只能创建新的渠道,删除旧渠道。删除渠道代码如下: private void deleteNotificationChannel(String channelId){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.deleteNotificationChannel(channelId); }}` 6.后台限制执行具体说明https://developer.android.google.cn/about/versions/oreo/background 应用在两个方面受到限制: 后台服务限制: 处于空闲状态时,应用可以使用的后台服务存在限制。 这些限制不适用于前台服务,因为前台服务更容易引起用户注意。 广播限制: 除了有限的例外情况,应用无法使用清单注册隐式广播。 它们仍然可以在运行时注册这些广播,并且可以使用清单注册专门针对它们的显式广播。 在大多数情况下,应用都可以使用 JobScheduler 克服这些限制。 这种方式让应用安排为在未活跃运行时执行工作,不过仍能够使系统可以在不影响用户体验的情况下安排这些作业。 关于的用法可以参考官方例子:android-JobSchedulerhttps://github.com/googlesamples/android-JobScheduler 当然还有后台位置的限制需要去注意。https://developer.android.google.cn/about/versions/oreo/background-location-limits 参考 MIUI 10 通知类别 / Channel 适配 https://dev.mi.com/console/doc/detail?pId=1303 Create and Manage Notification Channels https://developer.android.google.cn/training/notify-user/channels Presentation of Notificationshttps://source.android.google.cn/compatibility/8.0/android-8.0-cdd#3_8_user_interface_compatibility Android 实现应用更新适配 Android Ohttps://blog.csdn.net/mq2856992713/article/details/79688587","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Android 8.0","slug":"Android-8-0","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android-8-0/"}]},{"title":"Android多点触控时出现pointerIndex out of range 的错误分析","slug":"Android多点触控时出现pointerIndex out of range 的错误分析","date":"2018-03-05T08:07:36.000Z","updated":"2019-01-24T09:44:13.312Z","comments":true,"path":"2018/03/05/Android多点触控时出现pointerIndex out of range 的错误分析/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2018/03/05/Android多点触控时出现pointerIndex out of range 的错误分析/","excerpt":"本文主要说Android多点触控时出现pointerIndex out of range 的错误分析","text":"本文主要说Android多点触控时出现pointerIndex out of range 的错误分析 Android多点触控时出现pointerIndex out of range 的错误分析关于多点触控时出现pointerIndex out of range这个问题在网上查了好多资料,有以下几种情况: 1.Android自身的问题,需要重新编译代码:这个没有验证过; 2.将返回结果改为return false; :这是一个老外写的解决方法,本来以为能解决问题,结果….呵呵了,连单点的拖动都不能用了-.-!! 3.没有处理异常:IllegalArgumentException这个靠点谱,因为控制台确实也报了这个错误,但一般文章都只贴了try{}catch代码,没写为什么,试了好多次,“一拖动”图片没了! 下面是具体的报错日志: 12345E/CrashReport: java.lang.IllegalArgumentException: pointerIndex out of range at android.view.MotionEvent.nativeGetAxisValue(Native Method) at android.view.MotionEvent.getX(MotionEvent.java:2080) at androidx.viewpager.widget.ViewPager.onInterceptTouchEvent(ViewPager.java:2072) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2120) 这个问题通过重写viewpager 然后try()catch 12345678### 问题分析##### 这个问题的这个准确的位置是在event.getX(int)和event.getY(int)中会报错,因为你的触控点(那个int参数)可能越界了!我的问题是在两点触控后,有一个手指抬起的时候出现的,发现问题了没有!!!!当两点触控后抬起一个手指的时候只有一个触控点了,通过event.getX(1)的时候就越界了!所以只是try{}catch{}是可以解决问题,但是没解决根本问题的。 ##### 这个问题的根本在于判断动作的时候可能没有做好case:MotionEvent.ACTION_POINTER_UP这个动作的处理,或没有调用super.onInterceptTouchEvent导致没有对pointerIndex 进行重新赋值。这个动作是在多点触控后,抬起部分手指(还有手指在屏幕上)的时候触发的。### 问题解决#### 1.处理好case:MotionEvent.ACTION_POINTER_UP的操作#### 2.调用 super.onInterceptTouchEvent();更新poinerIndex状态#### 3.重写报错的viewGroup 并try{}catch会出错的onIntercepterTouchevent()方法 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { try { return super.onInterceptTouchEvent(ev); } catch (IllegalArgumentException ex) { ex.printStackTrace(); } return false; }`","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Exception","slug":"Exception","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Exception/"}]},{"title":"为什么要升级Rxjava2?","slug":"Rxjava2技术分享","date":"2017-12-10T08:07:36.000Z","updated":"2019-01-24T09:47:07.315Z","comments":true,"path":"2017/12/10/Rxjava2技术分享/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/12/10/Rxjava2技术分享/","excerpt":"本文主要说我们为什么要升级Rxjava2,以及升级Rxjava2的好处","text":"本文主要说我们为什么要升级Rxjava2,以及升级Rxjava2的好处 1. 为什么要升级Rxjava21. 兼容Reactive Streams规范 官网Reactive Streams 是一种为异步流处理提供非阻塞压力的标准。 2. 更好的处理BackPressure的情况— Flowable 背压,即生产者的速度大于消费者的速度带来的问题 使用Flowable我们需要定义它的事件处理策略(缓存,丢弃,报错) 3. 拥有比Rxjava1更好的性能 image 2 .Rxjava1到Rxjava2有哪些变化1.Nulls这是一个很大的变化,熟悉 RxJava 1.x 的童鞋一定都知道,1.x 是允许我们在发射事件的时候传入 null 值的 ,但现在 2.x 不支持了,直接抛 NullPointerException 。 2. Observable->Observable,Flowable1.)基本对比 - rxjava1的Observable支持背压 通过.onBackpressureXXX使得支持背压 订阅关系 Observable subscribe Observer(new Acition1) - rxjava2的Observable不支持背压 他的订阅关系Observable->subscribe ->Observer(new Consumer) - rxjava2 的为了支持背压 订阅关系 Flowable->subscribe->Subscriber(new Consumer) 2.)发射世界和接收事件满足的条件 - 上游可以发送无限个onNext, 下游也可以接收无限个onNext. - 当上游发送了一个onComplete后, 上游onComplete之后的事件将会继续发送, 而下游收到onComplete事件之后将不再继续接收事件. - 当上游发送了一个onError后, 上游onError之后的事件将继续发送, 而下游收到onError事件之后将不再继续接收事件. - 上游可以不发送onComplete或onError. - 最为关键的是onComplete和onError必须唯一并且互斥, 即不能发多个onComplete, 也不能发多个onError, 也不能先发一个onComplete, 然后再发一个onError, 反之亦然 3.调度器的变化- Schedulers.io() 代表io操作的线程, 通常用于网络,读写文件等io密集型的操作 - Schedulers.computation() 代表CPU计算密集型的操作, 例如需要大量计算的操作 - Schedulers.newThread() 代表一个常规的新线程 - Scheduiers.trampoline() 代表不是立即执行而是加入到队列它是repeat()和retry()的默认调度器 - Schedulers.immediate() 代表在当前线程操作(2.X 取消) - AndroidSchedulers.mainThread() 代表Android的主线程 4.Function相关在1.x 中是有 Func1,Func2.....FuncN的,但 2.x 中将它们移除,而采用 Function 替换了 Func1,采用 BiFunction 替换了 Func 2..N。并且,它们都增加了 throws Exception,也就是说,不用担心我们做某些操作还需要 try-catch 了。 5. 观察者的接口实 Observer对比Subscriber Observer(rx1) Subscription CompositeSubscription void onNext(Object o) void onError(Throwable e) void onCompleted(); Subscriber(rx2) Disposable CompositeDisposable void onSubscribe(Disposable s) void onNext(Object o) void onError(Throwable t) void onComplete() 3.Rxjava2中一些强大功能和应用场景 采用 concat 操作符先读取缓存再通过网络请求获取数据 利用 concat 的必须调用 onComplete 后才能订阅下一个 Observable 的特性 使用 flatMap 实现多个网络请求依次依赖 flatMap 操作符可以将一个发射数据的 Observable 变换为多个 Observables ,然后将它们发射的数据合并后放到一个单独的 Observable 使用 zip 操作符,实现多个接口数据共同更新 UI zip 操作符可以将多个 Observable 的数据结合为一个数据源再发射出去 采用 interval 操作符实现心跳间隔任务 interval就是轮训 使用debounce操作符,实现现需操作放过滤不必要的消息 debounce可以指定一段时间内仅第一次发出的消息有效想必即时通讯等需要轮训的任务在如今的 APP 中已是很常见,而 RxJava 2.x 的 interval 操作符可谓完美地解决了我们的疑惑。 retryWhen操作符实现错误重试机制 retryWhen可以在错误发生时指定重试次数以及间隔 4.RxJava1.x 如何平滑升级到RxJava2.x?由于RxJava2.x变化较大无法直接升级,幸运的是,官方开发者提供了RxJava2Interop这个库,可以方便地将RxJava1.x升级到RxJava2.x,或者将RxJava2.x转回RxJava1.x。地址:https://github.com/akarnokd/RxJava2Interop","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Rxjava","slug":"Rxjava","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Rxjava/"}]},{"title":"View 的事件体系","slug":"View的事件体系","date":"2017-11-17T05:07:36.000Z","updated":"2019-01-24T09:37:23.901Z","comments":true,"path":"2017/11/17/View的事件体系/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/11/17/View的事件体系/","excerpt":"本文主要说View的基础知识,View的滑动和View分发机制","text":"本文主要说View的基础知识,View的滑动和View分发机制 View 的事件体系1.View的基础知识 View是所有控件的基类 View由四个顶点决定,top,left,right,bottom x,y View左上角的坐标 translationX,translationY是View左上角相对于父容器的偏移量默认是0 x = left + translationX y = top + translationY MotionEvent 在手指接触屏幕后所产生的一系列事件中 典型的事件有如下几种: ACTION_DOWN—手指刚接触屏幕; ACTION_MOVE—手指在屏幕上移动; ACTION_UP—手机从屏幕上松开的一瞬间. 通过MotionEvent对象我们可以得到点击事件发生的x和y坐标 getX/getY返回的是相对于当前View左上角的x和y坐标 getRawX/getRawY 返回的是相对于屏幕左上角的x和y坐标 TouchSlop 系统能识别的被认为是滑动的最小距离. 获取这个常量:ViewConfiguration.get(getContext()).getScaledTouchSlop(). VelocityTracker 速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直速度. VelocityTracker vt = VelocityTracker.obtain(); vt.addMovement(event); vt.computeCurrentVelocity(1000); int xV= (int)vt.getXVelocity(); int yV = (int)vt.getYVelcity(); vt.clear(); vt.recycle(); GestureDetector 手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为.2. View的滑动View滑动的常见实现方式 通过View本身提供的scrollTo/scrollBy方法 通过动画给View施加平移效果来实现滑动 通过改变View的LayoutParams 是的View重新布局而实现滑动 3.View分发机制 实质上就是MotionEvent的事件分发机制。即当MotionEvent产生一个事件以后,系统需要把事件传递给一个具体的View的这样一个过程。他主要包括三个方法:dispatchTouchEvent onInterceptTouchEvent onTouchEvent. - public boolean dispatchTouchEvent(MotionEvent e)用来进行事件 经过该View的事件,一定会调用这个方法。返回结果受onIntercepterTouchEvent 和onTouchEvent影响。返回值表示是否消费事件。 - public boolean onInterceptTouchEvent(MotionEvent e)在上述方法内部,判断是否拦截事件 同一个事件只会运行一次 返回结果表示是否拦截事件 - public boolean onTouchEvent(MotionEvent e)在diapatchTouchEvent方法内部 用来处理点击事件 表示是否消耗该事件 同一事件序列只会执行一次 ViewGroup事件分发过程:对于一个Viewgroup来说,接收到事件以后首先会调用diapatchTouchEvent 如果返回值是true 也就是说onInterceptTouchEvent返回true则表示自己消费这个事件 那么就会调用onTouchEvent方法;如果onIntercepterTouchEvent返回值是false,表示自己不处理该事件通过child.dispatchTouchEvent发送事件给子布局,如此反复 知道最后被处理。 View的事件优先级当一个View设置了OnTouchListener以后 事件会先执行OnTouchListener的onTouch方法,如果onTouch返回为true 表示OntouchListener消费了事件 就不会传递到OntouchEvent;如果返回为false,才会传递到OntouchEvent,如果OntouchEvent返回为true,这OntouchEvent消费事件,后续额onClicklister就不会接收到事件,click方法就不会被调用;如果OntouchEvent返回为false 才会执行到click方法。 所以view的监听优先级 OnTouchListener > OnTouchEvent > OnClickListener 事件分发方法的执行顺序 dispatchTouchEvent->onIntercepteTouchEvent->OnTouchListener(onTouch)->onTouchEvent->onClickListener(click)","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"View","slug":"View","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/View/"}]},{"title":"应用开发进阶必经之路之性能优化","slug":"应用开发进阶必经之路之性能优化","date":"2017-10-17T08:00:36.000Z","updated":"2019-01-24T09:39:13.381Z","comments":true,"path":"2017/10/17/应用开发进阶必经之路之性能优化/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/10/17/应用开发进阶必经之路之性能优化/","excerpt":"本文主要说Android应用开发中的一些性能优化点,帮助你更好的做好APP的性能优化","text":"本文主要说Android应用开发中的一些性能优化点,帮助你更好的做好APP的性能优化 性能问题分类- 1.内存问题: 耗内存、OOM、程序切换到后台后占用内存无法释放(OOM会影响产品的稳定性;耗内存、内存泄露会影响整机的性能;占用内存多预示着留给其它应用的剩余内存空间小); - 2.功耗问题 发烫(耗电) - 3.流畅度问题 启动慢、页面显示需要长时间转圈加载、页面切换卡顿、黑白屏(卡慢崩会让人烦躁); 针对上面一系列的性能问题,谷歌官方提供了各种各样的工具来针对性的解决各个方面的问题,也有很多不错的第三方工具值得尝试:内存问题:提供了Android Studio的静态代码检测功能、Android Monitor;第三方内存泄露分析工具Leakcanary、MAT;功耗问题:提供了GPU呈现模式、battery-historian、Android Monitor; 流畅度问题:提供了Android Studio的静态代码检测功能、Android Monitor、HierarchyViewer、StrictMode、过渡绘制检测工具、TraceView等;流畅度问题 主要是界面过度绘制问题 优化方法 merge作为根目录减少层级 原理是减少根目录Framlayout的绘制 viewStub加载页面时不绘制该布局原理是重写onDraw方法并被置空,onMeasure方法也没有实现 从而不绘制 而是在需要显示的时候显示(inflate) Space加载页面时不绘制布局原理同viewStub类似 但是他是绘制尺寸大小的 有自己的宽高 去掉多余的背景一般的activity都有个主题 它会为activity的window设置背景,再为activity设置背景显然会多绘制一次原理是减少一层背景的绘制 ==说明==: 1、在主题中去掉Window的背景时要注意,去掉之后必须重新运行程序检查一下,避免有些Activity并没有设置背景导致界面背景为黑色; 2、有的程序为了避免冷启动时界面黑屏/白屏的问题,在主题中为window设置了一张图片,然后在布局文件中为Activity也设置了背景,这样既会导致过渡绘制问题,还会导致内存问题(同一个页面两张全屏的图片,双倍内存);所以这种解决方式并不妥,如果是启动速度问题,直接优化启动速度比这种方式靠谱。 最重要的是产品设计合理,多和产品、UI沟通,避免无意义的工作 内存优化工具:Android Studio提高代码质量必杀技:Inspact Code LeakCanery - 非静态内部类导致的内存泄漏: 比如Handler,解决方法是将内部类写成静态内部类,在静态内部类中使用软引用/弱引用持有外部类的实例 -IO操作后,没有关闭文件导致的内存泄露 比如Cursor、FileInputStream、FileOutputStream使用完后没有关闭,这种问题在Android Studio 2.0中能够通过静态代码分析检查出来,直接改善就可以了; -自定义View中使用TypedArray后,没有recycle, 这种问题也可以在Android Studio 2.0中能够通过静态代码分析检查出来,直接改善就可以了; -某些地方使用了四大组件的context,在离开这些组件后仍然持有其context导致的内存泄露 1、数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task,一般情况不推荐; 属性动画导致的内存泄漏 google提供的一种无限循环的属性动画 在activity销毁是 需要先将动画关闭 2、数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用; 3、数字3:在Receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视); 加载图片导致的内存问题 这个问题遵循以下原则就可以了:1、UI只提供一套高分辨率的图,图片建议放在drawable-xxhdpi文件夹下(放在xxxhdpi或者更高分辨率的文件夹下没有必要,权衡利弊,照顾主流设备即可),这样在低分辨率设备中图片的大小只是压缩,不会存在内存增大的情况;2、涉及到桌面插件或者不需要缩放的图片,放在drawable-nodpi文件夹下,这个文件夹下的图片在任何设备上都是不会缩放的。","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"性能优化","slug":"性能优化","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/性能优化/"}]},{"title":"HashMap的实现原理","slug":"HashMap的实现原理","date":"2017-09-10T08:07:36.000Z","updated":"2019-03-13T08:46:48.812Z","comments":true,"path":"2017/09/10/HashMap的实现原理/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/09/10/HashMap的实现原理/","excerpt":"HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。","text":"HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap的实现原理1.HashMap概述HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 2.HashMap的数据结构在 Java 编程语言中,最基本的结构就是两种,一个是数组,另外一个是指针(引用),HashMap 就是通过这两个数据结构进行实现。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。 image 每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢? 一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。 HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。 首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。 3. HashMap的初始化过程1234567 public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); inflateTable(threshold); putAllForCreate(m);} DEFAULT_LOAD_FACTOR : 负载因子的默认值0.75 表示数据填充的临界值,即数据达到总数据的75%时就开始准备扩容了. DEFAULT_INITIAL_CAPACITY : 数据默认值为1<<4 (也就是16)从上面看出 现实调用自己的构造方法,然后创建存储的Table(实际是数组),最后把值添加到创建的table中 this(var1,var2)实际调用的构造方法12345678910111213141516171819202122232425/** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) { initialCapacity = MAXIMUM_CAPACITY; } else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) { initialCapacity = DEFAULT_INITIAL_CAPACITY; } if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); threshold = initialCapacity; init();} initialCapacity : ==即初始化申请空间的值,不等于Map实际初始化的内部数组的长度(稍后解释为什么)==,若不填写默认是使用DEFAULT_INITIAL_CAPACITY也就是16initialCapacity的最大值为1 << 30 也就是2^30次方 loadFactor : 负载因子的初始化值,若不填写默认为DEFAULT_LOAD_FACTOR也就是0.75 threshold : 下次扩容时的申请空间值 inflateTable方法123456789101112131415161718/** * Inflates the table. */private void inflateTable(int toSize) { // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); // Android-changed: Replace usage of Math.min() here because this method is // called from the <clinit> of runtime, at which point the native libraries // needed by Float.* might not be loaded. float thresholdFloat = capacity * loadFactor; if (thresholdFloat > MAXIMUM_CAPACITY + 1) { thresholdFloat = MAXIMUM_CAPACITY + 1; } threshold = (int) thresholdFloat; table = new HashMapEntry[capacity];} 这里面有个重点 实际申请的内部数组的大小 int capacity = roundUpToPowerOf2(toSize); 12345678910 private static int roundUpToPowerOf2(int number) { // assert number >= 0 : "number must be non-negative"; int rounded = number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (rounded = Integer.highestOneBit(number)) != 0 ? (Integer.bitCount(number) > 1) ? rounded << 1 : rounded : 1; return rounded;} 这段代码在传入的number在正常数值都会走 Integer.highestOneBit(number)和Integer.bitCount(number) 里面的代码都是位运算 ==roundUpToPowerOf2这个方法实际上的功能就是返回一个最小的是2^n且它大于等于number的值== 例如 number= 4 那么roundUpToPowerOf2的返回值就是2^2 = 4 number = 5 那么roundUpToPowerOf2的返回值就是2^3 = 8 那么问题来了 为啥内部数组大小为啥是2^n呢?而当数组长度为16时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1(比如(24-1)2=1111),这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了高位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。 所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。 putAllForCreate方法1234private void putAllForCreate(Map<? extends K, ? extends V> m) { for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) putForCreate(e.getKey(), e.getValue());} 这里是个foreach循环,将m中的数据拿出来一一添加到map, 下面看具体的putForCreate方法1234567891011121314151617181920212223242526/** * This method is used instead of put by constructors and * pseudoconstructors (clone, readObject). It does not resize the table, * check for comodification, etc. It calls createEntry rather than * addEntry. */private void putForCreate(K key, V value) { int hash = null == key ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key); int i = indexFor(hash, table.length); /** * Look for preexisting entry for key. This will never happen for * clone or deserialize. It will only happen for construction if the * input Map is a sorted map whose ordering is inconsistent w/ equals. */ for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { e.value = value; return; } } createEntry(hash, key, value, i);} 1.计算key的hash值key==null -> hash=0 key!=null -> hash = hash(key) 2.计算数组下标(根据hash值求余,即余数相同的hash值放到相同的数组下标对应的一个链表上)按位取并,作用上相当于取模mod或者取余%。这意味着数组下标相同,并不表示hashCode相同。1234567/** * Returns index for hash code h. */static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1);} 3.在对应的key上赋值或者添加一组map 4.HashMap的存取实现HashMap的基本存取过程基本如下123456789// 存储时:int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值int index = hash % Entry[].length;Entry[index] = value;// 取值时:int hash = key.hashCode();int index = hash % Entry[].length;return Entry[index]; 存储数据12345678910111213141516171819202122public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key); int i = indexFor(hash, table.length); for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null;} 从上面的源代码中可以看出:1.Map支持key=null12345678910111213private V putForNullKey(V value) { for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); return null;} 从上面的代码中可以看到当key==null 会将值放到table[0]索引下,并且当数据重复时,新数据会覆盖原数据,并返回原数据,若不重复则添加到table hash=0,bucketIndex=0; 2.当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。 addEntry方法12345678910void addEntry(int hash, K key, V value, int bucketIndex) { // 获取指定 bucketIndex 索引处的 Entry Entry<K,V> e = table[bucketIndex]; // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //参数e, 是Entry.next // 如果 Map 中的 key-value 对的数量超过了极限 if (size++ >= threshold) // 把 table 对象的长度扩充到原来的2倍。 resize(2 * table.length);} 储HashMap中的key-value对时,完全没有考虑Entry中的value,仅仅只是根据key来计算并决定每个Entry的存储位置。我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。 hash(int h)方法根据key的hashCode重新计算一次散列。此算法加入了高位计算,防止低位不变,高位变化时,造成的hash冲突。 当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因子(loadFactor),随着map的size越来越大,Entry[]会以一定的规则加长长度。 读取数据1234567 public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue();} 当key=null 直接取table的索引为0下的值;当key!=null 12345678910111213141516 final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key); for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null;} 从HashMap中get元素时,首先计算key的hashCode,在通过indexFor方法找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。 归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。 5.HashMap的扩容 当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。 那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容 12345678910111213141516 void resize(int newCapacity) { HashMapEntry[] oldTable = table; int oldCapacity = oldTable.length; //如果当前的数组长度已经达到最大值,则不在进行调整 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; }//根据传入参数的长度定义新的数组 HashMapEntry[] newTable = new HashMapEntry[newCapacity]; //按照新的规则,将旧数组中的元素转移到新数组中 transfer(newTable); table = newTable; //更新临界值 threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } 旧数组的数据转到新数组 123456789101112 void transfer(HashMapEntry[] newTable) { int newCapacity = newTable.length; for (HashMapEntry<K,V> e : table) { while(null != e) { HashMapEntry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } }} 6.Fail-Fast机制: 我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。 这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。 12345678HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry HashMapEntry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } } 在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map: 注意到modCount声明为volatile,保证线程之间修改的可见性。(volatile之所以线程安全是因为被volatile修饰的变量不保存缓存,直接在内存中修改,因此能够保证线程之间修改的可见性) 123456789101112131415 final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); HashMapEntry<K,V> e = next; if (e == null) throw new NoSuchElementException(); if ((next = e.next) == null) { HashMapEntry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e;} 在HashMap的API中指出: 由所有HashMap类的“collection 视图方法”所返回的迭代器都是快速失败的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不保证在将来不确定的时间发生任意不确定行为的风险。 注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误. 解决办法 第一、使用线程安全的ConcurrentHashMap或HashTable,它就不会产生ConcurrentModificationException异常,也就是它使用迭代器完全不会产生fail-fast机制 第二.Collections.synchronizedMap将HashMap包装起来 返回由指定映射支持的同步(线程安全的)映射。为了保证按顺序访问,必须通过返回的映射完成对底层映射的所有访问。在返回的映射或其任意 collection 视图上进行迭代时,强制用户手工在返回的映射上进行同步:123456789Map m = Collections.synchronizedMap(new HashMap());...Set s = m.keySet(); //不需要加锁...synchronized(m) { // 对Map的对象m加锁Iterator i = s.iterator(); // 必须加锁的模块 while (i.hasNext()) foo(i.next());} 参考资料 深入Java集合学习系列:HashMap的实现原理 HashMap实现原理分析 HashMap, HashTable, CurrentHashMap的区别","categories":[{"name":"Java","slug":"Java","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Java/"},{"name":"Hashmap","slug":"Hashmap","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Hashmap/"}]},{"title":"升级Android Studio3.0 beta1问题汇总","slug":"升级Android Studio3.0 beta1问题汇总","date":"2017-09-01T08:07:36.000Z","updated":"2019-01-24T09:42:12.140Z","comments":true,"path":"2017/09/01/升级Android Studio3.0 beta1问题汇总/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/09/01/升级Android Studio3.0 beta1问题汇总/","excerpt":"本文主要说升级Android Studio3.0 的几个问题及解决方法","text":"本文主要说升级Android Studio3.0 的几个问题及解决方法 1.Gradle sync failed: Cause: java.lang.NullPointerException问题描述: 这是从AS3.0 alpha版本升级到beta产生的问题官方文档有说明地址https://androidstudio.googleblog.com/2017/08/android-studio-30-beta-1.html解决办法: 菜单栏上 Build > Clean Project 然后重新使用Sync Project 即可 2.Gradle sync failed: Cause: com.android.support:multidex:1.0.2问题描述: 这个也是升级beta1出现的 他提示我升级到multidex的1.0.2版本 然后我升级到1.0.2版本以后发现找不到资源(下载不下来)解决办法:在项目的根目录build.gradle中加入配置google()仓库,jcenter仓库中没有最新的1.0.2版本的multidex12345678allprojects { repositories { google()//新增的 jcenter() mavenCentral() maven { url "https://jitpack.io" } }} 2.Error:Execution failed for task ‘:app:lintVitalRelease’. > java.lang.NullPointerException (no error message)问题描述:react native编译报错 解决方法: 1.执行命令 ./gradlew app:assembleRelease -x lintVitalRelease 2.android - app - build.gradle 里面添加123456789android {... lintOptions { checkReleaseBuilds false }...}","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Android Studio","slug":"Android-Studio","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android-Studio/"}]},{"title":"深入了解Volley","slug":"Volley源码分析","date":"2017-08-07T08:07:36.000Z","updated":"2019-01-24T09:34:03.379Z","comments":true,"path":"2017/08/07/Volley源码分析/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/08/07/Volley源码分析/","excerpt":"本文主要说Volley的使用及分析源码,从而深入了解Volley","text":"本文主要说Volley的使用及分析源码,从而深入了解Volley 一.Volley的基本使用1.volley简单使用 1.创建一个RequestQueue1RequestQueue requestQueue = Volley.newReequestQueue(context); 2.创建一个StringRequest对象1234567891011StringRequest sq = new StringRequest("www.baidu.com",new Request.Listener<String>(){ @overide public void onRespose(String res){ Log.d("TAG",res) }},new Respose.ErrorListener(){ @Override public void onErrorRespose(VolleyError error){ log.e("TAG",error.getMessage(),error) }}); 3.把StringRequest对象添加到RequestQueue1requestQueue.add(sq); 2.其他StringRequest的请求方式 —四参构造方法123456789StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) { @Override protected Map<String, String> getParams() throws AuthFailureError { Map<String, String> map = new HashMap<String, String>(); map.put("params1", "value1"); map.put("params2", "value2"); return map; } }; JsonRequest的用法StringRequest,JsonRequest也是继承自Request类的不过由于JsonRequest是一个抽象类,因此我们无法直接创建它的实例,那么只能从它的子类入手了。JsonRequest有两个直接的子类,JsonObjectRequest和JsonArrayRequest12345678910111213JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html",null,new Respose.Listener<JSONObject>(){ @Override public void onRespose(JSONObject respose){ log.d("TAG",response.toString()); }},new Respose.ErrorListener(){ @Override public void onErrorListener(VolleyError error){ Log.e("TAG",error.getMessage(),error); }});//最后把它添加到RequestQueue就可以了mQueue.add(jsonObjectRequest); 2.使用Volley加载网络图片1.ImageRequest的用法1.创建RequestQueue对象1RequestQueue rq = Volley.newRequestQueue(context); 2.创建ImageRequest对像1234567891011ImageRequest ir = new ImageRequest("image_url",new Respose>listener<Bitmap>(){ @Override public void onRespose(Bitmap res){ imageview.setImageBitmap(res); }},0,0,Config.RGB_565,new Response.ErrorListener(){ @Override public void onErrorRespose(VolleyReeor error){ imageView.setImageResource(R.drawable.default_image); }}) 可以看到,ImageRequest的构造函数接收六个参数,第一个参数就是图片的URL地址,这个没什么需要解释的。第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。 3.最后将ImageRequest对象添加到RequestQueue中1mQueue.add(imageRequest); 2.ImageLoader的用法由于ImageLoader已经不是继承自Request的了,所以它的用法也和我们之前学到的内容有所不同,总结起来大致可以分为以下四步: 1. 创建一个RequestQueue对象。1RequestQueue rq = Volley.newRequestQueue(context); 2. 创建一个ImageLoader对象。123456789ImageLoader imageLoader = new ImageLoader(rq,new ImageCache(){ @Override public void putBitmap(String url,Bitmap bitmap){ } @Override public Bitmap getBitmap(String url){ return null }}) 3. 获取一个ImageListener对象。1ImageListener listener = ImageLoader.getImagerListener(imageview,R.drawable.default,R.drawable.failed_image); 我们通过调用ImageLoader的getImageListener()方法能够获取到一个ImageListener对象,getImageListener()方法接收三个参数,第一个参数指定用于显示图片的ImageView控件,第二个参数指定加载图片的过程中显示的图片,第三个参数指定加载图片失败的情况下显示的图片。 4. 调用ImageLoader的get()方法加载网络上的图片。1imageLoader.get("image_url",listener); get()方法接收两个参数,第一个参数就是图片的URL地址,第二个参数则是刚刚获取到的ImageListener对象。当然,如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的最大宽度和高度,如下所示:1imageLoader.get("http://img.my.csdn.net/uploads/201404/13/1397393290_5765.jpeg", listener, 200, 200); 三 定制自己的Request主要参考StringRequest 继承Request首先是StringRequest的源码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647/** * A canned request for retrieving the response body at a given URL as a String. */public class StringRequest extends Request<String> { private final Listener<String> mListener; /** * Creates a new request with the given method. * * @param method the request {@link Method} to use * @param url URL to fetch the string at * @param listener Listener to receive the String response * @param errorListener Error listener, or null to ignore errors */ public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; } /** * Creates a new GET request. * * @param url URL to fetch the string at * @param listener Listener to receive the String response * @param errorListener Error listener, or null to ignore errors */ public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); } @Override protected void deliverResponse(String response) { mListener.onResponse(response); } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); }} 可以看到,StringRequest的源码很简练,根本就没几行代码,我们一起来分析下。首先StringRequest是继承自Request类的,Request可以指定一个泛型类,这里指定的当然就是String了,接下来StringRequest中提供了两个有参的构造函数,参数包括请求类型,请求地址,以及响应回调等,由于我们已经很熟悉StringRequest的用法了,相信这几个参数的作用都不用再解释了吧。但需要注意的是,在构造函数中一定要调用super()方法将这几个参数传给父类,因为HTTP的请求和响应都是在父类中自动处理的。 另外,由于Request类中的deliverResponse()和parseNetworkResponse()是两个抽象方法,因此StringRequest中需要对这两个方法进行实现。deliverResponse()方法中的实现很简单,仅仅是调用了mListener中的onResponse()方法,并将response内容传入即可,这样就可以将服务器响应的数据进行回调了。parseNetworkResponse()方法中则应该对服务器响应的数据进行解析,其中数据是以字节的形式存放在NetworkResponse的data变量中的,这里将数据取出然后组装成一个String,并传入Response的success()方法中即可。 下面我们就可以动手来尝试实现一下XMLRequest了,代码如下所示:123456789101112131415161718192021222324252627282930313233343536public class XMLRequest extends Request<XmlPullParser> { private final Listener<XmlPullParser> mListener; public XMLRequest(int method, String url, Listener<XmlPullParser> listener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; } public XMLRequest(String url, Listener<XmlPullParser> listener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); } @Override protected Response<XmlPullParser> parseNetworkResponse(NetworkResponse response) { try { String xmlString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = factory.newPullParser(); xmlPullParser.setInput(new StringReader(xmlString)); return Response.success(xmlPullParser, HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch (XmlPullParserException e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(XmlPullParser response) { mListener.onResponse(response); }} 自定义GsonRequest123456789101112131415161718192021222324252627282930313233343536373839public class GsonRequest<T> extends Request<T> { private final Listener<T> mListener; private Gson mGson; private Class<T> mClass; public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener, ErrorListener errorListener) { super(method, url, errorListener); mGson = new Gson(); mClass = clazz; mListener = listener; } public GsonRequest(String url, Class<T> clazz, Listener<T> listener, ErrorListener errorListener) { this(Method.GET, url, clazz, listener, errorListener); } @Override protected Response<T> parseNetworkResponse(NetworkResponse response) { try { String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success(mGson.fromJson(jsonString, mClass), HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(T response) { mListener.onResponse(response); }} 带你从源码的角度理解Volley核心思路:我们在主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。 开始说起分析源码,那么应该从哪儿开始看起呢?这就要回顾一下Volley的用法了,还记得吗,使用Volley的第一步,首先要调用Volley.newRequestQueue(context)方法来获取一个RequestQueue对象,那么我们自然要从这个方法开始看起了,代码如下所示:123public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, null); } 这个方法仅仅只有一行代码,只是调用了newRequestQueue()的方法重载,并给第二个参数传入null。那我们看下带有两个参数的newRequestQueue()方法中的代码,如下所示:123456789101112131415161718192021public static RequestQueue newRequestQueue(Context content,HttpStack stack){ File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); String userAgent = "volley/0"; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } Network network = new BasicNetwork(stack); RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; } 可以看到,这里在第10行判断如果stack是等于null的,则去创建一个HttpStack对象,这里会判断如果手机系统版本号是大于9的,则创建一个HurlStack的实例,否则就创建一个HttpClientStack的实例。实际上HurlStack的内部就是使用HttpURLConnection进行网络通讯的,而HttpClientStack的内部则是使用HttpClient进行网络通讯的,这里为什么这样选择呢?可以参考我之前翻译的一篇文章Android访问网络,使用HttpURLConnection还是HttpClient? 创建好了HttpStack之后,接下来又创建了一个Network对象,它是用于根据传入的HttpStack对象来处理网络请求的,紧接着new出一个RequestQueue对象,并调用它的start()方法进行启动,然后将RequestQueue返回,这样newRequestQueue()的方法就执行结束了。 那么RequestQueue的start()方法内部到底执行了什么东西呢?我们跟进去瞧一瞧:12345678910111213public void start() { stop(); // Make sure any currently running dispatchers are stopped. // Create the cache dispatcher and start it. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); // Create network dispatchers (and corresponding threads) up to the pool size. for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } } 这里先是创建了一个CacheDispatcher的实例,然后调用了它的start()方法,接着在一个for循环里去创建NetworkDispatcher的实例,并分别调用它们的start()方法。这里的CacheDispatcher和NetworkDispatcher都是继承自Thread的,而默认情况下for循环会执行四次,也就是说当调用了Volley.newRequestQueue(context)之后,就会有五个线程一直在后台运行,不断等待网络请求的到来,其中CacheDispatcher是缓存线程,NetworkDispatcher是网络请求线程。 addRequest得到了RequestQueue之后,我们只需要构建出相应的Request,然后调用RequestQueue的add()方法将Request传入就可以完成网络请求操作了,那么不用说,add()方法的内部肯定有着非常复杂的逻辑,我们来一起看一下:12345678910111213141516171819202122232425262728293031323334353637public <T> Request<T> add(Request<T> request) { // Tag the request as belonging to this queue and add it to the set of current requests. request.setRequestQueue(this); synchronized (mCurrentRequests) { mCurrentRequests.add(request); } // Process requests in the order they are added. request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue"); // If the request is uncacheable, skip the cache queue and go straight to the network. if (!request.shouldCache()) { mNetworkQueue.add(request); return request; } // Insert request into stage if there's already a request with the same cache key in flight. synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); if (mWaitingRequests.containsKey(cacheKey)) { // There is already a request in flight. Queue up. Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); if (stagedRequests == null) { stagedRequests = new LinkedList<Request<?>>(); } stagedRequests.add(request); mWaitingRequests.put(cacheKey, stagedRequests); if (VolleyLog.DEBUG) { VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); } } else { // Insert 'null' queue for this cacheKey, indicating there is now a request in // flight. mWaitingRequests.put(cacheKey, null); mCacheQueue.add(request); } return request; } } 可以看到,在第11行的时候会判断当前的请求是否可以缓存,如果不能缓存则在第12行直接将这条请求加入网络请求队列,可以缓存的话则在第33行将这条请求加入缓存队列。在默认情况下,每条请求都是可以缓存的,当然我们也可以调用Request的setShouldCache(false)方法来改变这一默认行为。 OK,那么既然默认每条请求都是可以缓存的,自然就被添加到了缓存队列中,于是一直在后台等待的缓存线程就要开始运行起来了,我们看下CacheDispatcher中的run()方法,代码如下所示:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475public class CacheDispatcher extends Thread { …… @Override public void run() { if (DEBUG) VolleyLog.v("start new dispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // Make a blocking call to initialize the cache. mCache.initialize(); while (true) { try { // Get a request from the cache triage queue, blocking until // at least one is available. final Request<?> request = mCacheQueue.take(); request.addMarker("cache-queue-take"); // If the request has been canceled, don't bother dispatching it. if (request.isCanceled()) { request.finish("cache-discard-canceled"); continue; } // Attempt to retrieve this item from cache. Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { request.addMarker("cache-miss"); // Cache miss; send off to the network dispatcher. mNetworkQueue.put(request); continue; } // If it is completely expired, just send it to the network. if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); mNetworkQueue.put(request); continue; } // We have a cache hit; parse its data for delivery back to the request. request.addMarker("cache-hit"); Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); if (!entry.refreshNeeded()) { // Completely unexpired cache hit. Just deliver the response. mDelivery.postResponse(request, response); } else { // Soft-expired cache hit. We can deliver the cached response, // but we need to also send the request to the network for // refreshing. request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); // Mark the response as intermediate. response.intermediate = true; // Post the intermediate response back to the user and have // the delivery then forward the request along to the network. mDelivery.postResponse(request, response, new Runnable() { @Override public void run() { try { mNetworkQueue.put(request); } catch (InterruptedException e) { // Not much we can do about this. } } }); } } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } } }} 代码有点长,我们只挑重点看。首先在11行可以看到一个while(true)循环,说明缓存线程始终是在运行的,接着在第23行会尝试从缓存当中取出响应结果,如何为空的话则把这条请求加入到网络请求队列中,如果不为空的话再判断该缓存是否已过期,如果已经过期了则同样把这条请求加入到网络请求队列中,否则就认为不需要重发网络请求,直接使用缓存中的数据即可。之后会在第39行调用Request的parseNetworkResponse()方法来对数据进行解析,再往后就是将解析出来的数据进行回调了,这部分代码我们先跳过,因为它的逻辑和NetworkDispatcher后半部分的逻辑是基本相同的,那么我们等下合并在一起看就好了,先来看一下NetworkDispatcher中是怎么处理网络请求队列的,代码如下所示:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556public class NetworkDispatcher extends Thread { …… @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Request<?> request; while (true) { try { // Take a request from the queue. request = mQueue.take(); } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } try { request.addMarker("network-queue-take"); // If the request was cancelled already, do not perform the // network request. if (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } addTrafficStatsTag(request); // Perform the network request. NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // If the server returned 304 AND we delivered a response already, // we're done -- don't deliver a second identical response. if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } // Parse the response here on the worker thread. Response<?> response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); // Write to cache if applicable. // TODO: Only update cache metadata instead of entire record for 304s. if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } // Post the response back. request.markDelivered(); mDelivery.postResponse(request, response); } catch (VolleyError volleyError) { parseAndDeliverNetworkError(request, volleyError); } catch (Exception e) { VolleyLog.e(e, "Unhandled exception %s", e.toString()); mDelivery.postError(request, new VolleyError(e)); } } }}","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Volley","slug":"Volley","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Volley/"}]},{"title":"Android高效加载大图、多图解决方案,有效避免程序OOM","slug":"Android高效加载大图、多图解决方案,有效避免程序OOM","date":"2017-08-03T08:07:36.000Z","updated":"2019-01-24T09:34:50.523Z","comments":true,"path":"2017/08/03/Android高效加载大图、多图解决方案,有效避免程序OOM/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/08/03/Android高效加载大图、多图解决方案,有效避免程序OOM/","excerpt":"本文主要说Android高效加载大图、多图解决的方案,可以有效避免Android程序OOM","text":"本文主要说Android高效加载大图、多图解决的方案,可以有效避免Android程序OOM 一.高效加载大图1.查看程序可用内存大小12int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);Log.d("TAG","Max memory is "+maxmoory+"KB"); 因此在展示高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。 每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。如下代码所示:123456BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;//禁止为Bitmap分配内存BitmapFactory.decodeResource(getResources(),R.id.myimage,options);int imageHeihht = options.outHeight;int imageWidth = options.outWidth;String imageType = options.outMimeType; 加载图片前考虑是完整显示图片还是要压缩后再显示,就需要考虑以下因素: 预估整张图片占用的内存 为了加在一张图片,你愿意提供多少内存 用于展示的图片控件的实际大小 当前设备的屏幕尺寸和分辨率对图片压缩需要使用BitmapFactory.Options中的inSampleSize(例如:2048 1536像素的图片,inSampleSize= 4,图片被压缩成 512 384 所占的内存大小为512 384 4 = 0.75M (假设图片是ARGB_8888类型,即每个像素点占用4个字节)下面的方法可以根据宽高计算出合适的inSampleSize值:123456789101112131415public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){ //源图片的高度和宽度 final int height = options.outHeight; final int width = options.outWidth; int inSampleSaze= 1; if(height>reqHeight||width>reqWidth){ //计算实际宽高和目标宽高的比率 final int heightRatio = Math.round((float)height/(float)reqHeight); final int widthRatio = Math.round((float)height/(float)reqHeight); //选择宽高比例较小的作为InSampleSize的值,这样保证最终图片的宽高是大于目标的宽高 inSampleSize = heightRatio>widthRatio?widthRatio:heightRatio; } return inSampleSize;} 获取到inSampleSize值以后再把inJustDecodeBounds设置为false,就可以使用压缩后的图片了1234567891011public static Bitmap decodeSampleBitmapFromResource(Resources res,int resId,int reqWidth,int reqHeight){ //第一次设置inJustDecodeBounds为true,来获取图片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res,resId,options); //调用方法计算inSampleSize options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight); //使用insamplesize在此解析图片 options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res,resId,options);} 下面的代码非常简单的将任意一张图片设置压缩成100*100的缩略图,并显示在ImageView上1mImageView.setImageBitmap(decodeSampleBitmapFromResource(getResource(),R.id.myimage,100,100)); 二.使用图片缓存技术防止频繁的显示多张图片 以及回收过的图片再次显示导致大量的加载而引起OOM 内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是==把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。== 为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如: 你的设备可以为每个应用程序分配多大的内存? 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上? 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。 图片的尺寸和大小,还有每张图片会占据多少内存空间。 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效 下面是一个使用 LruCache 来缓存图片的例子:12345678910111213141516171819202122232425private LruCache<String,Bitmap>mMemoryChahe;@Overrideprotected void onCreate(Bundle savedInstanceState){ / 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 // LruCache通过构造函数传入缓存值,以KB为单位。 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 使用最大可用内存值的1/8作为缓存的大小。 int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String,Bitmap>(cacheSize){ @Override protect int sizeOf(String key,Bitmap bitmap){ //重写此方法来衡量每张图片的大小,默认返回图片数量. return bitmap.getByteCount()/1024; } };}public void addBitmapToMemoryCache(String key,Bitmap bitmap){ if(getBitmapFromMemCache(key)==null){ mMemoryCache.put(key,bitmap); }}public Bitmap getBitmapFromMemoryCache(String key){ return mMemoryCache.get(key);} 在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800 480 4)。因此,这个缓存大小可以存储2.5页的图片。 当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。1234567891011121314151617181920212223public void loadBitmap(int resId,ImageView imageview){ final String imageKay = String valueOf(resId); Bitmap bitmap = mMemoryCache.getBitmapFromMemoryCache(imageKey); if(bitmap!=null){ imageview.setImageBitmap(bitmap); }else{ imageview.setImageResource(R.drawable.image_placeholder); //缓存 BitmapWorkerTask task = new BitmapWorkTask(imageview); task.execute(resId); } }class BitmapWorkerTask extends AsyncTask<Integer,Void,Bitmap>{ //异步加载图片 @Override protected Bitmap doInBackground(Integer...params){ final Bitmap bitmap = edcodeSampleBitmapFromResource(getResource(),params[0],100,100); addBitmapToMemoryCache(String.valueOf(params[0],bitmap)); return bitmap; }}","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"OOM","slug":"OOM","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/OOM/"},{"name":"Image","slug":"Image","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Image/"}]},{"title":"Android设备root下使用Shell调用ADB指令方式来点击屏幕","slug":"使用Shell调用ADB指令方式来点击屏幕","date":"2017-06-06T08:17:36.000Z","updated":"2019-01-24T09:26:04.560Z","comments":true,"path":"2017/06/06/使用Shell调用ADB指令方式来点击屏幕/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/06/06/使用Shell调用ADB指令方式来点击屏幕/","excerpt":"本文主要说Android设备root下使用Shell调用ADB指令方式来点击屏幕","text":"本文主要说Android设备root下使用Shell调用ADB指令方式来点击屏幕 背景 老大最近有个需求要捕获一些东西(具体不能细说),然后在需求的最关键的一个点就归结到要点击一下别的APP的一个指定位置.在网上搜索安卓按键模拟。用了很多方式都不太好,最后锁定了使用ADB调试的方案,向手机发送按键事件。刚好之前做过在Root权限下,用Java调用安卓底层的Linux Shell,然后执行pm指令使用Shell调用ADB指令. 使用Java执行 Runtime.getRuntime().exec("su").getOutputStream(),获取了一个具有Root权限的Process的输出流对象,向其中写入字符串即可以Root权限被Shell执行,ADB模拟按键的指令为 input keyevent keyCode,keyCode为按键的键值,例如KeyEvent.KEYCODE_VOLUME_UP表示音量加。 至于触屏或鼠标事件,只要调用相应的ADB指令即可。但是有一点问题,就是反应速度非常慢,尤其是连续模拟多个按键的时候,甚至会死机。而按键精灵运行的就相当流畅,我又开始好奇按键精灵是怎么实现的。 后来终于还是找到了原因,模拟按键时,不应每次都调用Runtime.getRuntime().exec("su"),因为每次调用这个代码的时候,都会获取Runtime实例,并且执行”su”请求Root权限,反应就会很慢(我的理解是相当于每次都新开一个命令行窗口);而应该只是在一开始执行一次,并获取一个OutputStream实例,后来每次执行一条Shell指令,只需向其中写入相应字符串,这样就快了很多。 下面贴出可用的代码。要求设备已经Root,不需要其他任何特殊权限或签名。由于用的是ADB指令,兼容性也不会有太大问题。首次运行程序时(其实也就是执行Runtime.exec(“su”)的时候),会请求Root权限。 123456789101112131415161718192021222324252627282930313233343536/** * 用root权限执行Linux下的Shell指令 * */public class RootShell { private OutputStream os; /** * 执行shell指令 * * @param cmd * 指令 */ public final void exec(String cmd) { try { if (os == null) { os = Runtime.getRuntime().exec("su").getOutputStream(); } os.write(cmd.getBytes()); os.flush(); } catch (Exception e) { e.printStackTrace(); } } /** * 后台模拟全局按键 * * @param keyCode * 键值 */ public final void simulateKey(int keyCode) { exec("input keyevent " + keyCode + "\\n"); }} 其他参考博客地址1地址2","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Root","slug":"Root","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Root/"},{"name":"ADB","slug":"ADB","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/ADB/"}]},{"title":"Android集成微信支付出现的-1等错误","slug":"Android集成微信支付的出现-1等错误需要注意的要点","date":"2017-06-06T08:07:36.000Z","updated":"2019-01-24T09:18:03.444Z","comments":true,"path":"2017/06/06/Android集成微信支付的出现-1等错误需要注意的要点/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/06/06/Android集成微信支付的出现-1等错误需要注意的要点/","excerpt":"本文主要说Android集成微信支付出现的-1等错误的解决及注意的点","text":"本文主要说Android集成微信支付出现的-1等错误的解决及注意的点 一.前言 微信支付和支付宝支付是现在APP常用的支付方式,但是真正接入过两种支付方式的猿友会很明显的感觉到微信支付真心比支付宝麻烦很多,会出现很多莫名其妙的错误,但是官方的文档却很难给出较好的解决方案. 前几天公司的APP需要支付功能然后也需要这个-1问题,简直感觉微信支付丧心病狂,这里总结下自己出现的问题和一些其他网友出现的问题做个总结,最后,欢迎补充.谢谢** 二.错误的统计*官方的描述: -1 错误 可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等。 1.签名错误: (1).签名的参数集合没有按照参数名ASCII码从小到大排序(字典序). (2).签名的是时候漏了使用key,(key的由来可以看下面第三条的分析) (3).签名的KEY错误. 这里用来签名的key是申请支付功能以后,微信给你的一个商户账号里面设置的.具体key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置 (4).签名后的key没有进行转化成大写或者其他例如前面的签名参数先排好序最后才加上key(key字段不参与ASCII码的大小排序,而是直接放到最后) (5),还有一些其他的格式错误请参看官方文档的详细说明 微信官方的签名说明,请认真对比. (6),签名问题的终极大招—–使用官方的签名认证工具一一对比.注:最好在连接生成的key和最终MD5之后的结果 在log下打印出来,可以方便查看出错的位置 接口调试工具 2.APPID错误 (1)APPID是在open.weixin.qq.com上创建的应用,可以通过 点击管理中心–>应用详情 来查看APPID 这里写图片描述 (2)创建APP时候上传的证书与现在使用的不一致.商户在微信开放平台申请开发应用后,微信开放平台会生成APP的唯一标识APPID。由于需要保证支付安全,需要在开放平台绑定商户应用包名和应用签名,设置好后才能正常发起支付。设置界面在【开放平台】中的栏目【 管理中心 –> 修改应用 –> 修改开发信息】里面 这里写图片描述 应用包名:是在APP项目配置文件AndroidManifest.xml中声明的package值,例如DEMO中的package=”com.nmm.paydemo”。应用签名:根据项目的应用包名和编译使用的keystore,可由签名工具生成一个32位的md5串,在调试的手机上安装签名工具后,运行可生成应用签名串,如图8.9所示,绿色串即应用签名。签名工具下载地址 对比查看应用签名是否一致,特别注意,一般上传都是使用release版本的key所以在测试的时候就需要使用签名版的apk,普通的debug版本key是不一致的 #三.结束文档主要参考网络和自己的使用过程,有什么错误的地方欢迎指正","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"微信","slug":"微信","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/微信/"}]},{"title":"升级Android Studio2.4问题汇总","slug":"升级Android Studio2.4问题汇总","date":"2017-04-24T08:07:36.000Z","updated":"2019-01-24T09:28:52.179Z","comments":true,"path":"2017/04/24/升级Android Studio2.4问题汇总/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/04/24/升级Android Studio2.4问题汇总/","excerpt":"本文主要说升级Android Studio2.4的一些问题及解决方案","text":"本文主要说升级Android Studio2.4的一些问题及解决方案 1. Warning:android-apt plugin is incompatible with future version of Android Gradle plugin. Please use ‘annotationProcessor’ configuration instead.如上图所示是完整的警告提示就是说新版本这个组件是不相容的需要去除 解决方案: 在 build.gradle中删除这一行 ( apply plugin: ‘android-apt’ ) 删除目录app/build/generated 这个文件夹 重新编译 2. Error:Could not initialize class com.android.ide.common.util.ReadWriteProcessLock解决方法停止这个进程,然后有两种方式来解决 : 1- File/ Invalidate Caches/Restart Android Studio, 2- 在Android Studio的Terminal 界面输入命令: ./gradlew –stop 然后重新编译项目 3.Warning:The Jack toolchain is deprecated. To enable support for Java 8 language features, remove ‘jackOptions { … }’ from your build.gradle file, and add问题原因:AndroidStudio2.4默认工具链通过执行字节码转换,称为实现了新的语言特性desugar,在输出 javac编译器。Jack不再需要.所以只要把jackOptions的配置去掉即可 另外现在默认支持lambda 而不需要在配置retroLambda官网说明点击","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Android Studio","slug":"Android-Studio","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android-Studio/"}]},{"title":"Android Spinner值不显示,选择列表正常","slug":"Android Spinner值不显示,选择列表正常","date":"2017-04-06T08:07:36.000Z","updated":"2019-01-24T09:22:49.671Z","comments":true,"path":"2017/04/06/Android Spinner值不显示,选择列表正常/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/04/06/Android Spinner值不显示,选择列表正常/","excerpt":"本文主要说Android Spinner选择列表正常但是值不正常显示的问题","text":"本文主要说Android Spinner选择列表正常但是值不正常显示的问题 Android Spinner值不显示,选择列表正常 1.项目开发过程中,只有小米的手机出现这个问题 2.我尝试使用AppCompatSpinner问题仍然没有解决 结论:,其他没有遇到过,可能miui自己对Spinner的样式做了修改 ##正文 一.先看结果1.先看不显示的效果图 这是预览的 选中值却没有显示出来的效果 这是预览的 选中值却没有显示出来的效果 2.显示正常的效果图 这里写图片描述 二.分析及过程1.分析考虑到其他手机能显示手上这部小米4不显示,可能是因为Spinner分配的空间不够显示,然后我尝试将三个Spinner宽度上1:1:1 的宽度全都改为wrap_content,并且将小标题的”配送地址”的宽度也调小了,最终测试的结果是 他们任然不显示.只有最后一个Spinner显示了,But 只显示了一个字,后面都是….. 这样来看的话说明我们用系统的android.R.layout.simple_spinner_item 样式的问题了,这个Spinner被miui修改过了 ,它要占用更大的空间才能显示,那么问题来了,我们只有这么大的空间,怎么处理呢?答案就是 使用自定义的样式,把Spinner内部负责显示的TextView的TextSize调小 文件: R.layout.simple_spinner_item 123456789<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="marquee" android:singleLine="true" android:textAlignment="inherit" android:textSize="16sp"/> 这样在初始化Adapter的时候将1mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mList); 变成1mAdapter = new ArrayAdapter<String>(this, R.layout.simple_spinner_item, mList); 使用本地的布局文件修改textsize=”16sp” 编译运行,结果妥妥的!!!","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Spinner","slug":"Spinner","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Spinner/"}]},{"title":"图片过大导致Activity启动慢的问题","slug":"图片过大导致Activity启动慢的问题","date":"2017-03-17T08:07:36.000Z","updated":"2019-01-24T09:06:29.369Z","comments":true,"path":"2017/03/17/图片过大导致Activity启动慢的问题/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/03/17/图片过大导致Activity启动慢的问题/","excerpt":"本文主要说图片过大导致Activity启动慢的问题","text":"本文主要说图片过大导致Activity启动慢的问题 Activity启动慢的问题前言:今天写个demo 无意间发现布居中只有两个常规的控件但是Activity的启动时间达到惊人的 5s184ms 如下103-15 18:15:39.578 1093-1120/? I/ActivityManager: Displayed com.yasin.androidndemo/.activity.AnimationActivity: +5s184ms 然后我就不淡定了,这是什么鬼?我决定找到元凶并解决它! 1.定位问题 1.首先把Activity中初始化的东西全部注释 结果:运行时间并没有什么大变化 所以排除代码的问题 2.注释父控件布局内的所有控件 结果:时间变成AnimationActivity: +183ms 启动时间锐减啊,这说明启动慢是控件的问题 3.一层一层放开注释 最终发现影响启动时间的是一个ImageView这是我突然想到 不会是==图片过大==吧!!! 打开图片一看 还真是 400*386 的分辨率2.解决问题找到问题就好解决了这两有两种方案 1 .通过代码压缩图片显示这样方式就不在ImageView中设置图片 在代码中通过压缩图片大小来显示图片(一种是自己写BitmapFactory 调整inSampleSize ,另一种是直接使用图片加载框架glide等) 注 :这个方式写起来简单,但是扩展性不强 2 .自定义View写一个继承ImageView的View 在初始化中处理加载图片逻辑现在来实现下这个过程 1)在values的 attrs.xml中创建一个属性集合ImageViewNative 和一个xml属性srcNative,并指定类型integer(也就是图片的类型在项目中是id)如下: 123 <declare-styleable name="ImageViewNative"> <attr name="srcNative" format="integer"/></declare-styleable> 2)在View的构造方法中解析相应标签并做处理 1234567891011121314151617181920212223242526public class ImageViewNative extends AppCompatImageView {ImageViewNative aNative;public ImageViewNative(Context context) { super(context);}public ImageViewNative(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs);}public ImageViewNative(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs);}private void init(Context context, AttributeSet attrs) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ImageViewNative); if (!isInEditMode()) { int img = array.getResourceId(R.styleable.ImageViewNative_srcNative, 0); array.recycle(); if (img != 0) GlideUtils.displayNative(this, img); }}} 注: GlideUtils.displayNative(this, img); 是一段Glide图片加载的封装,你可以使用其他方式替代 3)在布局中使用自定义属性 1234567<com.yasin.androidndemo.view.ImageViewNativeandroid:id="@+id/tv_logo"android:layout_width="100dp"android:layout_height="100dp"android:layout_marginBottom="@dimen/margin_30dp"app:srcNative="@mipmap/ic_logo"/> 使用自定义属性需要在布局文件中添加schemas声明 1xmlns:app="http://schemas.android.com/apk/res-auto" 自定义View可以在自定义的View中处理图片而不用去每个类中修改代码,另外它的扩展性比较强 如果不需要设置圆角等其他定制的话可以方便添加 最后使用新的方案加载结果为 .activity.AnimationActivity: +264ms 效果很明显","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Image","slug":"Image","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Image/"}]},{"title":"Android 开发常见错误及分析解决","slug":"Android 开发常见错误及分析解决","date":"2017-03-16T08:58:36.000Z","updated":"2019-01-24T09:11:15.838Z","comments":true,"path":"2017/03/16/Android 开发常见错误及分析解决/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/03/16/Android 开发常见错误及分析解决/","excerpt":"本文主要说Android 开发常见错误及分析解决","text":"本文主要说Android 开发常见错误及分析解决 1.android.content.res.Resources$NotFoundException: Resource ID #0x0 错误原因:系统找不到资源ID. 常见错误是: TextView中setText(value) 方法中使用了int类型的值,系统误以为是对应的R文件中的资源文件,结果找不到而报错. 样式文件更新 找不到对应的属性 例如:style中的界面theme 的item name=alertDialogStyle 之前版本是好的 更新到新的版本 需要改成 name= android:alertDilogTheme 2. 打包提示这个错误。Error:A problem was found with the configuration of task ‘:app:packageRelease’ 错误原因:shrinkResources true 表示打包时删除无用的资源文件 解决方法:shrinkResources false 3. fatal: refusing to merge unrelated histories 错误原因:默认拒绝合并没有关系的历史记录 处理方法:强制允许合并,使用如下代码 1git pull origin branchname --allow-unrelated-histories","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"Bug","slug":"Bug","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Bug/"},{"name":"Exception","slug":"Exception","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Exception/"}]},{"title":"Hello World,I'm going to start GitPages","slug":"hello-world","date":"2017-03-15T08:07:36.000Z","updated":"2019-04-17T02:51:05.234Z","comments":true,"path":"2017/03/15/hello-world/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/03/15/hello-world/","excerpt":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy ConclusionLocal Test123hexo cleanhexo generate #生成hexo server #打开服务 or 1hexo s -g #生成 启动本地服务,进行文章预览调试 Deploy Step123hexo cleanhexo generate #生成hexo deploy #部署 or1hexo d -g Add New MD Fileadd file in ```12in file title add this tag layout: androidtitle: View 的事件体系date: 2019-01-17 16:07:36 tags: android,view` More info: Deployment","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"View","slug":"View","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/View/"}]},{"title":"迁移到AndroidX","slug":"迁移到AndroidX","date":"2017-01-15T08:07:36.000Z","updated":"2019-01-24T09:54:51.561Z","comments":true,"path":"2017/01/15/迁移到AndroidX/","link":"","permalink":"https://www.jianshu.com/u/3e759a2c717f/2017/01/15/迁移到AndroidX/","excerpt":"本文主要说迁移到AndroidX的方法和可能遇到的问题","text":"本文主要说迁移到AndroidX的方法和可能遇到的问题 1.迁移的方式使用Android Studio 3.2及更高版本,您可以通过从菜单栏中选择Refactor> Migrate to AndroidX,快速迁移现有项目以使用AndroidX。注意 :如果您有任何尚未迁移到AndroidX名称空间的Maven依赖项,那么当您在gradle.properties文件中将以下两个标志设置为true时,Android Studio构建系统也会为您迁移这些依赖项:12android.useAndroidX=trueandroid.enableJetifier=true 这两个标签会在通过菜单栏设置后自动生成,若手动加入或导致一些依赖包名不能被修改成新对应的包名 2.详细的对应AndroidX的包名更换列表点击查看 3.迁移到AndroidX可存在的问题 1.先设置标签12android.useAndroidX=trueandroid.enableJetifier=true 导致编译过的一些组建被自动识别成AndroidX的导致不能被转成AndroidX对应的路径 2.使用工具后ConstraintLayout的地址默认会转出错使用Android Studio的工具转的路径为1androidx.constraintlayout.ConstraintLayout 实际正确的路径为1androidx.constraintlayout.widget.ConstraintLayout 这个后期插件升级应该会修复 3.design包对应的库没有被转换这个地方是应为design转换后并不是androidx开头的路径,而是1com.google.android.material.xxx 的路径,插件没有适配这种情况;这个也得等待插件升级。","categories":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/Android/"},{"name":"AndroidX","slug":"AndroidX","permalink":"https://www.jianshu.com/u/3e759a2c717f/tags/AndroidX/"}]}]}