面试技能点整理

1. 熟悉PicassoGlideUniversal-Image-Loader等主流的图片加载处理框架,了解三者的优缺点,能够针对不同的情况将合适的框架运用到实际开发当中。

Picasso是一款开源的比较早的图片加载类库,支持加载多种来源的图片,比如网络,sd卡,res资源;可以设置占位图,也支持对图片的自定义处理;另外在回收imageview时,如果imageview中有下载进程,它会将这个下载进程给取消掉;

Glide是一款专注于处理平滑滑动的图片加载类库,底层默认使用httpURLconnection来加载图片,当然也可以设置为okhttp或volley;

Glide与Picasso相比,Glide可以加载GIF动态图,并且由于glide的缓存默认是根据imageview控件的宽高进行的,并不像Picasso默认加载整个图片,因此glide加载图片的速度要优于Picasso;但由于glide默认的Bitmap格式是RGB_565,所以在图片的质量上要比Picasso差一些(但单凭肉眼难以分辨) ---gifVideo,如果你们是做美拍、爱拍这种视频类应用,建议使用glide,如果对app大小很在意可以考虑Picasso

Universal-Image-Loader拥有丰富的配置项,如线程池,下载器,缓存策略,缓存文件名称生成器等,支持加载多种来源的图片,如sd卡,网络,uri,asset,res目录,同时它也支持给加载图片添加动画和圆角图片;使用时需要先初始化Universal-Image-loader,一般在application的onCreate中初始化,并配置一下options;配置完之后使用起来十分方便简单,这也是我个人比较喜欢的一个类库.

2. 熟悉iconVolleyRetrofit等主流的网络加载框架,了解三者的优缺点能够对其进行相应的封装,针对不同的情况将合适的框架运用到实际开发当中。

ion一个异步的网格请求框架与图片加载框架,底层是通过socket来实现的,其中在它的核心对象ion中封装了子线程与http的请求细节,向服务端发送请求后在它的FutureCallback回调中接收服务端返回的数据,在onCompleted方法中根据接收的数据进行相应的处理;

Volley是在13年Google I/O大会上推出的一款网络请求和图片加载框架,内部使用HttpURLConnection和HttpClient进行网络请求,只是在底层对于不同的Android版本进行了响应的切换,2.3之前使用的HttpClient,2.3之后使用的是HttpURLConnection

非常适合那种数据量不大但是通信频繁的网络请求,但它对于大数据量的操作,如文本下载,表现则没有那么好,同时它本身也没有实现图片的三级缓存;此外, 当用户finish当前的Activity的时候,而当前Activity正在进行网络请求,Volley支持取消网络请求,这样可以减少内存泄漏,减少用户流量的消耗(写在onDestroy方法中);

创建请求队列RequestQueue-->根据返回值决定使用哪个请求类去进行网络请求-->在listener回调中接收请求成功后返回的数据,在errorlistener中接收请求失败或异常信息-->最后将请求添加到请求队列中发送请求; 当然通常还要对volley进行一定封装后再进行使用;

Retrofit相对来说是性能最好的一款网络请求框架,和我们之前的所接触的框架不同,它通过使用注解配置请求相关的参数,减少了代码量,但相对其他框架来说有点难理解;它底层网络请求默认采用okhttp,当然也可以和volley这些主流框架进行切换

3. 熟悉LruCache以及线程池的底层实现原理。---实战技巧

LruCache是一个实现图片内存缓存的类,采用Lru删除最近最少使用的算法;使用它必须实现sizeOf方法,用来指定每条数据的size,(此处是返回bitmap的大小)它内部通过一个按照访问顺序排序的LinkedHashMap来存储数据,每次缓存命中的时候,会将该条数据移到上方,并会判断当前缓存size是否超出了最大缓存maxSize(谷歌推荐的是app可用内存的1/8),如果超出则移除最下方也就是最少使用的数据;

least recentlly use 最少最近使用算法会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定;

如果问你加载图片的原理:

在加载图片中使用了三级缓存,在处理图片内部缓存的时候采用的是LRUcache,在处理图片加载任务的时候采用线程池来管理;然后再将这三方面扩展一下讲

三级缓存:首次加载app时,通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中(为了避免内存溢出的问题,需要对图片进行压缩理,inSampleSize),之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片,将图片保存到本地时图片名用MD5加密来实现(在没退出app前若想要加载最新数据,则通过上拉刷新下拉加载手动来获取)

 

