Android自定义Spinner的例子
最近在做的项目中有很多下拉框,为了实现方便就用了Android 自带的Spinner,但是自带的Spinner的样式又不符合要求,就学习了一下自定义Spinner。下面是整个步骤:
1.准备好图片
2.style中定义
<!-- spinner --> <style name="spinner_style"> <item name="android:background">@drawable/spinner</item> <item name="android:paddingLeft">5dip</item>
3.调用
<Spinner android:id="@+id/field_item_spinner_content" style="@style/spinner_style" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
4.在layout中定义simple_spinner_item.xml
<?xml version="1.0" encoding="utf-8"?> <CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" android:paddingLeft="5dip" android:paddingRight="5dip" android:gravity="center_vertical" android:textColor="#808080" android:singleLine="true" android:layout_width="fill_parent" android:layout_height="wrap_content" />
5.java代码
ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext,R.layout.simple_spinner_item); String level[] = getResources().getStringArray(R.array.affair_level);//资源文件 for (int i = 0; i < level.length; i++) { adapter.add(level[i]); } adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter);
效果图:
本文主要 介绍Android 开发中的两个UI控件RecyclerView,CardView,及这两个控件的导入导出功能。
先上效果图:
原理图:
这是RecycleView的工作原理:
1.LayoutManager用来处理RecycleView的“列表”样式,Support包默认包含了:LinearLayoutManager 横向或纵向的滚动列表、
GridLayoutManager 网格列表、StaggeredGridLayoutManager 交错的网格列表。
2.Adapter负责处理RecycleView的数据和样式
3.在传统的ListView中有一种常见的写法是使用ViewHolder来缓存数据集,在新版的RecycleView内置了ViewHolder这一模块,所以在Adapter内部新建内部类ViewHolder。
4.RecycleView 和listView的一个区别就是本身不处理点击事件,点击事件应该绑在ViewHolder里面,可以直接写也可以通过接口绑在Adapter里面来实现。
首先添加数据集:
package com.lfk.drawapictiure.Info; /** * Created by liufengkai on 15/9/13. */ public class MenuInfo { private String paint_name; private String paint_time; private String paint_root; private String paint_img_root; public MenuInfo(String paint_name, String paint_time, String paint_root, String paint_img_root) { this.paint_name = paint_name; this.paint_time = paint_time; this.paint_root = paint_root; this.paint_img_root = paint_img_root; } public String getPaint_name() { return paint_name; } public String getPaint_time() { return paint_time; } public String getPaint_root() { return paint_root; } public String getPaint_img_root() { return paint_img_root; } }
实现继承自RecycleView的Adapter中间要包裹自己实现的ViewHolder,onCreateviewHolder函数和onBindViewHolder实现了ListView里面getView的工作,分别为找到控件和控件赋值,
实现点击的接口,设置接口并且绑在ViewHolder的itemView里面即根视图中。
public class MainLayoutAdapter extends RecyclerView.Adapter<MainLayoutAdapter.MainViewHolder> { private LayoutInflater inflater; private ArrayList<MenuInfo> userList; private Context context; private MainItemClickListener itemClickListener; public MainLayoutAdapter(ArrayList<MenuInfo> userList, Context context) { this.userList = userList; this.context = context; this.inflater = LayoutInflater.from(context); } public void setItemClickListener(MainItemClickListener itemClickListener) { this.itemClickListener = itemClickListener; } //onCreateviewHolder函数和onBindViewHolder实现了ListView里面getView的工作,分别为找到控件和控件赋值 @Override public MainLayoutAdapter.MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View wrapper = inflater.inflate(R.layout.draw_item, parent, false); return new MainViewHolder( wrapper, (TextView)wrapper.findViewById(R.id.paint_name), (TextView)wrapper.findViewById(R.id.paint_time), (TextView)wrapper.findViewById(R.id.paint_root), (ImageView)wrapper.findViewById(R.id.paint_img)); } @Override public void onBindViewHolder(MainViewHolder holder, int position) { MenuInfo menuInfo = userList.get(position); holder.paint_img.setImageURI(Uri.parse(menuInfo.getPaint_img_root())); holder.paint_name.setText(menuInfo.getPaint_name()); holder.paint_time.setText(menuInfo.getPaint_time()); holder.paint_root.setText(menuInfo.getPaint_root()); } @Override public int getItemCount() { return userList.size(); } public class MainViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,View.OnLongClickListener{ private TextView paint_name; private TextView paint_time; private TextView paint_root; private ImageView paint_img; public MainViewHolder(View itemView, TextView paint_name, TextView paint_time, TextView paint_root, ImageView paint_img) { super(itemView); itemView.setOnClickListener(this); this.paint_name = paint_name; this.paint_time = paint_time; this.paint_root = paint_root; this.paint_img = paint_img; } @Override public void onClick(View view) { MenuInfo menuInfo = userList.get(getAdapterPosition()); itemClickListener.onItemClick(view,menuInfo.getPaint_name(),menuInfo.getPaint_root()); } @Override public boolean onLongClick(View view) { return false; } } }
Activity中的使用:
package com.lfk.drawapictiure.Fragment; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.lfk.drawapictiure.Adapter.MainLayoutAdapter; import com.lfk.drawapictiure.Info.MenuInfo; import com.lfk.drawapictiure.InterFace.MainItemClickListener; import com.lfk.drawapictiure.MainActivity; import com.lfk.drawapictiure.R; import java.util.ArrayList; public class PaintFragment extends android.support.v4.app.Fragment { private RecyclerView mRecyclerView; private MainLayoutAdapter mAdapter; private RecyclerView.LayoutManager mLayoutManager; private String path = Environment.getExternalStorageDirectory().getPath() + "/DrawAPicture"; public static PaintFragment newInstance() { return new PaintFragment(); } public PaintFragment() { // Required empty public constructor } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View wrapper = inflater.inflate(R.layout.fragment_paint, container, false); mRecyclerView = (RecyclerView)wrapper.findViewById(R.id.paint_recycle_view); mLayoutManager = new LinearLayoutManager(getActivity()); mRecyclerView.setLayoutManager(mLayoutManager); //绑上列表管理器 ArrayList<MenuInfo> arrayList = new ArrayList<>(); arrayList.add(new MenuInfo("刘丰恺","09-13 05:28",path+"/"+"12138.lfk",path+"/"+"6826.jpg")); arrayList.add(new MenuInfo("刘丰恺","09-13 05:28",path+"/"+"12138.lfk",path+"/"+"6826.jpg")); arrayList.add(new MenuInfo("刘丰恺","09-13 05:28",path+"/"+"12138.lfk",path+"/"+"6826.jpg")); arrayList.add(new MenuInfo("刘丰恺","09-13 05:28",path+"/"+"12138.lfk",path+"/"+"6826.jpg")); mAdapter = new MainLayoutAdapter(arrayList, getActivity()); // 设置点击事件 mAdapter.setItemClickListener(new MainItemClickListener() { @Override public void onItemClick(View view, String name, String path) { Intent intent = new Intent(getActivity(), MainActivity.class); intent.setData(Uri.parse(path)); startActivity(intent); } }); //绑定数据集 mRecyclerView.setAdapter(mAdapter); return wrapper; } }
其中的子布局使用了CardView:
我设置的东西只有:
card_view:cardCornerRadius="4dp" //设定圆角半径 card_view:cardElevation="8dp"//设定阴影 <?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:background="@drawable/list_item_selector" android:layout_width="match_parent" android:layout_height="wrap_content" card_view:cardCornerRadius="4dp" card_view: android:layout_margin="@dimen/card_margin"> <RelativeLayout android:layout_marginTop="16dp" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginBottom="16dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:maxHeight="100dp" android:layout_marginTop="8dp" android:layout_below="@+id/line_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/paint_img" android:src="@mipmap/ic_launcher" /> <TextView android:id="@+id/paint_name" android:textColor="#000" android:textSize="18sp" android:text="项目名" android:layout_alignParentRight="true" android:singleLine="true" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/paint_time" android:text="项目时间" android:textColor="@color/black" android:textSize="18sp" android:singleLine="true" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <View android:id="@+id/line_view" android:background="@color/gray" android:layout_below="@+id/paint_time" android:layout_width="match_parent" android:layout_height="1dp"/> <TextView android:id="@+id/paint_root" android:visibility="invisible" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> </android.support.v7.widget.CardView>
Android L——RecyclerView,CardView导入和使用
这篇文章是ANDROID L——Material Design详解(UI控件)的一个补充或者说是应用实例,如果有时间建议大家稍微浏览一下上篇文章。
本文主要介绍Android L新增加的两个UI控件RecyclerView,CardView的导入和使用。
RecyclerView是ListView的升级版
CardView则是Google提供的一个卡片式视图组件
本例就是使用RecyclerView来展示多个CardView的一个小例子,先看下效果图:
导入RecyclerView,CardView
由于RecyclerView,CardView是放在support library v7包中,所以我们想要使用就必须要导包。
下面就介绍下在Eclipse和Android Studio中是如何导入这两个包的。
Eclipse:
第一步:通过SDK manager下载/更新Android Support Libraries(5.0版本最新为21)
第二步:导入CardView和RecyclerView项目(都在support v7中)
1.在Eclipse中点击Import,导入Android项目
2.导入CardView和RecycleView,路径为your sdk path\extras\android\support\v7\cardview(RecycleView则为相同目录下的recyclerview)
3.导入时记得将工程copy到本地并建议重命名,这样方便以后管理例如:
第三步:设置Library
1..将两个工程设置为Library
2..在主工程中引入这两个Library例如:
通过这三步就可以将这两个包导入进来了。
Android Studio
Android Stuido相对于Eclipse简单的多:
第一步:
首先要确保已经将Android Support Libraries升级到最新.
第二步:
打开项目中的build.gradle文件,在dependencies中添加如下代码。
dependencies {
compile 'com.android.support:recyclerview-v7:21.+'
compile 'com.android.support:cardview-v7:21.+'
}
第三步:
重新Build一下工程。
Build完成后就会发现这两个包就已经导入进来了
代码介绍:
主题:
首先这个黑色基调的主题是使用了Material.Dark.ActionBar样式。
设置方法:修改values-v21文件夹下styles.xml文件:
<resources> <style name="AppTheme" parent="android:ThemeOverlay.Material.Dark.ActionBar"> </style> </resources>
布局文件:
recycler_view.xml(RecyclerView布局文件):
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MyActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MyActivity" /> </FrameLayout>
FrameLayout里包含了RecyclerView控件
card_view.xml(CardView布局文件):
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="5dp" android:orientation="horizontal" card_view:cardBackgroundColor="@color/cardview_dark_background" card_view:cardCornerRadius="5dp" > <RelativeLayout android:layout_width="match_parent" android:layout_height="100dp" android:padding="5dp" > <ImageView android:id="@+id/pic" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:scaleType="centerCrop" /> <TextView android:clickable="true" android:id="@+id/name" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="10dp" android:layout_marginRight="10dp" android:gravity="right|bottom" android:textColor="@android:color/white" android:textSize="24sp" /> </RelativeLayout> </android.support.v7.widget.CardView>
CardView视图中包含了一个ImageView和一个TextView分别显示图片和文字信息
唯一需要介绍的就是在布局文件中使用了,如下两个属性:
card_view:cardBackgroundColor="@color/cardview_dark_background"
card_view:cardCornerRadius="5dp"
他俩的作用分别是设置CardView的背景颜色和外围的圆角大小(注意要使用card_view命名空间)
代码:
Actor类(封装数据的Model类):
public class Actor { String name; String picName; public Actor(String name, String picName) { this.name = name; this.picName = picName; } public int getImageResourceId( Context context ) { try { return context.getResources().getIdentifier(this.picName, "drawable", context.getPackageName()); } catch (Exception e) { e.printStackTrace(); return -1; } } }
封装了演员的名字和图片名,getImageResourceId()方法的作用就是根据图片命找到系统资源
MyActivity(程序主控制Activity)
public class MyActivity extends Activity { private RecyclerView mRecyclerView; private MyAdapter myAdapter; private List<Actor> actors = new ArrayList<Actor>(); private String[] names = { "朱茵", "张柏芝", "张敏", "巩俐", "黄圣依", "赵薇", "莫文蔚", "如花" }; private String[] pics = { "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8" }; @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate(savedInstanceState); setContentView(R.layout.recycler_view); actors.add(new Actor("朱茵", "p1")); getActionBar().setTitle("那些年我们追的星女郎"); // 拿到RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.list); // 设置LinearLayoutManager mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); // 设置ItemAnimator mRecyclerView.setItemAnimator(new DefaultItemAnimator()); // 设置固定大小 mRecyclerView.setHasFixedSize(true); // 初始化自定义的适配器 myAdapter = new MyAdapter(this, actors); // 为mRecyclerView设置适配器 mRecyclerView.setAdapter(myAdapter); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { // 当点击actionbar上的添加按钮时,向adapter中添加一个新数据并通知刷新 case R.id.action_add: if (myAdapter.getItemCount() != names.length) { actors.add(new Actor(names[myAdapter.getItemCount()], pics[myAdapter.getItemCount()])); mRecyclerView.scrollToPosition(myAdapter.getItemCount() - 1); myAdapter.notifyDataSetChanged(); } return true; // 当点击actionbar上的删除按钮时,向adapter中移除最后一个数据并通知刷新 case R.id.action_remove: if (myAdapter.getItemCount() != 0) { actors.remove(myAdapter.getItemCount()-1); mRecyclerView.scrollToPosition(myAdapter.getItemCount() - 1); myAdapter.notifyDataSetChanged(); } return true; } return super.onOptionsItemSelected(item); } }
MyAdapter(自定义适配器类)
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private List<Actor> actors; private Context mContext; public MyAdapter( Context context , List<Actor> actors) { this.mContext = context; this.actors = actors; } @Override public ViewHolder onCreateViewHolder( ViewGroup viewGroup, int i ) { // 给ViewHolder设置布局文件 View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_view, viewGroup, false); return new ViewHolder(v); } @Override public void onBindViewHolder( ViewHolder viewHolder, int i ) { // 给ViewHolder设置元素 Actor p = actors.get(i); viewHolder.mTextView.setText(p.name); viewHolder.mImageView.setImageDrawable(mContext.getDrawable(p.getImageResourceId(mContext))); } @Override public int getItemCount() { // 返回数据总数 return actors == null ? 0 : actors.size(); } // 重写的自定义ViewHolder public static class ViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public ImageView mImageView; public ViewHolder( View v ) { super(v); mTextView = (TextView) v.findViewById(R.id.name); mImageView = (ImageView) v.findViewById(R.id.pic); } } }
所有代码介绍完毕了,可以总结为以下两点:
RecyclerView:
理解为之前的ListView,不过需要设置LinearLayoutManager(目前资料不多我也有点迷糊以后再补充)和ItemAnimator(为每个条目设置操作动画)两个新属性
RecyclerView.Adapter:
理解为默认自带和基于ViewHolder的新的适配器,只不过回调方法稍有不同,但本质都是一样的。
一、前言
对抗反编译是指让apk文件或者dex文件无法正常通过反编译工具,而且有可能导致工具异常或者崩溃,如apktool、baksmali、dex2jar、JEB等等工具,如下图dex2jar无法正常工作。
二、Dex文件格式解析
目前大多数android软件的反编译工具都是开源的,比如apktool、Dex2jar、baksamli,大家可以非常方便的从github下载并源阅读代码,然后找到可以利用的点,再在自己的软件中加入干扰代码,让反编译工具出现异常或者无法正常阅读代码。
接下来让我们先来熟悉一下dex文件格式 ,一个dex文件由以下几个部份组成:
1. Dex Header: Dex结构头它指定了dex文件的一些基本属性,并记录了部份数据表在dex文件中的物理偏移。 2. String Table: 字符串表,存储字符串的索引和个数 3. Type Table: 类型表,存储类型的索引和个数 4. Proto Table: 函数元型表,存储函数元型索引和个数 5. Field Table: 字段表,存储字段索引和个数 6. Method Table: 方法表,存储方法索引和个数 7. Class def Table:类定义表,存储类定义索引和个数 8. Data Section: 存储数据,由以上类型的索引查找,
有兴趣的可以直接翻看android的源码或者参考以下链接:
http://www.netmite.com/android/mydroid/dalvik/docs/dex-format.html
既然要在代码中添加干扰指令,接下的DexClassDef结构,肯定是要了解的非常清楚。
结构中包含了类的类型偏移、访问标志、父类类型索引、接口偏移、注释、静态类型的偏移信息,整体结构图定义如下:
struct DexClassDef { u4 classIdx; u4 accessFlags; u4 superclassIdx; u4 interfacesOff; u4 sourcefileIdx; u4 annotationsOff; u4 classDataOff; u4 staticValuesOff; }
classIdx 字段是一个索引值,类的类型,做为下标索引在DexTypeID结构列表中查找
accessFlags 字段是类的访问标志,以ACC_开头的枚举值
superclassIdx 字段是父类的类型,做为下标索引在DexTypeID结构列表中查找
interfacesOff 字段是接口类型,做为下标索引在DexTypeList结构列表中查找
sourcefileIdx 字段是源文件名,做为下标索引在DexTypeList结构列表中查找
annotationsOff 字段是注释信息的偏移,指向DexAnnotationsDirectoryItem结构
classdataOff 字段是指向了DexClassData结构体的偏移
staticValuesOff 字段是指向DexEncodeArray结构体的偏移,记录静态数据的信息
DexClassData结构说明:
struct DexClassData { DexClassDataHeader header; DexField* staticFields; //静态字段,DexField结构 DexField* instanceFields; //实例字段,DexField结构 DexMethod* directMethods; //直接方法,DexMethod结构 DexMethod* virtualMethods; //虚方法, DexMethod结构 }
DexClassData结构中的DexMethod类型描述了:方法的原型、名称、访问标志以及代码数据块,codeOff字段指向了一个DexCode结构,它描述了方法更详细的信息以及方法中指令的内容。
DexMethod结构声明如下
struct DexMethod { u4 methodIdx; //指向DexMethodId的索引 u4 accessFlags;//访问标志 u4 codeOff; //指向dexCode的结构偏移 } struct DexCode { ushort registerSsize;//使用的寄存器的数目 ushort insSize; //传入参数的数目 unshort outsSize; //调用其他方法时使用的寄存器个数 unshort triesSize; //try/catch异常块个数 uint debugInfoOff; //调试信息的偏移 uint insnsSize; //指令集个数 ushort insns[1]; //指令集数组,变长数组 }
DexClassData树型结构图:
:
三、调试dex2jar工程
1. 将dex2jar源码导入IntelliJ IDEA,导入后IntelliJ IDEA会自动查找对应 Grable并下载,需要比较长的时间等待
源码地址: https://github.com/pxb1988/dex2jar
2. 选中Grable中的dex2jar/dex2jar/Tasks/other/antlr2java进行编译
3. 点击工具栏的 Project Structure, 然后选择Modules -> d2j-smali -> build,然后点击Excluded
4. 选择 build/generated-sources/antlr, 然后点击 Sources
5. 最后打开Excluded Gradle Task弹出对话框,运行clean distZip 命令
执行完Gradl clean distZip命令后会在\dex2jar-2.x\dex-tools\build\distributions目录下生成 dex-tools-2.1-SNAPSHOT.zip,压缩包内是编译完后生成的jar文件和一些配置信息文件。大家请参考dex2jar.sh文件,它向我们说明需要使用lib目录下的所有jar文件和入口函数com.googlecode.dex2jar.tools.Dex2jarCmd 才能将dex文件转换成jar文件
6.将dex文件转换成jar文件需要执行转换命令
格式如下:
java -Xms512m -Xmx1024m -classpath .\lib\*.jar “com.googlecode.dex2jar.tools.Dex2jarCmd” classdex.dex
根据以上的命令,来配置调试参数,设置如下:
7.设置完成后,就可以开始调试
四、dex2jar反编译失败原因分析
1.首先我们从解包失败的错误异常入手,定位崩溃处的代码。
通过错误提示可以定位到dex2jar崩溃处的代码,源码位置如下:
dex-ir\src\main\java\com\googlecode\dex2jar\ir\TypeClass.java
2. 在崩溃的函数处下断点,开始调试。
a)通过抛出的异常的说明“cant not merge I and Z”,以及整个调用堆栈。
我们可以看出造成这个异常的原因是函数调用时,实参的类型和形参类型不匹配引起的。因为在Java语法中,将实参是布尔类型传递给形参是int类型。这样是不合法的,所以导致dex2jar在检查的参数类型的时候失败。
b)知道了崩溃的原因,那我们就需要确定它具体是使用怎样的方法做到的,通过dex2jar生成的error异常信息详细说明文件得知该dex文件中的每一个函数头部都加了一句这样的代码Exit.b(Exit.a())请看Ida,现在大致能猜到混淆者所做的工作:
1.首先解析dex文件格式,定位到DexCode结构中的insns成员
2.向代码中添加一个类成员对象名字叫Exit,然后添加代码Exit.b(Exit.a())
3.明白了怎么添加干扰代码,接下来分析一下dex2jar的工作流程,dex2jar转换成jar文件这个过程中会验证,函数调用时的形参和实参的合法性,请看下图,解析到INVOKE_开头的指令时,会开始解析返回值,供给参数,和实参等信息,具体逻辑和代码大家有兴趣可以详细地研究一下dex2jar源码。
4.merge负责的工作是参数类型的验证,如果两个类型相同,返回形参的类型,形参为UNKONW返回实参,实参为UNKONW返回形参,两个类型都不相同,则匹配异常
5.最后在dex2jar里添加代码,使dex文件能正确的被反编译成功。这里给出一段可以成功让dex2jar反编译的出jar文件的代码(其实方法有很多)
下图是成功反编译的jar文件
本教程我们主要学习在Android开发中ExecutorService线程池的应用,如何创建线程池;调用线程池的方法,获取线程执行完毕后的结果;关闭线程等。
首先我们先了解一下到底什么是线程池,只有了解了其中的道理,我们才能够进行应用...java.util.concurrent.ExecutorService表述了异步执行的机制
首先我们简单的举一个例子...
package executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Executor { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("cc"); ExecutorService executorService=Executors.newFixedThreadPool(10); executorService.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(true){ System.out.println("aa"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); System.out.println("bb"); } }
这里我们指定了十个线程处于一个线程池内部,线程池的原理其实就是对多线程的一个管理,为了实现异步机制的一种方法,其实就是多个线程执行多个任务,最终这些线程通过线程池进行管理...不用手动去维护...一次可以处理多个任务,这样就可以迅速的进行相应...比如说一个网站成为了热点网站,那么对于大量的点击量,就必须要对每一次的点击做出迅速的处理,这样才能达到更好的交互效果...这样就需要多个线程去处理这些请求,以便能够更好的提供服务...
1. 简单的说一下如何创建线程池进行初始化....创建线程有几种常用方式...这里都是使用了Executors工厂来实例化对象,同时我们也可以根据需求自己去写一个ExecutorService...这几种常用的方法有一定的区别...
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
ExecutorService executorService2 = Executors.newFixedThreadPool(10);
ExecutorService executorService3 = Executors.newScheduledThreadPool(10);
ExecutorService executorService4 = Executors.newCacheThreadPool();
Executors.newSingleThreadExecutor()
单例线程,表示在任意的时间段内,线程池中只有一个线程在工作...
Executors.newCacheThreadPool()
缓存线程池,先查看线程池中是否有当前执行线程的缓存,如果有就resue(复用),如果没有,那么需要创建一个线程来完成当前的调用.并且这类线程池只能完成一些生存期很短的一些任务.并且这类线程池内部规定能resue(复用)的线程,空闲的时间不能超过60s,一旦超过了60s,就会被移出线程池.
Executors.newFixedThreadPool(10) 固定型线程池,和newCacheThreadPool()差不多,也能够实现resue(复用),但是这个池子规定了线程的最大数量,也就是说当池子有空闲时,那么新的任务将会在空闲线程中被执行,一旦线程池内的线程都在进行工作,那么新的任务就必须等待线程池有空闲的时候才能够进入线程池,其他的任务继续排队等待.这类池子没有规定其空闲的时间到底有多长.这一类的池子更适用于服务器.
Executors.newScheduledThreadPool(10)
调度型线程池,调度型线程池会根据Scheduled(任务列表)进行延迟执行,或者是进行周期性的执行.适用于一些周期性的工作.
这就是线程池创建的几种方式...我们需要根据不同的需求来适当的选择到底使用哪种线程池...
2.那么创建了线程池以后就需要对线程池进行调用..将任务加载到其中...
i.ExecutorService.execute(Runnable);
第一种调用方式...通过这种方式将线程任务加载到线程池当中...我们可以添加多个任务...贴上一个完整的代码...大家看一下代码的解释就明白到底是怎么回事了..不难理解...
package executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Executor { /** * @param args * */ public static void main(String[] args) { // TODO Auto-generated method stub ExecutorService executorService=Executors.newFixedThreadPool(2);//定义了线程池中最大存在的线程数目... //添加了第一个任务...这个任务会一直被执行... executorService.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(true){ System.out.println("aa"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); //添加第二个任务,被执行三次停止... executorService.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub int i=0; while(true){ i++; System.out.println("bb"); if(i==3){ break; } try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); /* * @param * 第三个任务...只有当第二个任务被执行三次之后才能被执行... * 由于三次前,线程池已经满了,这个任务是轮不到被执行的..只能排队进行等待. * 三次之后,第二个任务被终止,也就是线程池中出现了空闲的状态,所以这个任务将被放入到线程池中执行... * */ executorService.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(true){ System.out.println("cc"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); } }
ii.executorService.submit(Runnable) 第二种调用方式...这种方式与第一种的区别在于可以使用一个Future对象来判断当前的线程是否执行完毕...但是这种方法只能判断当前的线程是否执行完毕,无法返回数据信息...
Future future = executorService.submit(new Runnable() { public void run() { System.out.println("Asynchronous task"); } }); //如果任务结束执行则返回 null System.out.println("future.get()=" + future.get());
iii.executorService.submit(Callable)... 第三种调用方式...这种调用方式与前一种有所不同,传递的参数为Callable对象,Callable与Runnbale很相似,但是Callable的call()方法可以返回数据信息...通过Future就能够获取到其中的信息..而Runnbale.run()方法时无法获取数据信息的....Future应用于多线程...可以获取call()方法返回的数据信息...其实他是一种模式,是为了性能优化而提供的一种思想...这里我就不说Future...
uture future = executorService.submit(new Callable(){ public Object call() throws Exception { System.out.println("Asynchronous Callable"); return "Callable Result"; } }); System.out.println("future.get() = " + future.get()); //上述样例代码会输出如下结果: //Asynchronous Callable //future.get() = Callable Result
iv.inVokeAny()...第四种调用方式...方法 invokeAny() 接收一个包含 Callable 对象的集合作为参数。调用该方法不会返回 Future 对象,而是返回集合中某一个 Callable 对象的结果,而且无法保证调用之后返回的结果是哪一个Callable,只知道它是这些 Callable 中一个执行结束的 Callable 对象...说实话这个方法我不知道它创建的目的到底是什么...这里执行后的结果是随机的...也就是输出是不固定的....
ExecutorService executorService = Executors.newSingleThreadExecutor(); Set<Callable<String>> callables = new HashSet<Callable<String>>(); callables.add(new Callable<String>() { public String call() throws Exception { return "Task 1"; } }); callables.add(new Callable<String>() { public String call() throws Exception { return "Task 2"; } }); callables.add(new Callable<String>() { public String call() throws Exception { return "Task 3"; } }); String result = executorService.invokeAny(callables); System.out.println("result = " + result);
v.inVokeAll()这个方法和上面不同的地方就在于它可以返回所有Callable的执行结果...获取到所有的执行结果,我们可以对其进行管理...相对而言,我觉得这个方法比上一个更实用吧...
ExecutorService executorService = Executors.newSingleThreadExecutor(); Set<Callable<String>> callables = new HashSet<Callable<String>>(); callables.add(new Callable<String>() { public String call() throws Exception { return "Task 1"; } }); callables.add(new Callable<String>() { public String call() throws Exception { return "Task 2"; } }); callables.add(new Callable<String>() { public String call() throws Exception { return "Task 3"; } }); List<Future<String>> futures = executorService.invokeAll(callables); for(Future<String> future : futures){ System.out.println("future.get = " + future.get());
3.线程池的关闭...
当我们不需要使用线程池的时候,我们需要对其进行关闭...有两种方法可以关闭掉线程池...
i.shutdown()...
shutdown并不是直接关闭线程池,而是不再接受新的任务...如果线程池内有任务,那么把这些任务执行完毕后,关闭线程池....
ii.shutdownNow()
这个方法表示不再接受新的任务,并把任务队列中的任务直接移出掉,如果有正在执行的,尝试进行停止...
大家自己试着运行下面的代码就了解其中到底是怎么回事了...
package executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Executor { /** * @param args * */ public static void main(String[] args) { // TODO Auto-generated method stub ExecutorService executorService=Executors.newFixedThreadPool(1);//定义了线程池中最大存在的线程数目... //添加了第一个任务...这个执行三次停止... executorService.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub int j=0; while(true){ j++; System.out.println("aa"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(j==3){ break; } } } }); //添加第二个任务,由于使用executorService.shutdown(),由于它的加入是在这个方法调用之前的,因此这个任务也会被执行... //如果我们使用了executorService.shutdownNow();方法,就算是他在之前加入的,由于调用了executorService.shutdownNow()方法 //那么这个任务将直接被移出队列并且不会被执行... executorService.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub int i=0; while(true){ i++; System.out.println("bb"); if(i==3){ break; } } } }); executorService.shutdown();//这里无论使用了那种方法,都会抛出一个异常... /* * @param * 第三个任务...只有当第二个任务被执行三次之后才能被执行... * 由于三次前,线程池已经满了,这个任务是轮不到被执行的..只能排队进行等待. * 三次之后,第二个任务被终止,也就是线程池中出现了空闲的状态,所以这个任务将被放入到线程池中执行... * */ executorService.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(true){ System.out.println("cc"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); } }
ExecutorService线程池的用法
在Java5之后,并发线程这块发生了根本的变化,最重要的莫过于新的启动、调度、管理线程的一大堆API了。在Java5以后,通过 Executor来启动线程比用Thread的start()更好。在新特征中,可以很容易控制线程的启动、执行和关闭过程,还可以很容易使用线程池的特性。
一、创建任务
任务就是一个实现了Runnable接口的类。
创建的时候实run方法即可。
二、执行任务
通过java.util.concurrent.ExecutorService接口对象来执行任务,该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建。
Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
ExecutorService提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以关闭 ExecutorService,这将导致其停止接受新任务。关闭后,执行程序将最后终止,这时没有任务在执行,也没有任务在等待执行,并且无法提交新任务。
executorService.execute(new TestRunnable());
1、创建ExecutorService
通过工具类java.util.concurrent.Executors的静态方法来创建。
Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
比如,创建一个ExecutorService的实例,ExecutorService实际上是一个线程池的管理工具:
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService = Executors.newFixedThreadPool(3);
ExecutorService executorService = Executors.newSingleThreadExecutor();
2、将任务添加到线程去执行
当将一个任务添加到线程池中的时候,线程池会为每个任务创建一个线程,该线程会在之后的某个时刻自动执行。
三、关闭执行服务对象
executorService.shutdown();
五、获取任务的执行的返回值
在Java5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被 ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的(<T> task) 方法来执行,并且返回一个 <T><T>,是表示任务等待完成的 Future。
public interface Callable<V>
返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
Callable 接口类似于,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
类包含一些从其他普通形式转换成 Callable 类的实用方法。
Callable中的call()方法类似Runnable的run()方法,就是前者有返回值,后者没有。
当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。
同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。
遗憾的是,在Java API文档中,这块介绍的很糊涂,估计是翻译人员还没搞清楚的缘故吧。或者说是注释不到位。下面看个例子:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; publicclass CallableDemo { publicstaticvoid main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); List<Future<String>> resultList = new ArrayList<Future<String>>(); //创建10个任务并执行 for (int i = 0; i < 10; i++) { //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中 Future<String> future = executorService.submit(new TaskWithResult(i)); //将任务执行结果存储到List中 resultList.add(future); } //遍历任务的结果 for (Future<String> fs : resultList) { try { System.out.println(fs.get()); //打印各个线程(任务)执行的结果 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } finally { //启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。 executorService.shutdown(); } } } } class TaskWithResult implements Callable<String> { privateint id; public TaskWithResult(int id) { this.id = id; } public String call() throws Exception { System.out.println("call()方法被自动调用,干活!!! " + Thread.currentThread().getName()); //一个模拟耗时的操作 for (int i = 999999; i > 0; i--) ; return"call()方法被自动调用,任务的结果是:" + id + " " + Thread.currentThread().getName(); } }
运行结果:
call()方法被自动调用,干活!!! pool-1-thread-1
call()方法被自动调用,干活!!! pool-1-thread-3
call()方法被自动调用,干活!!! pool-1-thread-4
call()方法被自动调用,干活!!! pool-1-thread-6
call()方法被自动调用,干活!!! pool-1-thread-2
call()方法被自动调用,干活!!! pool-1-thread-5
call()方法被自动调用,任务的结果是:0 pool-1-thread-1
call()方法被自动调用,任务的结果是:1 pool-1-thread-2
call()方法被自动调用,干活!!! pool-1-thread-2
call()方法被自动调用,干活!!! pool-1-thread-6
call()方法被自动调用,干活!!! pool-1-thread-4
call()方法被自动调用,任务的结果是:2 pool-1-thread-3
call()方法被自动调用,干活!!! pool-1-thread-3
call()方法被自动调用,任务的结果是:3 pool-1-thread-4
call()方法被自动调用,任务的结果是:4 pool-1-thread-5
call()方法被自动调用,任务的结果是:5 pool-1-thread-6
call()方法被自动调用,任务的结果是:6 pool-1-thread-2
call()方法被自动调用,任务的结果是:7 pool-1-thread-6
call()方法被自动调用,任务的结果是:8 pool-1-thread-4
call()方法被自动调用,任务的结果是:9 pool-1-thread-3
几种不同的ExecutorService线程池对象
1.newCachedThreadPool() -缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse.如果没有,就建一个新的线程加入池中
-缓存型池子通常用于执行一些生存期很短的异步型任务
因此在一些面向连接的daemon型SERVER中用得不多。
-能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。
2. newFixedThreadPool -newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
-其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
-和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
-从方法的源代码看,cache池和fixed 池调用的是同一个底层池,只不过参数不同:
fixed池线程数固定,并且是0秒IDLE(无IDLE)
cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE
3.ScheduledThreadPool -调度型线程池
-这个池子里的线程可以按schedule依次delay执行,或周期执行
4.SingleThreadExecutor -单例线程,任意时间池中只能有一个线程
-用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)
上面四种线程池,都使用Executor的缺省线程工厂建立线程,也可单独定义自己的线程工厂
下面是缺省线程工厂代码:
static class DefaultThreadFactory implements ThreadFactory { static final AtomicInteger poolNumber = new AtomicInteger(1); final ThreadGroup group; final AtomicInteger threadNumber = new AtomicInteger(1); final String namePrefix; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null)? s.getThreadGroup() :Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } public Thread newThread(Runnable r) { Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0); if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } }
也可自己定义ThreadFactory,加入建立池的参数中
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
Executor的execute()方法
execute() 方法将Runnable实例加入pool中,并进行一些pool size计算和优先级处理
execute() 方法本身在Executor接口中定义,有多个实现类都定义了不同的execute()方法
如ThreadPoolExecutor类(cache,fiexed,single三种池子都是调用它)的execute方法如下:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) reject(command); // is shutdown or saturated } }
相关文章
- 下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
- 这篇文章主要给大家介绍了关于C#创建自定义控件及添加自定义属性和事件使用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-25
- 本文实例讲述了JS实现自定义简单网页软键盘效果。分享给大家供大家参考,具体如下:这是一款自定义的简单点的网页软键盘,没有使用任何控件,仅是为了练习JavaScript编写水平,安全性方面没有过多考虑,有顾虑的可以不用,目的是学...2015-11-08
Android开发中findViewById()函数用法与简化
findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20- 如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
- 夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
- 为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
- 如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
- 深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
- 下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
- java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
- 自定义一个jquery模态窗口插件,将它集成到现有平台框架中时,它只能在mainFrame窗口中显示,无法在顶层窗口显示. 解决这个问题的办法: 通过以下代码就可能实现在顶层窗口弹窗 复制代码 代码如下: $(window.top.documen...2014-05-31
- 这篇文章主要介绍了自定义feignClient的常见坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-20
- TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
- 今天小编就为大家分享一篇pytorch 自定义卷积核进行卷积操作方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-05-06
android.os.BinderProxy cannot be cast to com解决办法
本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20- 这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
PHP YII框架开发小技巧之模型(models)中rules自定义验证规则
YII的models中的rules部分是一些表单的验证规则,对于表单验证十分有用,在相应的视图(views)里面添加了表单,在表单被提交之前程序都会自动先来这里面的规则里验证,只有通过对其有效的限制规则后才能被提交,可以很有效地保证...2015-11-24- 这篇文章主要介绍了jquery自定义插件开发之window的实现过程的相关资料,需要的朋友可以参考下...2016-05-09
- 下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02