4. 熟悉Android中常见的内存泄露的引发原因以及内存优化的方案。

内存溢出: 你要求分配的内存也就是你需要的内存,超出了()可用的最大内存

内存泄漏: 简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有占据着内存但却不再被再次使用,导致 GC 不能回收。

由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。比如:如果你在单例模式中传入一个context上下文,如果这个这个上下文是application的上下文还好,因为它和应用的生命周期一样,不会导致内存溢出;但如果传入的是一个activity的上下文,那么当这个activity被销毁时,由于这个context的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以它的内存并不会被回收,这就造成泄漏了。--因此上下文要使用application的上下文

在开启activity或显示dialog时须用activity的上下文,其余什么上下文均可!!!

 

 

 

非静态内部类创建静态实例造成的内存泄漏:

静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法),只能访问外部类的静态成员(包括静态变量和静态方法)

有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法:

        public class MainActivity extends AppCompatActivity {

        private static TestResource mResource = null;

        @Override

        protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        if(mManager == null){

        mManager = new TestResource();

        }

        //...

        }

        class TestResource {

        //...

        }

        }

这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:

将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。

资源未关闭造成的内存泄漏:

对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

Handler引起的内存泄漏:

Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    }
}

在上面的代码中当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用,Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)或延迟操作一起出现,所以如果用户在网络请求过程中延时消息得到处理前关闭了Activity,那么因为这条消息持有对handler的引用,而handler又持有对其外部类(在这里,即SampleActivity)的潜在引用。这条引用关系会一直保持 直到网络请求完毕或延迟消息得到处理,这阻止了SampleActivity被垃圾回收器回收,同时造成应用程序的泄漏。

Handler内存泄漏的解决方案:

方法一:当销毁activity的时候,activityonStoponDestroy方法中调用Handler的removeCallbacks(Runnable)removeMessages(int what)或直接mHandler.removeCallbacksAndMessages(null);方法,把消息对象从消息队列移除就行了

方法二:Handler声明为静态类,同时通过弱引用的方式引入 Activity

 

 

加载图片不经压缩处理直接加载大图,Listview 如果不复用缓存或通过viewholder来减少findviewbyId的操作在很大程度上都可能早成内存溢出;

避免OOM(内存溢出):

A.避免使用枚举

B.Bitmap对象是很消耗内存的,所以我们在加载图片的时候要通过 inSampleSize为图片设置一个合适的缩放比,避免直接加载大图;解码格式如果没有什么特殊需求,应使用RGB565而不是ARGB8888

C.在对图片进行压缩处理的同时,还要提高复用性,如使用LRUCache 内存缓存,只保留最近最常使用图片

D.另外在一些会被频繁调用的方法(值动画的监听方法,onDraw方法,getView方法)中,要尽量避免在其中new对象,这样会迅速增加内存的使用

5. 熟悉Handler消息传递机制的应用和原理,能够通过Handler来实现线程间的通讯。

因为在主线程队列中不能处理耗时操作,超过5秒就会报ANR异常,所以通常通过在子线程中处理耗时操作,将结果通过消息发送给handler,handler在主线程中接收到消息,就会通过handlerMessage方法进行相应的处理;

这时handler使用的一个大致流程,其实当系统跑起来的时候,在主线程中就会创建出MessageQueue Looper对象,这也是我们使用handler的时候不用手动创建MessageQueue Looper的原因,然后为了保证一个线程只对应一个loop对象,一个loop只对应一个MessageQueue队列,系统在创建loop的时候会通过ThreadLocal(线程级单例)把它和当前线程绑定在一起,同样创建messagequeue的时候会通过一个final类型的成员变量将其保存起来,这样就实现了上面所说的;

通过 Looper.loop() 方法去取消息,在这个方法中设置了一个死循环来让looper不断的去消息队列中查看有没有消息,如果有的话,就将消息取出来,然后会调用handler 的dispatchMessage方法来分发消息,因为message中可能有回调,handler在创建的的时候也可能会给它传一个callback,因此在这个方法中会做一个判断,判断handler和message在构造的时候是否传入了callback,只有当这两个回调都为空的时候,才会执行handMessage方法,去处理消息;否则则交给回调函数处理;

Loop中死循环不会对主线程造成影响,因为对于主线程来说我们肯定不希望,它运行一段时间,执行完一段代码后程序就自己退出了,这样会造成很不好的用户体验,我们肯定希望它可以一直活着,而loop中的死循环便能保证主线程不会被退出,无消息时会挂起休眠,当收到消息的时候又会被重新唤醒;

下面可不用说:

向消息队列发送消息 handler sendMessageAtTime()-> messqueue.enqueuemessage() 消息排序(通过消息要执行的时间进行排序)  Message.next()  有消息需要马上执行 调用nativewake()->把Looper.looper()  唤醒 queue.next()开始工作````

6. 熟悉Android中通讯的实现,包括四大组件之间的通讯、fragment之间的通讯、进程间的通讯以及线程间的通讯。

线程间通信: Handler

进程间通信: Activity可以跨进程调用其他应用程序的Activity,比如调用系统打电话,发短信的进程;Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播,收到广播后,可以做出相应的操作,是一种被动的通信方式;Content Provider可以把应用的私有数据共享给其他应用访问,所以可以实现进程间通信;

AIDLAndroid Interface Define Language),是android的一种接口定义语言,可以定义接口,使得客户端和服务端之间实现进程间通信

 

Activityfragment之间的通讯:

Fragment可以调用getActivity()方法很容易的得到它所在的activity的对象,然后就可以查找activity中的控件们(findViewById())。例如:

ViewlistView =getActivity().findViewById(R.id.list);同样的,activity也可以通过FragmentManager的方法查找它所包含的frament们。例如:

[java] view plain copy

1. ExampleFragment fragment =(ExampleFragment)getFragmentManager().findFragmentById(R.id.example_fragment  

有时,你可能需要fragment与activity共享事件。一个好办法是在fragment中定义一个回调接口,然后在activity中实现之或者在fragment中定义含参的set,get方法,在activity中传入参数调用方法;也可以反过来;

 

7. 熟悉Android中的Json的解析,熟悉Gson框架的使用并对XML的解析及XStream框架的使用有一定的了解

 

8. 熟悉Android中的事件分发机制,熟悉常见的事件冲突解决方案

 

 

 

9. 熟悉常用的UI框架,能够熟练的完成UI界面的搭建。

 

10. 屏幕适配 

 

于五种主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)应按照 2:3:4:6:8 的比例进行缩放。例如,一个启动图标的尺寸为48x48 dp,这表示在 MDPI 的屏幕上其实际尺寸应为 48x48 px,在 HDPI 的屏幕上其实际大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的屏幕上其实际大小是 MDPI 的 2 倍 (96x96 px),依此类推。

11. 熟悉Android原生的SwipeRefreshLayout控件和第三方的PullToRefresh框架,来实现对ListViewRecyclerView的下拉刷新和上拉加载功能的实现。

 

12. 熟悉Android中自定义控件,自定义动画的实现(一定程度上),能够根据具体的要求来实现相应的控件效果。

 

13. 熟悉WebView的使用。 

 

14. 熟悉APK的打包、加密以及上市流程。

 

15. 熟悉推送、登陆、支付等常见的第三方sdk的使用。

 

16. 熟悉常用的版本控制工具。

 

17. 熟悉常见的加密方式(如:RSA、 MD5、 DES,AES、 SHA1、Base64 等)

URL编码:http协议中请求的url不支持中文和特殊字符(如&?),所以需要对url进行编码和解码,编码使用的是URLEncoder,解码使用的是

Base64:可以将二进制数据(图片视频都是以二进制的形式存储)对象转为字符串(文本格式),主要用于在http传输中将一些比较大的比如二进制数据,编码为字符串,适合放入url进行传递,而且具有非明文行

sharedpreference本身只能够存储基本数据类型,不能存储自定义对象,但我们可以将对象进行base64编码后,间接存储到sharedpreference中,取出时再对其解码

数字摘要:是指通过算法将长数据变为短数据,通常用来标识数据的唯一性,以及数据是否被修改,常用的加密算法有md5和sha1,如安卓app的签名也是用这2种算法计算的;

md5由于具有不可逆性,也被用来作为密码加密,并且通常情况下为了让加密过程变的不可预测,我们会进行加盐操作

sha1也不可逆,比md5长度更长,所以更安全,但是加密的效率比md5要慢一些,如文件的秒传功能,(比如你想给你好友传一文件,上传时会系统会匹配本地文件的sha1值,是否和服务器上的某个文件相同,就是服务器是是否有相同的文件,有就直接从服务器发给你好友,就不用你在上传了),以及相同的v4包冲突都是根据文件的sha1值进行比对的(sha1值不同报冲突,相同就不报了)

· 

加密和解密,一般分为对称加密和非对称加密

· 

· 

对称加密:

· 

密钥可以自己指定,只有一把密钥,如果密钥暴露,文件就会被暴露,

常见对称加密算法有DES算法(Data Encryption Standard)和AES算法(Advanced Encryption Standard),AES比DES安全一点,相对的效率要低一点

特点是加密速度很快,但是缺点是安全性较低,因为只要密钥暴漏,数据就可以被解密了。

· 非对称加密:有两把钥匙(密钥对),公钥和私钥,公钥的话给别人,私钥自己保存

常见非对称加密算法是RSA

2把密钥通常是通过程序生成,不能自己指定

特点是加密速度慢些,但是安全系数很高

加密和解密的规则是:公钥加密只能私钥解密,私钥加密只能公钥解密,只能两者配合使用

应用场景举例:在集成支付宝支付sdk时,需要生成私钥和公钥,公钥需要设置到支付宝网站的管理后台,在程序中调用支付接口的时候,使用我们自己的私钥进行加密,这样支付宝由于有公钥可以解密,其他人即时劫持了数据,但是没有公钥,也无法解密。

· 代码实践,使用hmAndroidUtils中的工具类进行操作

 

 

 

18. 熟悉android 5.0 和android 6.0的新特性,并可以运用到项目中

 

18. 熟练掌握Android中的常见机制,比如说事件分发和拦截机制、Handler机制等等

 

19. 熟练掌握自定义控件,能实现常用的UI效果

 

20. 熟练掌握代码复用基类的抽取优化代码结构提高运行效率

 

21. 了解JNI开发的基本流程

 

22. 了解常用的设计模式,如工厂模式、观察者模式、模版模式等。

23. shareSDK分享

登录官网--》进入后台,添加应用,生成appk--》下载SDK,可以选择【】要集成的平台--》解压压缩包,打开快速集成的jar文件,--》弹出一个对话框,输入你你的项目名称,和你应用的包名(需要到项目清单文件中去复制),以及选择要集成的平台--》生成一个文件夹--》将文件夹中的内容全部copy到项目中--》根据集成文档,配置清单文件、activity(直接copy集成文档中的相应内容就好)--》用之前生成的appk,将项目assets目录下的shareSDK.xml中的sharedSDK节点下的appk给替换掉,其他的appk不用替换--》在代码中给分享按钮设置点击事件,将集成文档中,分享的代码copy过来直接用就可以

24. 极光推送 ---day06

消息推送

· 1.消息拉取,并不是真正的消息推送,它是通过一个死循环,设置计时器,每隔一段时间,就向服务器发送请求,如果有最新更新的信息,服务器就返回给客户端,虽然能实现类似消息推送的效果,但一直执行会费电,费流量,并且得到的最新消息有一定的延时,导致消息不及时(和服务器建立的是无状态的链接,请求完成后,链接就断开了)

· 2.真正的消息推送: 客户端会与服务器建立一个长连接,但如果是用数据链接的网络的话,当连上基站后,它一般会判断,如果5分钟以内没有数据,基站就会回收掉你占用的端口,这时长连接就会断掉;因此通常需要通过心跳(隔一段时间随便发送一个或两个字节的数据包)来维持长连接;服务器中会有一个集合,保存了所有与之建立长连接的客户端的IP,当要推送消息的时候,服务端,就会遍历集合将消息发送给所有客户端

· 极光推送:客户端先与极光推送的服务器建立长连接-->服务器端通过极光推送提供的API将消息发送给极光推送的服务器-->极光推送的服务器再将消息转发给客户端

· 即时通讯:两个想要相互通信的客户端都需要通过心跳与服务端建立长连接,然后发送消息的通过from 或 to 来标识发送方或接收方,from 代表发送消息的人,也就是自己; to 代表接收消息的人,也就是要发送给谁; 服务端受到消息后,根据from 或 to ,遍历集合(集合里保存了所有与之建立长连接的客户端的IP),将收到的相应的消息转发给 to 所指定的IP;如果 to 所指定的IP 不在线,则服务端会暂时缓存要转发的信息

· 视频会议: a先给服务器发一个包,标明邀请谁来参加视频会议,服务器,遍历集合,找到相应的IP,如果b,c 在线就将消息发送过去;b,c,点击接受后,a 就会将视频发给服务器,服务器再将视频转发给b,c

步骤:

进入官网--.>创建应用,指定应用包名(和清单文件保持一致)-->文档,资源下载,下载SDK--》解压压缩包,里面提供了一个例子,打开examplexml文件--》根据它将项目的清单文件进行相应的替换--》根据中文提示,在适当位置将包名和appk替换为项目包名和之前生成的appk--》到解压后的文件夹的lib目录下将armx86的文件夹,以及jpush-android.jar,  copy到项目的libs文件夹下(如果要还需要适配其他的CPU类型,再将其他相应的文件夹copy过来即可)-->创建一个广播,用来接收信息,将广播配置到清单文件的server标签中--》初始化API,在application类的oncreate方法中,将集成文档中的初始化代码copy过来--》进入个人后台,选中应用,点击推送--》在推送界面就可以模拟一下服务端向客户端推送消息--》客户端收到推送的消息后,会在屏幕的下拉条中展示,当用户点击该消息的时候,触发广播-->进而执行广播中的代码,根据集成文档中的高级功能部分,查看怎样解析intent获取推送的消息

 

25. 了解主流的架构模式,了解MVPMVCMVVM等模式实际项目中的应用

26. 轮播图红点的移动

27. 一开始就通过slidingmenu将界面分为contentfragmentleftfragment两部分,在contentfragment中底部使用radiogroup,上面是一个填充剩下布局的viewpager,给viewpager设置适配器展示数据,展示的数是一个个pager,contentfragment中的inITview负责加载要展示界面的布局(界面公共的部分),initdata负责加载将要展示 的界面pager添加到集合中,通过viewpager的适配器进行展示,其中pagerinITview的加载的是标题栏(直接在父类中实现了),子类的pager中只需要加载要展示界面的数据即可initdata

 

 通过drawlayout来布局,在mainactivity中找到viewpager,给viewpager设置适配器,适配器中展示的数据是一个个fragment,适配器继承fragmentpageradaptergetCountgetItemgetPageTitle),在fragment的基类中通过loadpager来加载界面和数据,对外提供inITviewinitdata的方法,所以之后每个具体的fragment只要实现这两个方法就好了,通过再次设置适配器来展示initdata加载的数据

 

多种条目布局:(对谷歌市场中的多条目布局进行适当的修改)

服务端传数据的时候,要传入该条目的类型type;客户端接收到数据后,编写been,遍历从服务器获取的数据,分别将获取条目类型type和条目内容添加到一个新建的集合中;展示数据时,通过position获取集合中每个位置的数据,并通过switch和之前代码中定义的条目类型进行比对,匹配后返回对应的条目类型,根据条目类型来加载不同的布局

重写baseAdaptergetViewCountgetItemViewType方法

 

 

谷歌市场热门界面,有自定义布局和动画,详情界面值得一看;

 

 

/**

 * 线程池管理的封装:

 * 精通java线程池的使用

 *  1.java还提供了封装好的方便我们创建线程池的类和方法:

 *    Executors.newFixedThreadPool(nThreads);

 *    Executors.newCachedThreadPool(threadFactory);

 *    Executors.newSingleThreadExecutor();

 *  2.我们也可以自定义线程池的配置,使用ThreadPoolExecutor自定义,如下:

 */

public class ThreadPoolManager {

private int corePoolSize;//核心线程池的大小,就是指能够同时执行的任务数量

private int maximumPoolSize;//最大线程池的大小,线程池数量的上限

private int keepAliveTime = 1;//存活时间

private TimeUnit unit = TimeUnit.HOURS;//时间单位

private ThreadPoolExecutor threadPoolExecutor;

private static ThreadPoolManager threadPoolManager = new ThreadPoolManager();

public static ThreadPoolManager getInstance() {

return threadPoolManager;

}

//通过构造函数进行初始化

private ThreadPoolManager() {

//计算核心线程池的大小:设备的可用处理器核心数*2 + 1,该数量的线程能够让cpu的效率得到最大发挥

corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;

maximumPoolSize = corePoolSize; //最大线程池数量不要小于核心线程池数量

//线程池的处理机制:优先核心线程池->缓冲队列->最大线程池->拒绝线程的策略

threadPoolExecutor = new ThreadPoolExecutor(

corePoolSize, //3

maximumPoolSize,//10,当缓冲队列满的时候,会判断最大线程池 ,注意:最大线程池的大小是包含核心线程池大小的

keepAliveTime, //表示的最大线程池中等待任务的存活时间

unit,

new LinkedBlockingQueue(5), //缓冲队列,当核心线程池满的时候,会将任务存放到缓冲队列里等着

Executors.defaultThreadFactory(), //创建线程的工厂

new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝线程的处理策略,CallerRunsPolic将当前线程移除

}

  

/**

 * 添加任务

 */

public void execute(Runnable runnable) {

if (runnable != null) {

threadPoolExecutor.execute(runnable);

}

}

/**

 * 将任务从线程池中移除,如果任务正在执行,会先终止线程,再移除

 */

public void remove(Runnable runnable) {

if (runnable != null) {

threadPoolExecutor.remove(runnable);

}

}

 

 

 

 

//请求网络获取数据

getDataFromServer();

/**

 * 适配器不能再这里设置,因为getDataFromServer中包含请求网络的操作,异步操作,导致设置适配器的代码和getDataFromServer

 * 平级,所以适配器中需要的数据,getDataFromServer还没有返回,-->导致空指针异常!!!

 */

/*//展示轮播图(图片),为viewpager设置适配器

mViewPager.setAdapter(new Myadapter());*/

 

轮播图实现:

布局:最外层套一个相对布局,指定高度--》放一个viewpager,和外层布局等高--》再放一个相对布局--》在最下方的左边放一个图片描述信息--》在最下方的右边放一个线性布局,当做一个容器,用来放点;

或者在先对布局中设置好容器和红点的位置(右下方),然后先放容器,在放imageview红点,让红点覆盖 可以覆盖掉容器中的一个白点;

代码实现:添加点时要么采用引导动画的方式,有几张图片就通过创建view,创建几个点,点的背景图片直接设置为白点,通过:

LinearLayout.LayoutParams params = new LayoutParams(5, 5);//设置点点宽高

params.rightMargin = 5;//设置点的右边距

view.setLayoutParams(params);//将设置好的点的属性值赋给bai

 

来设置点的宽高等属性,然后添加到linearlayout容器中;--viewPager设置适配器展示图片--》设置OnPageChangeListener,在onPageScrolled 当界面切换时,计算红点的移动距离,然后先获取红点的属性,在将移动距离设置给红点来实现移动:

// 2.拿到红点当前的的属性

RelativeLayout.LayoutParams params =(RelativeLayout.LayoutParams)mRedPoint

.getLayoutParams();

// 3.重新设置(修改)该属性值

params.leftMargin =redPixels;

// 4.将修改后的属性值重新设置给红点,来实现红点的移动

mRedPoint.setLayoutParams(params);

要么,在添加点时,点的背景图片设置为状态选择器,通过setenable来设置显示红点还是白点--》为viewpager设置适配器展示图片,在container.addView(imageView);之后设置图片的触摸监听,根据图片的触摸状态判断是否继续轮播图片OnTouchListener):

//设置图片的触摸监听,根据图片的触摸状态判断是否继续轮播图片

private class MyOnTouchListenerimplements OnTouchListener {

@Override

public boolean onTouch(Viewv, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

//当按下图片的时候需要停止轮播,即删除已经发送的消息

hd.removeCallbacksAndMessages(null);//删除发送的消息

break;

case MotionEvent.ACTION_UP:

//当手指抬起的时候,继续轮播图片,即发送消息,继续切换下一张图片

hd.sendMessageDelayed(Message.obtain(), 2000);//发送延时信息

break;

case MotionEvent.ACTION_CANCEL:

//当事件取消的时候(按下控件后,不抬起,移出控件),继续轮播图片

hd.sendMessageDelayed(Message.obtain(), 2000);//发送延时信息

break;

default:

break;

}

// 返回false 孩子不处理事件 父容器就不传递其他的事件了,也就无法得到抬起或取消的事件,因此须返回true,表示孩子处理事件

return true;

}

--》因为轮播图上有相应的图片描述,所以为了根据图片显示出相应的描述信息,需要对viewpager设置界面滑动监听,onPagechangeListener ,在它的onPageSelected中,当前界面被选中,切换完成时,设置相应图片描述信息,并将之前的红点变白点,当前的白点变红点,并记录当前点:

@Override

public void onPageSelected(int position) {

//显示轮播图片的描述信息

mIconInfo.setText(tabDetailBeen.data.topnews.get(position).title);

//切换到该界面的时候先将之间的红点变成白点

mLayout.getChildAt(preRedPoint).setEnabled(false);

//切换到该界面后将该界面的点变红

mLayout.getChildAt(position).setEnabled(true);

//记录前一个红点的位置

preRedPoint = position;

}

--》最后发送消息切换轮播图:

//发送消息,切换轮播图

//因为当缓存或访问网络的时候都要发送消息,所以为了确保只发送一个消息,需要先清空之前未发送的消息

if (hd ==null) {

hd = new MyHandler();

}

//清空之前的还为来得及发送的消息

hd.removeCallbacksAndMessages(null);//null: 删除之前发送的所有消息,填入具体的消息则删除指定的消息

//发送延时消息

hd.sendMessageDelayed(Message.obtain(), 2000);

Handler中实现无限循环播放:

private class MyHandler extends Handler {

@Override

public void handleMessage(Messagemsg) {

//解决内存泄漏,判断当前ViewPager是否存在界面上,如果不显示就不再发消息了

if (mViewPager.getWindowVisibility() == View.GONE) {

hd.removeCallbacksAndMessages(null);

return;

}

//切换到最后一页时,需要重新切回第一页,所以对当前的mViewPager.getCurrentItem() + 1取模

int newIndex = (mViewPager.getCurrentItem() + 1) %tabDetailBeen.data.topnews.size();

//接收消息,切换轮播图,当即将切换到第一页的时候,不要切换动画

if (newIndex == 0) {

mViewPager.setCurrentItem(newIndex,false);//false: 不要切换动画

}else {

mViewPager.setCurrentItem(newIndex);

}

//再次发送消息,实现无限循环(即使不动也要发消息,使轮播图继续)

hd.sendMessageDelayed(Message.obtain(), 2000);

}

viewpager的事件处理:

public TabDetailVeiwPager(Contextcontext, AttributeSet attrs) {

super(context,attrs);

}

/**

 * 1、不处理上下滑动,让父容器拦截,getParent().requestDisallowInterceptTouchEvent(false);

 * 2、左右滑动自己处理事件,不让父容器拦截事件

 * 2.1、第1页时,且手指从左往右,滑动到最后一页

 * 2.2、最后一页时,且手指从右往左,滑动到第一页

getParent().requestDisallowInterceptTouchEvent(true);

 *

 */

@Override

public boolean dispatchTouchEvent(MotionEventev) {

//请求父元素不拦截事件

getParent().requestDisallowInterceptTouchEvent(true);

// 1.不处理上下滑动

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

downX = (int)ev.getX();

downY = (int)ev.getY();

//因为在安卓中像viewgroup这些控件拦截的都是move事件,至少会将down事件传下来,

//所以为了保证move事件可以执行,在down事件中应请求父控件不要拦截事件

getParent().requestDisallowInterceptTouchEvent(true);

break;

case MotionEvent.ACTION_MOVE:

int moveX = (int)ev.getX();

int moveY = (int)ev.getY();

//获取移动的X,Y的距离,需要去绝对值!!!!!

int distanceX = Math.abs(moveX -downX);

int distanceY = Math.abs(moveY -downY);

if (distanceY >distanceX) {

//请求父控件拦截事件(针对的是上下滑动)

getParent().requestDisallowInterceptTouchEvent(false);

}else {

//左右滑动

//Int  currentItem = -1;

                //currentItem = getCurrentItem()+ 1 % getAdapter( ).getCount( );

// 2.1 如果是第一页,且由左向右滑动,当前页应变为最后一页

if (getCurrentItem() ==0 && (moveX -downX) > 0) {

  setCurrentItemgetAdapter(). getCount()- 1;

  //setCurrentItem(currentItem);

}else if (getCurrentItem() == getAdapter().getCount() - 1 && (moveX -downX) < 0 ) {

// 2.2 如果是最后一页,且由右向左滑动时,当前页应变为第一页 setCurrentItem0;

}

// 2.3 其他情况,则需要请求父控件不拦截事件,由孩子(轮播图viewPager来处理事件 getParent().requestDisallowInterceptTouchEvent(true);

}

break;

}

return super.dispatchTouchEvent(ev);

}

· 

4、Webview使用

/**

 * @Description: 初始化网页

 * @param:

 */

private void initWebView() {

    String url = getIntent().getStringExtra("url");

    System.out.println(url);

 

    //配置webview

    WebSettings settings = mWeb.getSettings();

    settings.setBuiltInZoomControls(true);// 支持缩放按钮

    settings.setUseWideViewPort(true);// 支持双击缩放

    settings.setJavaScriptEnabled(true);// 支持JavaScript

 

    //监听webview

    mWeb.setWebViewClient(new WebViewClient() {

        @Override

        public void onPageFinished(WebView view, String url) {

            // 网页加载完成后回调

            //网页加载完成后隐藏进度条

            mProgress.setVisibility(View.GONE);

            super.onPageFinished(view, url);

        }

    });

 

    //加载URL

    mWeb.loadUrl(url);

}

· 

6、更改Webview字体大小

private int currentIndex = 2;

private WebSettings settings;

private int tempIndex;  //临时变量

private void showTextsize() {

    AlertDialog.Builder builder = new Builder(this);

    String[] items = new String[]{"超大号字体","大号字体","正常字体","小号字体","超小号字体"};

    builder.setSingleChoiceItems(items, currentIndex, new OnClickListener() {

 

    @Override

    public void onClick(DialogInterface dialog, int which) {

        //将选择的字体的索引先保存在临时变量中,只有点击确定的时候才保存

        tempIndex = which;

    }

});

builder.setPositiveButton("确定", new OnClickListener() {

 

    @Override

    public void onClick(DialogInterface dialog, int which) {

        currentIndex = tempIndex;

        // 根据位置改变字体

        switchTextSize(currentIndex);

    }

});

builder.setNegativeButton("取消",null);

builder.show();

}

 

 

主线程向子线程发消息:(详见收藏)

--》创建一个类去继承Thread--》重写run方法--》在run方法里创建handler(子线程注意要手动创建looper对象和消息队列),调用handmessage方法处理收到的消息

--》主线程中创建该类的对象--》使用该对象通过其中的handler调用sendMessage方法发送消息

 

或者使用HandlerThread也可以,HandlerThread 继承自Thread,内部封装了Looper。

1. 创建一个HandlerThread,即创建了一个包含Looper的线程。

HandlerThread handlerThread = new HandlerThread("leochin.com");

   handlerThread.start(); //创建HandlerThread后一定要记start()

2. 获取HandlerThreadLooper

Looper looper = handlerThread.getLooper();

3. 创建Handler,通过Looper初始化

Handler handler = new Handler(looper);

通过以上三步我们就成功创建HandlerThread。通过handler发送消息,就会在子线程中执行。

如果想让HandlerThread退出,则需要调用handlerThread.quit();

 

reclyerView 的点击事件:

通过adapter中自己去提供回调的方式,adapter中定义接口OnItemClickLitener,并定义两个抽象方法onItemClick(View view, int position);

onItemLongClick(View view , int position);--》在adapter的onBindViewHolder方法中对接口进行回调

--》使用这种方式可以将这部分相同的代码抽取到父类中去,这样只写一次就可以了

当然如果逻辑很简单的话,也可以直接在adapter中对条目布局设置监听

在或者可以通过对reclyerView设置addOnItemTouchListener的方式,只是这种方式之前没用过,不是很了解

EventBus:

EventBus主要解决线程间的通信,组件(四大组件)之间的通信:

首先定义一个事件(可以是任何的javabean)--》谁想接受这个事件,就在它的任意一个方法上(生命周期方法除外)加一个注解@Subscribe,这样就创建了一个订阅者,可以接收发过来的消息(接收消息还要注册订阅者,一般在oncreate或onstart方法中注册,在ondestory方法中解除注册--》发消息,可以在你代码的任何一个地方执行发消息的代码

猜你喜欢

转载自blog.csdn.net/u013212407/article/details/52275984
今日推荐