Android中ViewPage+Fragment顶部及FragmentTabHost+Fragment底部 滑动切换
Android开发中ViewPage+Fragment实现区域顶部tab滑动切换
本教程我们将说说tab导航,导航分为一层和两层(底部区块+区域内头部导航),主要实现方案有RadioGroup+ViewPage+Fragment、Viewpager Indicator、ActionBar Tabs、FragmentTabHost+Fragment等,下面我们先采用RadioGroup+ViewPage+Fragment实现区域头部导航。
如图所示:
案例主要组件
1、先看一下MainActivity布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <HorizontalScrollView android:id="@+id/hvChannel" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none" > <RadioGroup android:id="@+id/rgChannel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> </RadioGroup> </HorizontalScrollView> <android.support.v4.view.ViewPager android:id="@+id/vpNewsList" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" > </android.support.v4.view.ViewPager> </LinearLayout>
2、MainActivity代码:
public class MainActivity extends FragmentActivity implements OnPageChangeListener{ private ViewPager viewPager; private RadioGroup rgChannel=null; private HorizontalScrollView hvChannel; private PageFragmentAdapter adapter=null; private List<Fragment> fragmentList=new ArrayList<Fragment>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView(){ rgChannel=(RadioGroup)super.findViewById(R.id.rgChannel); viewPager=(ViewPager)super.findViewById(R.id.vpNewsList); hvChannel=(HorizontalScrollView)super.findViewById(R.id.hvChannel); rgChannel.setOnCheckedChangeListener( new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { viewPager.setCurrentItem(checkedId); } }); viewPager.setOnPageChangeListener(this); initTab();//动态产生RadioButton initViewPager(); rgChannel.check(0); } private void initTab(){ List<Channel> channelList=ChannelDb.getSelectedChannel(); for(int i=0;i<channelList.size();i++){ RadioButton rb=(RadioButton)LayoutInflater.from(this). inflate(R.layout.tab_rb, null); rb.setId(i); rb.setText(channelList.get(i).getName()); RadioGroup.LayoutParams params=new RadioGroup.LayoutParams(RadioGroup.LayoutParams.WRAP_CONTENT, RadioGroup.LayoutParams.WRAP_CONTENT); rgChannel.addView(rb,params); } } private void initViewPager(){ List<Channel> channelList=ChannelDb.getSelectedChannel(); for(int i=0;i<channelList.size();i++){ NewsFragment frag=new NewsFragment(); Bundle bundle=new Bundle(); bundle.putString("weburl", channelList.get(i).getWeburl()); bundle.putString("name", channelList.get(i).getName()); frag.setArguments(bundle); //向Fragment传入数据 fragmentList.add(frag); } adapter=new PageFragmentAdapter(super.getSupportFragmentManager(),fragmentList); viewPager.setAdapter(adapter); //viewPager.setOffscreenPageLimit(0); } /** * 滑动ViewPager时调整ScroollView的位置以便显示按钮 * @param idx */ private void setTab(int idx){ RadioButton rb=(RadioButton)rgChannel.getChildAt(idx); rb.setChecked(true); int left=rb.getLeft(); int width=rb.getMeasuredWidth(); DisplayMetrics metrics=new DisplayMetrics(); super.getWindowManager().getDefaultDisplay().getMetrics(metrics); int screenWidth=metrics.widthPixels; int len=left+width/2-screenWidth/2; hvChannel.smoothScrollTo(len, 0);//滑动ScroollView } @Override public void onPageScrollStateChanged(int arg0) { } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageSelected(int position) { // TODO Auto-generated method stub setTab(position); } }
其中initTab()方法实现向RadioGroup动态添加RadioButton
导航按钮数据来源于ChannelDb
private static List<Channel> selectedChannel=new ArrayList<Channel>(); static{ selectedChannel.add(new Channel("","头条",0,"","")); selectedChannel.add(new Channel("","娱乐",0,"","")); selectedChannel.add(new Channel("","体育",0,"","")); selectedChannel.add(new Channel("","财经",0,"","")); selectedChannel.add(new Channel("","热点",0,"","")); selectedChannel.add(new Channel("","科技",0,"","")); selectedChannel.add(new Channel("","图片",0,"","")); selectedChannel.add(new Channel("","汽车",0,"","")); selectedChannel.add(new Channel("","时尚",0,"","")); } public static List<Channel> getSelectedChannel(){ return selectedChannel; }
导航按钮外观:tab_rb.xml和tab_selector.xml背景选择器(实现选择后带红色下划线效果)
<?xml version="1.0" encoding="utf-8"?> <RadioButton xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="30dp" android:text="今日" android:background="@drawable/tab_selector" android:paddingLeft="15dp" android:paddingRight="15dp" android:paddingTop="10dp" android:paddingBottom="10dp" android:button="@null" /> tab_selector.xml: <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_checked="true" ><!-- 选中状态 --> <layer-list > <item > <shape android:shape="rectangle"> <stroke android:width="5dp" android:color="#ff0000"/> </shape> </item> <item android:bottom="5dp" > <shape android:shape="rectangle" > <solid android:color="#fff"/> </shape> </item> </layer-list> </item> <item ><!-- 默认状态 --> <shape > <solid android:color="#FAFAFA"/> </shape> </item> </selector>
3、PageFragmentAdapter适配器
public class PageFragmentAdapter extends FragmentPagerAdapter{ private List<Fragment> fragmentList; private FragmentManager fm; public PageFragmentAdapter(FragmentManager fm,List<Fragment> fragmentList){ super(fm); this.fragmentList=fragmentList; this.fm=fm; } @Override public Fragment getItem(int idx) { return fragmentList.get(idx%fragmentList.size()); } @Override public int getCount() { return fragmentList.size(); } @Override public int getItemPosition(Object object) { return POSITION_NONE; //没有找到child要求重新加载 } }
4、NewsFragment组件:
public class NewsFragment extends Fragment { private String weburl; private String channelName; @Override public void onAttach(Activity activity) { super.onAttach(activity); } private View view; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if(view==null){//优化View减少View的创建次数 //该部分可通过xml文件设计Fragment界面,再通过LayoutInflater转换为View组件 //这里通过代码为fragment添加一个TextView TextView tvTitle=new TextView(getActivity()); tvTitle.setText(channelName); tvTitle.setTextSize(16); tvTitle.setGravity(Gravity.CENTER); tvTitle.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT)); view=tvTitle; } ViewGroup parent=(ViewGroup)view.getParent(); if(parent!=null){//如果View已经添加到容器中,要进行删除,负责会报错 parent.removeView(view); } return view; } @Override public void setArguments(Bundle bundle) {//接收传入的数据 weburl=bundle.getString("weburl"); channelName=bundle.getString("name"); } }
Android开发中FragmentTabHost+Fragment实现底部tab切换
上面我们使用RadioGroup+ViewPage+Fragmen实现了顶部滑动导航,接下来我们使用FragmentTabHost+Fragment实现底部tab切换,效果如图所示
案例主要组件
1、MainActivity布局
把整个Activity分成两部TabHost和TabContent,TabHost包含各个tab,tab之间切换将在TabContent所关联的FrameLayout区域中显示各自板块的内容
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" > <FrameLayout android:id="@+id/contentLayout" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> </FrameLayout> <android.support.v4.app.FragmentTabHost android:id="@android:id/tabhost" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#F6F6F6" > <FrameLayout android:id="@android:id/tabcontent" android:layout_height="0dp" android:layout_width="0dp" /> </android.support.v4.app.FragmentTabHost> </LinearLayout>
2、MainActivity代码
public class MainActivity extends FragmentActivity implements OnTabChangeListener{ private FragmentTabHost tabHost; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tabHost=(FragmentTabHost)super.findViewById(android.R.id.tabhost); tabHost.setup(this,super.getSupportFragmentManager() ,R.id.contentLayout); tabHost.getTabWidget().setDividerDrawable(null); tabHost.setOnTabChangedListener(this); initTab(); } private void initTab(){ String tabs[]=TabDb.getTabsTxt(); for(int i=0;i<tabs.length;i++){ TabSpec tabSpec=tabHost.newTabSpec(tabs[i]).setIndicator(getTabView(i)); tabHost.addTab(tabSpec,TabDb.getFragments()[i],null); tabHost.setTag(i); } } private View getTabView(int idx){ View view=LayoutInflater.from(this).inflate(R.layout.footer_tabs,null); ((TextView)view.findViewById(R.id.tvTab)).setText(TabDb.getTabsTxt()[idx]); if(idx==0){ ((TextView)view.findViewById(R.id.tvTab)).setTextColor(Color.RED); ((ImageView)view.findViewById(R.id.ivImg)).setImageResource(TabDb.getTabsImgLight()[idx]); }else{ ((ImageView)view.findViewById(R.id.ivImg)).setImageResource(TabDb.getTabsImg()[idx]); } return view; } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public void onTabChanged(String tabId) { // TODO Auto-generated method stub updateTab(); } private void updateTab(){ TabWidget tabw=tabHost.getTabWidget(); for(int i=0;i<tabw.getChildCount();i++){ View view=tabw.getChildAt(i); ImageView iv=(ImageView)view.findViewById(R.id.ivImg); if(i==tabHost.getCurrentTab()){ ((TextView)view.findViewById(R.id.tvTab)).setTextColor(Color.RED); iv.setImageResource(TabDb.getTabsImgLight()[i]); }else{ ((TextView)view.findViewById(R.id.tvTab)).setTextColor(getResources().getColor(R.color.foot_txt_gray)); iv.setImageResource(TabDb.getTabsImg()[i]); } } } }
3、TabDb组件
提供界面设计所需的tab文本、tab图片和Fragment类型数据
public class TabDb { public static String[] getTabsTxt(){ String[] tabs={"新闻","阅读","试听","发现"," 我"}; return tabs; } public static int[] getTabsImg(){ int[] ids={R.drawable.foot_news_normal,R.drawable.foot_read_normal,R.drawable.foot_vdio_normal,R.drawable.foot_fond_normal,R.drawable.foot_out_normal}; return ids; } public static int[] getTabsImgLight(){ int[] ids={R.drawable.foot_news_light,R.drawable.foot_read_light,R.drawable.foot_vdio_light,R.drawable.foot_found_light,R.drawable.foot_out_light}; return ids; } public static Class[] getFragments(){ Class[] clz={NewsFragment.class,ReadFragment.class,VideoFragment.class,FoundFragment.class,OwnerFragment.class}; return clz; } }
4、每个tab各自对应的Fragment组件
共5个Fragment为NewsFragment、ReadFragment、FoundFragment、OwnerFragment、VideoFragment,根据不同板块各自设计界面,这里重点是如何实现底部tab切换,简单布局一下即可,以NewsFragment为例代码如下:
public class NewsFragment extends Fragment { @Override public void onAttach(Activity activity) { // TODO Auto-generated method stub super.onAttach(activity); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub TextView tvTitle=new TextView(super.getActivity()); tvTitle.setText("新闻"); tvTitle.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT)); tvTitle.setGravity(Gravity.CENTER); tvTitle.setTextSize(30); return tvTitle; } @Override public void setArguments(Bundle args) { // TODO Auto-generated method stub super.setArguments(args); } }
5、tab布局样式(footer_tabs.xml)
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="5dp" android:background="#F6F6F6" > <ImageView android:id="@+id/ivImg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" /> <TextView android:id="@+id/tvTab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/ivImg" android:textColor="#AEAEAE" android:text="新闻" android:layout_marginTop="2dp"/>
一、ANR介绍
在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。所以一个流畅的合理的应用程序中不能出现anr,而让用户每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样系统不会显示ANR给用户。默认情况下,在android中Activity的最长执行时间是5秒,BroadcastReceiver的最长执行时间则是10秒。
二:ANR的类型
ANR一般有三种类型:
1. KeyDispatchTimeout(5 seconds) --主要类型按键或触摸事件在特定时间内无响应
2. BroadcastTimeout(10 seconds) --BroadcastReceiver在特定时间内无法处理完成
3. ServiceTimeout(20 seconds) --小概率类型 Service在特定的时间内无法处理完成
三:KeyDispatchTimeout
Akey or touch event was not dispatched within the specified time(按键或触摸事件在特定时间内无响应)
具体的超时时间的定义在framework下的ActivityManagerService.java
//How long we wait until we timeout on key dispatching.
staticfinal int KEY_DISPATCHING_TIMEOUT = 5*1000
四:为什么会超时呢?
超时时间的计数一般是从按键分发给app开始。超时的原因一般有两种:
(1)当前的事件没有机会得到处理(即UI线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
(2)当前的事件正在处理,但没有及时完成
五:如何避免KeyDispatchTimeout
1. UI线程尽量只做跟UI相关的工作
2. 耗时的工作(比如数据库操作,I/O,连接网络或者别的有可能阻碍UI线程的操作)把它放入单独的线程处理
3. 尽量用Handler来处理UIthread和别的thread之间的交互
六:UI线程
说了那么多的UI线程,那么哪些属于UI线程呢?
UI线程主要包括如下:
1. Activity:onCreate(), onResume(), onDestroy(), onKeyDown(), onClick(),etc
2. AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(), onCancel,etc
3. Mainthread handler: handleMessage(), post*(runnable r), etc
4. other
七:如何去分析ANR
先看个LOG:
04-01 13:12:11.572 I/InputDispatcher( 220): Application is not responding:Window{2b263310com.android.email/com.android.email.activity.SplitScreenActivitypaused=false}.
5009.8ms since event, 5009.5ms since waitstarted
04-0113:12:11.572 I/WindowManager( 220): Input event
dispatching timedout sending
tocom.android.email/com.android.email.activity.SplitScreenActivity
04-01 13:12:14.123 I/Process( 220): Sending signal. PID: 21404 SIG: 3---发生ANR的时间和生成trace.txt的时间
04-01 13:12:14.123 I/dalvikvm(21404):threadid=4: reacting to
signal 3
……
04-0113:12:15.872 E/ActivityManager( 220): ANR in
com.android.email(com.android.email/.activity.SplitScreenActivity)
04-0113:12:15.872 E/ActivityManager( 220):
Reason:keyDispatchingTimedOut
04-0113:12:15.872 E/ActivityManager( 220): Load: 8.68 / 8.37 / 8.53
04-0113:12:15.872 E/ActivityManager( 220): CPUusage from 4361ms to 699ms ago ----CPU在ANR发生前的使用情况
04-0113:12:15.872 E/ActivityManager( 220): 5.5%21404/com.android.email: 1.3% user + 4.1% kernel / faults:
10 minor
04-0113:12:15.872 E/ActivityManager( 220): 4.3%220/system_server: 2.7% user + 1.5% kernel / faults: 11
minor 2 major
04-0113:12:15.872 E/ActivityManager( 220): 0.9%52/spi_qsd.0: 0% user + 0.9% kernel
04-0113:12:15.872 E/ActivityManager( 220): 0.5%65/irq/170-cyttsp-: 0% user + 0.5% kernel
04-0113:12:15.872 E/ActivityManager( 220): 0.5%296/com.android.systemui: 0.5% user + 0% kernel
04-0113:12:15.872 E/ActivityManager( 220): 100%TOTAL: 4.8% user + 7.6% kernel + 87% iowait
04-0113:12:15.872 E/ActivityManager( 220): CPUusage from 3697ms to 4223ms later:-- ANR后CPU的使用量
04-0113:12:15.872 E/ActivityManager( 220): 25%21404/com.android.email: 25% user + 0% kernel / faults: 191 minor
04-0113:12:15.872 E/ActivityManager( 220): 16% 21603/__eas(par.hakan: 16% user + 0% kernel
04-0113:12:15.872 E/ActivityManager( 220): 7.2% 21406/GC: 7.2% user + 0% kernel
04-0113:12:15.872 E/ActivityManager( 220): 1.8% 21409/Compiler: 1.8% user + 0% kernel
04-0113:12:15.872 E/ActivityManager( 220): 5.5%220/system_server: 0% user + 5.5% kernel / faults: 1 minor
04-0113:12:15.872 E/ActivityManager( 220): 5.5% 263/InputDispatcher: 0% user + 5.5% kernel
04-0113:12:15.872 E/ActivityManager( 220): 32%TOTAL: 28% user + 3.7% kernel
从LOG可以看出ANR的类型,CPU的使用情况,如果CPU使用量接近100%,说明当前设备很忙,有可能是CPU饥饿导致了ANR
如果CPU使用量很少,说明主线程被BLOCK了
如果IOwait很高,说明ANR有可能是主线程在进行I/O操作造成的
除了看LOG,解决ANR还得需要trace.txt文件,
如何获取呢?可以用如下命令获取
$chmod 777 /data/anr
$rm /data/anr/traces.txt
$ps
$kill -3 PID
adbpull data/anr/traces.txt ./mytraces.txt
从trace.txt文件,看到最多的是如下的信息:
-----pid 21404 at 2011-04-01 13:12:14 -----
Cmdline: com.android.email
DALVIK THREADS:
(mutexes: tll=0tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)
"main" prio=5 tid=1NATIVE
| group="main" sCount=1 dsCount=0obj=0x2aad2248 self=0xcf70
| sysTid=21404 nice=0 sched=0/0cgrp=[fopen-error:2]
handle=1876218976
atandroid.os.MessageQueue.nativePollOnce(Native Method)
atandroid.os.MessageQueue.next(MessageQueue.java:119)
atandroid.os.Looper.loop(Looper.java:110)
at android.app.ActivityThread.main(ActivityThread.java:3688)
at java.lang.reflect.Method.invokeNative(Native Method)
atjava.lang.reflect.Method.invoke(Method.java:507)
atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:624)
at dalvik.system.NativeStart.main(Native Method)
说明主线程在等待下条消息进入消息队列
八:Thread状态
ThreadState (defined at “dalvik/vm/thread.h “)
THREAD_UNDEFINED = -1, /* makes enum compatible with int32_t */
THREAD_ZOMBIE = 0, /* TERMINATED */
THREAD_RUNNING = 1, /* RUNNABLE or running now */
THREAD_TIMED_WAIT = 2, /* TIMED_WAITING in Object.wait() */
THREAD_MONITOR = 3, /* BLOCKED on a monitor */
THREAD_WAIT = 4, /* WAITING in Object.wait() */
THREAD_INITIALIZING= 5, /* allocated, not yet running */
THREAD_STARTING = 6, /* started, not yet on thread list */
THREAD_NATIVE = 7, /* off in a JNI native method */
THREAD_VMWAIT = 8, /* waiting on a VM resource */
THREAD_SUSPENDED = 9, /* suspended, usually by GC or debugger */
九:如何调查并解决ANR
1. 首先分析log
2. 从trace.txt文件查看调用stack.
3. 看代码
4. 仔细查看ANR的成因(iowait?block?memoryleak?)
十:案例
案例1:关键词:ContentResolver in AsyncTask onPostExecute, high iowait
原因:IOWait很高,说明当前系统在忙于I/O,因此数据库操作被阻塞
原来:
final Message message=Message.restoreMessageWithId(mProviderContext,messageId); if(message==null){ return; } Account account=Account.restoreAccountWithId(mProviderContext,message.mAccountKey); if(account==null){ return;//isMessagingController returns false for null, but let's make itclear. } if(isMessagingController(account)){ new Thread(){ @Override public void run(){ mLegacyController.processPendingActions(message.mAccountKey); }}.start(); }
解决后:
newThread() { finalMessagemessage=Message.restoreMessageWithId(mProviderContext,messageId); if(message==null){ return; } Accountaccount=Account.restoreAccountWithId(mProviderContext,message.mAccountKey); if(account==null){ return;//isMessagingController returns false for null, but let's make itclear. } if(isMessagingController(account)) { mLegacyController.processPendingActions(message.mAccountKey); } }.start();
关于AsyncTask:http://developer.android.com/reference/android/os/AsyncTask.html
案例2:关键词:在UI线程进行网络数据的读写
ANRin process: com.android.mediascape:PhotoViewer (last incom.android.mediascape:PhotoViewer) Annotation:keyDispatchingTimedOut CPU usage: Load: 6.74 / 6.89 / 6.12 CPUusage from 8254ms to 3224ms ago: ovider.webmedia: 4% = 4% user +0% kernel / faults: 68 minor system_server: 2% = 1% user + 0%kernel / faults: 18 minor re-initialized>: 0% = 0% user + 0%kernel / faults: 50 minor events/0: 0% = 0% user + 0%kernel TOTAL:7% = 6% user + 1% kernel DALVIKTHREADS: ""main"" prio=5 tid=3 NATIVE |group=""main"" sCount=1 dsCount=0 s=Yobj=0x4001b240 self=0xbda8 | sysTid=2579 nice=0 sched=0/0cgrp=unknown handle=-1343993184 atorg.apache.harmony.luni.platform.OSNetworkSystem.receiveStreamImpl(NativeMethod) atorg.apache.harmony.luni.platform.OSNetworkSystem.receiveStream(OSNetworkSystem.java:478) atorg.apache.harmony.luni.net.PlainSocketImpl.read(PlainSocketImpl.java:565) atorg.apache.harmony.luni.net.SocketInputStream.read(SocketInputStream.java:87) atorg.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnection$LimitedInputStream.read(HttpURLConnection.java:303) atjava.io.InputStream.read(InputStream.java:133) atjava.io.BufferedInputStream.fillbuf(BufferedInputStream.java:157) atjava.io.BufferedInputStream.read(BufferedInputStream.java:346) atandroid.graphics.BitmapFactory.nativeDecodeStream(Native Method) atandroid.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459) atcom.android.mediascape.activity.PhotoViewerActivity.getPreviewImage(PhotoViewerActivity.java:4465) atcom.android.mediascape.activity.PhotoViewerActivity.dispPreview(PhotoViewerActivity.java:4406) atcom.android.mediascape.activity.PhotoViewerActivity.access$6500(PhotoViewerActivity.java:125) atcom.android.mediascape.activity.PhotoViewerActivity$33$1.run(PhotoViewerActivity.java:4558) atandroid.os.Handler.handleCallback(Handler.java:587) atandroid.os.Handler.dispatchMessage(Handler.java:92) atandroid.os.Looper.loop(Looper.java:123) atandroid.app.ActivityThread.main(ActivityThread.java:4370) atjava.lang.reflect.Method.invokeNative(Native Method) atjava.lang.reflect.Method.invoke(Method.java:521) atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868) atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:626) atdalvik.system.NativeStart.main(Native Method)
关于网络连接,在设计的时候可以设置个timeout的时间或者放入独立的线程来处理。
关于Handler的问题,可以参考:http://developer.android.com/reference/android/os/Handler.html
案例3:关键词:Memoryleak/Thread leak
11-1621:41:42.560 I/ActivityManager( 1190): ANR in process:android.process.acore (last in android.process.acore) 11-1621:41:42.560 I/ActivityManager( 1190): Annotation:keyDispatchingTimedOut 11-16 21:41:42.560 I/ActivityManager(1190): CPU usage: 11-16 21:41:42.560 I/ActivityManager( 1190):Load: 11.5 / 11.1 / 11.09 11-16 21:41:42.560 I/ActivityManager(1190): CPU usage from 9046ms to 4018ms ago: 11-16 21:41:42.560I/ActivityManager( 1190): d.process.acore:98%= 97% user + 0% kernel / faults: 1134 minor 11-16 21:41:42.560I/ActivityManager( 1190): system_server: 0% = 0% user + 0% kernel /faults: 1 minor 11-16 21:41:42.560 I/ActivityManager( 1190): adbd:0% = 0% user + 0% kernel 11-16 21:41:42.560 I/ActivityManager(1190): logcat: 0% = 0% user + 0% kernel 11-16 21:41:42.560I/ActivityManager( 1190): TOTAL:100% = 98% user + 1% kernel Cmdline: android.process.acore DALVIK THREADS: "main"prio=5 tid=3 VMWAIT |group="main" sCount=1 dsCount=0 s=N obj=0x40026240self=0xbda8 | sysTid=1815 nice=0 sched=0/0 cgrp=unknownhandle=-1344001376 atdalvik.system.VMRuntime.trackExternalAllocation(NativeMethod) atandroid.graphics.Bitmap.nativeCreate(Native Method) atandroid.graphics.Bitmap.createBitmap(Bitmap.java:468) atandroid.view.View.buildDrawingCache(View.java:6324) atandroid.view.View.getDrawingCache(View.java:6178) atandroid.view.ViewGroup.drawChild(ViewGroup.java:1541) …… atcom.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1830) atandroid.view.ViewRoot.draw(ViewRoot.java:1349) atandroid.view.ViewRoot.performTraversals(ViewRoot.java:1114) atandroid.view.ViewRoot.handleMessage(ViewRoot.java:1633) atandroid.os.Handler.dispatchMessage(Handler.java:99) atandroid.os.Looper.loop(Looper.java:123) atandroid.app.ActivityThread.main(ActivityThread.java:4370) atjava.lang.reflect.Method.invokeNative(Native Method) atjava.lang.reflect.Method.invoke(Method.java:521) atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868) atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:626) atdalvik.system.NativeStart.main(Native Method) "Thread-408"prio=5 tid=329 WAIT |group="main" sCount=1 dsCount=0 s=N obj=0x46910d40self=0xcd0548 | sysTid=10602 nice=0 sched=0/0 cgrp=unknownhandle=15470792 at java.lang.Object.wait(Native Method) -waiting on <0x468cd420> (a java.lang.Object) atjava.lang.Object.wait(Object.java:288) atcom.android.dialer.CallLogContentHelper$UiUpdaterExecutor$1.run(CallLogContentHelper.java:289) atjava.lang.Thread.run(Thread.java:1096)
分析:
atdalvik.system.VMRuntime.trackExternalAllocation(NativeMethod)内存不足导致block在创建bitmap上
**MEMINFO in pid 1360 [android.process.acore] **
native dalvik other total
size: 17036 23111 N/A 40147
allocated: 16484 20675 N/A 37159
free: 296 2436 N/A 2732
解决:如果机器的内存族,可以修改虚拟机的内存为36M或更大,不过最好是复查代码,查看哪些内存没有释放
关于Android中ANR的一些思考
以前做Android系统开发,一般很少写程序。现在到一公司做Android互联网应用,程序中不时出现一些ANR。
上峰对ANR非常的关注,期望我能彻底解决该项目的ANR。⊙?⊙b汗!
因此我对ANR进行了些思考,并和刚从腾讯QQ项目组跳槽来公司北京总部的某架构师同事进行了探讨。他也基本认同我的观点。
现将我应对ANR的思路整理如下:
一、在项目之前,应该确保工程师理解产生ANR的基本原理,Handler的基本原理,明白一些主要回调函数的执行线程。如果工程师还没达到这些要求,应该通过培训等方式尽量让工程师了解这些知识。
关于这些技术的知识可参考《关于ANR的官方建议》和《Android线程模型》和《Looper和Handler》
二、如果程序在初始化阶段较耗时,考虑显示一splash屏或者尽快让主视图快速显示处理,然后才显示其他的视图。不管是哪一种情况,应该设法表明程序正在往前执行,以免用户觉得应用冻结了。
三、在进行架构设计时,架构师应该尽量采用MVC架构,另外一定要清楚那些代码应该是在主线程中执行,那些代码应该是在非主线程中执行。当然要做好框架也不容易啊!⊙?⊙b汗!
四、对于已处于项目中后期,而没有采用MVC框架,或者采用了MVC框架但其实现并不太好的项目,应该画出其整体框架图,时序图等进行分析,尽量采用较小的代价,逐步迭代的方式让其项目最后达到良好的MVC架构。对于我们的当前项目,我正试图使用该方式来最大程度的避免ANR。也不知道最后能不能达到很好的效果。
五、在进行编码时,工程师一定要考虑当前代码死否在主线程中执行;当前代码是否是耗时操作;
线程对锁的竞争是否可能造成代码的等待,而耗时太多;代码是否可能造成死锁,而产生ANR。
六、如果应用程序中使用了第三方的应用程序,请把这些第三方的应用程序做为单独的进程来处理,以避免它的不良代码而造成本项目出现ANR。关于此技术请参考《Android中单APK应用多进程》
七、在程序运行时,出现了ANR,工程师应该通过/data/anr/traces.txt并结合代码,进行ANR的分析。
八、在项目中后期,你可以使用 StrictMode 来帮助你在主线程中查找潜在的耗时操作,比如对网络或数据库操作。
九、在项目后期,应该通过monkey等来进行压力测试,找出潜在的ANR,并进行修改。
关于monkey的使用请参考《Android的monkey用法》
十、对于一些重要的ANR及不良代码,要进行归纳和总结,形成文档,以便分享给其他的同事或项目组,并作为新员工的培训资料。
总结
每当产生ANR我们可以根据/data/anr/traces.txt分析解决,但这个顽疾很难从根本上解决,只有通过引入好的框架(比如MVC框架),提升开发工程师认知,技术积累去避免。
本文假定你已经对属性动画有了一定的了解,至少使用过属性动画。下面我们就从属性动画最简单的使用开始。
ObjectAnimator .ofInt(target,propName,values[]) .setInterpolator(LinearInterpolator) .setEvaluator(IntEvaluator) .setDuration(500) .start();
相信这段代码对你一定不陌生,代码中有几个地方是本文中将要重点关注的,setInterpolator(...)、setEvaluator(...)、setDuration(...)在源代码中是如何被使用的。另外,我们也将重点关注Android中属性动画是如何一步步地实现动画效果的(精确到每一帧(frame))。最后??录妇洌?疚闹惺褂玫拇?胧?ndroid 4.2.2。
上面代码的作用就是生成一个属性动画,根据ofInt()我们知道只是一个属性值类型为Int的View的动画。先放过其他的函数,从ObjectAnimator的start()函数开始。
public void start() {
//...省略不必要代码
super.start();
}
从代码中我们知道,它调用了父类的start()方法,也就是ValueAnimator.start()。这个方法内部又调用了自身类内部的start(boolean playBackwards)方法。
/** * 方法简要介绍: * 这个方法开始播放动画。这个start()方法使用一个boolean值playBackwards来判断是否需 * 要回放动画。这个值通常为false,但当它从reverse()方法被调用的时候也 * 可能为true。有一点需要注意的是,这个方法必须从UI主线程调用。 */ private void start(boolean playBackwards) { if (Looper.myLooper() == null) { throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } mPlayingBackwards = playBackwards; mCurrentIteration = 0; mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; AnimationHandler animationHandler = getOrCreateAnimationHandler(); animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // 在动画实际运行前,设置动画的初始值 setCurrentPlayTime(0); mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); } animationHandler.start(); }
对代码中几个值的解释:
mPlayingStated代表当前动画的状态。用于找出什么时候开始动画(if state == STOPPED)。当然也用于在animator被调用了cancel()或 end()在动画的最后一帧停止它。可能的值为STOPPED, RUNNING, SEEKED.
mStarted是Animator中一个额外用于标识播放状态的值,用来指示这个动画是否需要延时执行。
mStartedDelay指示这个动画是否已经从startDelay中开始执行。
AnimationHandler animationHandler 是一个实现了Runnable接口的ValueAnimator内部类,暂时先放过,后面我们会具体谈到。
从上面这段代码中,我们了解到一个ValueAnimator有它自己的状态(STOPPED, RUNNING, SEEKED),另外是否延时也影响ValueAnimator的执行。代码的最后调用了animationHandler.start(),看来动画就是从这里启动的。别急,我们还没初始化ValueAnimator呢,跟进setCurrentPlayTime(0)看看。
public void setCurrentPlayTime(long playTime) { initAnimation(); long currentTime = AnimationUtils.currentAnimationTimeMillis(); if (mPlayingState != RUNNING) { mSeekTime = playTime; mPlayingState = SEEKED; } mStartTime = currentTime - playTime; doAnimationFrame(currentTime); }
这个函数在animation开始前,设置它的初始值。这个函数用于设置animation进度为指定时间点。playTime应该介于0到animation的总时间之间,包括animation重复执行的时候。如果animation还没有开始,那么它会等到被设置这个时间后才开始。如果animation已经运行,那么setCurrentTime()会将当前的进度设置为这个值,然后从这个点继续播放。
接下来让我们看看initAnimation()
void initAnimation() { if (!mInitialized) { int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].init(); } mInitialized = true; } }
这个函数一看就觉得跟初始化动画有关。这个函数在处理动画的第一帧前就会被调用。如果startDelay不为0,这个函数就会在就会在延时结束后调用。它完成animation最终的初始化。
那么mValues是什么呢?还记得我们在文章的开头介绍ObjectAnimator的使用吧?还有一个ofInt(T target, Property property, int... values)方法没有介绍。官方文档中对这个方法的解释是:构造并返回一个在int类型的values数值之间ObjectAnimator对象。当values只有一个值的时候,这个值就作为animator的终点值。如果有两个值的话,那么这两个值就作为开始值和结束值。如果有超过两个以上的值的话,那么这些值就作为开始值,作为animator运行的中间值,以及结束值。这些值将均匀地分配到animator的持续时间。
先中断ObjectAnimator.start()流程的分析,回到开头ObjectAnimator.ofInt(...)
接下来让我们深入ofInt(...)的内部看看。
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setIntValues(values); return anim; }
对这个函数的解释:
target 就是将要进行动画的对象
propertyName 就是这个对象属性将要进行动画的属性名
values 一组值。随着时间的推移,动画将根据这组值进行变化。
再看看anim.setIntValues这个函数
public void setIntValues(int... values) { if (mValues == null || mValues.length == 0) { // No values yet - this animator is being constructed piecemeal. Init the values with // whatever the current propertyName is if (mProperty != null) { setValues(PropertyValuesHolder.ofInt(mProperty, values)); } else { setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); } } else { super.setIntValues(values); } }
一开始的时候,mProperty肯定还没有初始化,我们进去setValues(PropertyValuesHolder.ofInt(mPropertyName, values))看看。这里涉及到PropertyValuesHolder这个类。PropertyValuesHolder这个类拥有关于属性的信息和动画期间需要使用的值。PropertyValuesHolder对象可以用来和ObjectAnimator或ValueAnimator一起创建可以并行操作不PropertyValuesHolder同属性的animator。
那么PropertyValuesHolder.ofInt()是干嘛用的呢?它通过传入的属性和values来构造并返回一个特定类型的PropertyValuesHolder对象(在这里是IntPropertyValuesHolder类型)。
public static PropertyValuesHolder ofInt(String propertyName, int... values) { return new IntPropertyValuesHolder(propertyName, values); }
在IntPropertyValuesHolder内部
public IntPropertyValuesHolder(String propertyName, int... values) { super(propertyName); setIntValues(values); } @Override public void setIntValues(int... values) { super.setIntValues(values); mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; }
跳转到父类(PropertyValuesHolder)的setIntValues
public void setIntValues(int... values) { mValueType = int.class; mKeyframeSet = KeyframeSet.ofInt(values); }
这个函数其实跟我们前面介绍到的 PropertyValueHolder的构造函数相似,它就是设置动画过程中需要的值。如果只有一个值,那么这个值就假定为animator的终点值,动画的初始值会自动被推断出来,通过对象的getter方法得到。当然,如果所有值都为空,那么同样的这些值也会在动画开始的时候也会自动被填上。这套自动推断填值的机制只在PropertyValuesHolder对象跟ObjectAnimator一起使用的时候才有效,并且有一个能从propertyName自动推断出的getter方法这些条件都成立的时候才能用,不然PropertyValuesHolder没有办法决定这些值是什么。
接下来我们看到KeyframeSet.ofInt(values)方法。KeyframeSet这个类持有Keyframe的集合,在一组给定的animator的关键帧(keyframe)中会被ValueAnimator用来计算值。这个类的访问权限为包可见,因为这个类实现Keyframe怎么被存储和使用的具体细节,外部不需要知道。
接下来我们看看KeyframeSet.ofInt(values)方法。
public static KeyframeSet ofInt(int... values) { int numKeyframes = values.length; IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)]; if (numKeyframes == 1) { keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f); keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]); } else { keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]); for (int i = 1; i < numKeyframes; ++i) { keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]); } } return new IntKeyframeSet(keyframes); }
在这个方法里面,我们看见从最开始的ObjectAnimator.ofInt(target,propName,values[]),也就是我们在文章的开头使用系统提供的动画初始化函数中传入的int数组,在这里得到了具体的使用。先不关心具体的使用Keyframe.ofInt(...)。从这里我们就可以知道原来Android SDK通过传入的int[]的长度来决定animator中每个帧(frame)的值。具体的对传入的int[]的使用可以参考文章里面对ObjectAnimator.ofInt(...)的介绍。在KeyframeSet.ofInt这个函数的最后一句话使用了IntKeyframeSet的构造函数来初始化这些Keyframe。
public IntKeyframeSet(IntKeyframe... keyframes) { super(keyframes); }
在IntKeyframeSet的构造函数中又调用父类KeyframeSet的构造函数来实现。
public KeyframeSet(Keyframe... keyframes) { mNumKeyframes = keyframes.length; mKeyframes = new ArrayList<Keyframe>(); mKeyframes.addAll(Arrays.asList(keyframes)); mFirstKeyframe = mKeyframes.get(0); mLastKeyframe = mKeyframes.get(mNumKeyframes - 1); mInterpolator = mLastKeyframe.getInterpolator(); }
从这个构造函数中我们又可以了解到刚刚初始化后的Keyframe数组的第一项和最后一项(也就是第一帧和最后一帧)得到了优先的待遇,作为在KeyframeSet中的字段,估计是为了后面计算动画开始和结束的时候方便。
小结ObjectValue、PropertyValueHolder、KeyframeSet的关系
我们绕了很久,不知道是否把你弄晕了,这里稍稍总结一下。我们就不从调用的顺序一步步分析下来了,太长了。我直接说说ObjectValue、PropertyValueHolder、KeyframeSet之间的关系。这三个类比较有特点的地方,ObjectAnimator无疑是对的API接口,ObjectAnimator持有PropertyValuesHolder作为存储关于将要进行动画的具体对象(通常是View类型的控件)的属性和动画期间需要的值。而PropertyValueHolder又使用KeyframeSet来保存animator从开始到结束期间关键帧的值。这下子我们就了解animator在执行期间用来存储和使用的数据结构。废话一下,从PropertyValueHolder、KeyframeSet这个两个类的源代码来看,这三个类的API的设计挺有技巧的,他们都是通过将具有特定类型的实现作为一个大的概况性的类的内部实现,通过这个大的抽象类提供对外的API(例如,PropertyValuesHolder.ofInt(...)的实现)。
回到ObjectAnimator.start()流程的分析
不知道是否把你上面你是否能清楚,反正不太影响下面对ObjectAnimator.start()流程的分析。从上面一段的分析我们了解到ValueAnimator.initAnimation()中的mValue是 PropertyValuesHolder类型的东西。在initAnimation()里mValues[i].init()初始化它们的估值器Evaluator
void init() { if (mEvaluator == null) { // We already handle int and float automatically, but not their Object // equivalents mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : (mValueType == Float.class) ? sFloatEvaluator : null; } if (mEvaluator != null) { // KeyframeSet knows how to evaluate the common types - only give it a custom // evaluator if one has been set on this class mKeyframeSet.setEvaluator(mEvaluator); } }
mEvaluator当然也可以使用ObjectAnimator.setEvaluator(...)传入;为空时,SDK根据mValueType为我们初始化特定类型的Evaluator。这样我们的初始化就完成了。接下来,跳出initAnimation()回到
setCurrentPlayTime(...)
public void setCurrentPlayTime(long playTime) { initAnimation(); long currentTime = AnimationUtils.currentAnimationTimeMillis(); if (mPlayingState != RUNNING) { mSeekTime = playTime; mPlayingState = SEEKED; } mStartTime = currentTime - playTime; doAnimationFrame(currentTime); }
对animator三种状态STOPPED、RUNNING、SEEKED的解释
static final int STOPPED = 0; // 还没开始播放
static final int RUNNING = 1; // 正常播放中
static final int SEEKED = 2; // 定位到一些时间值(Seeked to some time value)
对mSeekedTime、mStartTime的解释
mSeekedTime 当setCurrentPlayTime()被调用的时候设置。如果为负数,animator还没能定位到一个值。
mStartTime 第一次在animation.animateFrame()方法调用时使用。这个时间在第二次调用animateFrame()时用来确定运行时间(以及运行的分数值)
setCurrentPlayTime(...)中doAnimationFrame(currentTime) 之前的代码其实都是对Animator的初始化。看来doAnimator(...)就是真正处理动画帧的函数了。这个函数主要主要用来处理animator中的一帧,并在有必要的时候调整animator的开始时间。
final boolean doAnimationFrame(long frameTime) { //对animator的开始时间和状态进行调整 if (mPlayingState == STOPPED) { mPlayingState = RUNNING; if (mSeekTime < 0) { mStartTime = frameTime; } else { mStartTime = frameTime - mSeekTime; // Now that we're playing, reset the seek time mSeekTime = -1; } } // The frame time might be before the start time during the first frame of // an animation. The "current time" must always be on or after the start // time to avoid animating frames at negative time intervals. In practice, this // is very rare and only happens when seeking backwards. final long currentTime = Math.max(frameTime, mStartTime); return animationFrame(currentTime); }
看来这个函数就是调整了一些参数,真正的处理函数还在animationFrame(...)中。我们跟进去看看。
boolean animationFrame(long currentTime) { boolean done = false; switch (mPlayingState) { case RUNNING: case SEEKED: float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f; if (fraction >= 1f) { if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { // Time to repeat if (mListeners != null) { int numListeners = mListeners.size(); for (int i = 0; i < numListeners; ++i) { mListeners.get(i).onAnimationRepeat(this); } } if (mRepeatMode == REVERSE) { mPlayingBackwards = mPlayingBackwards ? false : true; } mCurrentIteration += (int)fraction; fraction = fraction % 1f; mStartTime += mDuration; } else { done = true; fraction = Math.min(fraction, 1.0f); } } if (mPlayingBackwards) { fraction = 1f - fraction; } animateValue(fraction); break; } return done; }
这个内部函数对给定的animation的一个简单的动画帧进行处理。currentTime这个参数是由定时脉冲(先不要了解这个定时脉冲是什么,后面我们会涉及)通过handler发送过来的(当然也可能是初始化的时候,被程序调用的,就像我们的分析过程一样),它用于计算animation已运行的时间,以及已经运行分数值。这个函数的返回值标识这个animation是否应该停止(在运行时间超过animation应该运行的总时长的时候,包括重复次数超过的情况)。
我们可以把这个函数里面的fraction简单地理解成animator的进度条的当前的位置。if (fraction >= 1f) 注意到函数里面的这句话,当animator开始需要重复执行的时候,那么就需要执行这个if判断里面的东西,这里面主要就是记录和改变重复执行animator的一些状态和变量。为了不让这篇文章太复杂,我们这里就不进行分析了。通过最简单的animator只执行一次的情况来分析。那么接下来就应该执行animateValue(fraction)了。
void animateValue(float fraction) { fraction = mInterpolator.getInterpolation(fraction); mCurrentFraction = fraction; int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].calculateValue(fraction); } if (mUpdateListeners != null) { int numListeners = mUpdateListeners.size(); for (int i = 0; i < numListeners; ++i) { mUpdateListeners.get(i).onAnimationUpdate(this); } } }
在每一个animator的帧,这个函数都会被调用,结合传入的参数:已运行时间分数(fraction)。这个函数将已经运行的分数转为interpolaterd分数,然后转化成一个可用于动画的值(通过evaluator、这个函数通常在animation update的时候调用,但是它也可能在end()函数调用的时候被调用,用来设置property的最终值)。
在这里我们需要理清一下Interpolaterd和evaluator之间的关系。
Interpolator:用来定义animator变化的速率。它让基础的动画效果(渐变、拉伸、平移、旋转)有加速、减速、重复等效果。在源代码中,其实interpolation的作用就是根据某一个时间点来计算它的播放时间分数,具体见官方文档。
evaluator: evaluator全部继承至TypeEvaluator接口,它只有一个evaluate()方法。它用来返回你要进行动画的那个属性在当前时间点所需要的属性值。
我们可以把动画的过程想象成是一部电影的播放,电影的播放中有进度条,Interpolator就是用来控制电影播放频率,也就是快进快退要多少倍速。然后Evaluator根据Interpolator提供的值计算当前播放电影中的哪一个画面,也就是进度条要处于什么位置。
这个函数分三步:
通过Interpolator计算出动画运行时间的分数
变量ValueAnimator中的mValues[i].calculateValue(fraction)(也就是 PropertyValuesHolder对象数组)计算当前动画的值
调用animation的onAnimationUpdate(...)通知animation更新的消息
//PropertyValuesHolder.calculateValue(...) void calculateValue(float fraction) { mAnimatedValue = mKeyframeSet.getValue(fraction); } //mKeyframeSet.getValue public Object getValue(float fraction) { // Special-case optimization for the common case of only two keyframes if (mNumKeyframes == 2) { if (mInterpolator != null) { fraction = mInterpolator.getInterpolation(fraction); } return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(), mLastKeyframe.getValue()); } if (fraction <= 0f) { final Keyframe nextKeyframe = mKeyframes.get(1); final TimeInterpolator interpolator = nextKeyframe.getInterpolator(); if (interpolator != null) { fraction = interpolator.getInterpolation(fraction); } final float prevFraction = mFirstKeyframe.getFraction(); float intervalFraction = (fraction - prevFraction) / (nextKeyframe.getFraction() - prevFraction); return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(), nextKeyframe.getValue()); } else if (fraction >= 1f) { final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2); final TimeInterpolator interpolator = mLastKeyframe.getInterpolator(); if (interpolator != null) { fraction = interpolator.getInterpolation(fraction); } final float prevFraction = prevKeyframe.getFraction(); float intervalFraction = (fraction - prevFraction) / (mLastKeyframe.getFraction() - prevFraction); return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), mLastKeyframe.getValue()); } Keyframe prevKeyframe = mFirstKeyframe; for (int i = 1; i < mNumKeyframes; ++i) { Keyframe nextKeyframe = mKeyframes.get(i); if (fraction < nextKeyframe.getFraction()) { final TimeInterpolator interpolator = nextKeyframe.getInterpolator(); if (interpolator != null) { fraction = interpolator.getInterpolation(fraction); } final float prevFraction = prevKeyframe.getFraction(); float intervalFraction = (fraction - prevFraction) / (nextKeyframe.getFraction() - prevFraction); return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), nextKeyframe.getValue()); } prevKeyframe = nextKeyframe; } // shouldn't reach here return mLastKeyframe.getValue(); }
我们先只关注if (mNumKeyframes == 2)这种情况,因为这种情况最常见。还记的我们使用ObjectAnimator.ofInt(...)传入的int[]数组吗?我们一般就传入动画开始值和结束值,也就是这里的mNumKeyframes ==2 的情况。这里通过mEvaluator来计算,我们看看代码(IntEvaluator.evaluate(...)的代码)。
public Integer evaluate(float fraction, Integer startValue, Integer endValue) { int startInt = startValue; return (int)(startInt + fraction * (endValue - startInt)); }
mEvaluator.evaluate(...)计算后,我们就返回到ValueAnimator.animateValue(...)中,再回退到ValueAnimator.setCurrentPlayTime(...)。最后回到ValueAnimator.start(boolean playBackwards)。终于解析完了setCurrentPlayTime(...)这个函数,总结一下:这个函数主要用来初始化动画的值,当然这个初始化比我们想象中的复杂多了,它主要通过PropertyValuesHolder、Evaluator、Interpolator来进行值的初始化。PropertyValueHolder又通过KeyframeSet来存储需要的值。
我们回到文章开头介绍的ValueAnimator.start(boolean playBackwards)
private void start(boolean playBackwards) { if (Looper.myLooper() == null) { throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } mPlayingBackwards = playBackwards; mCurrentIteration = 0; mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; AnimationHandler animationHandler = getOrCreateAnimationHandler(); animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // This sets the initial value of the animation, prior to actually starting it running setCurrentPlayTime(0); mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); } animationHandler.start(); }
在setCurrentPlayTime(0)后,紧接着就通过notifyStartListeners()通知animation启动的消息。最后通过animationHandler.start()去执行。animationHandler是一个AnimationHandler类型的对象,它实现了runable接口。
//AnimationHandler.start() public void start() { scheduleAnimation(); } //AnimationHandler.scheduleAnimation() private void scheduleAnimation() { if (!mAnimationScheduled) { mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null); mAnimationScheduled = true; } } // Called by the Choreographer. @Override public void run() { mAnimationScheduled = false; doAnimationFrame(mChoreographer.getFrameTime()); }
mHandler.start()最终就是通过mChoreographer.发送给UI系统。这个过程比较复杂,这里不介绍。我们仅仅需要知道,动画中的一帧通过这种方式发送给UI系统后,在UI系统执行完一帧后,又会回调AnimationHandler.run()。那么其实这个过程就相当于,AnimationHandler.start()开始第一次动画的执行→UI系统执行AnimationHandler.run()→UI系统执行完后,回调相关函数→再执行AnimationHandler.run().可以理解为AnimationHandler.run()会一直调用自身多次(当然这是由UI系统驱动的),直至动画结束。
android属性动画小结
一直以来都没有用属性动画,认为可以靠postDelayed()一个任务来不断invalidate这个view从而实现动画效果。
但是今天发现使用属性动画会更流畅
public void rotateyAnimRun(final View view) { ObjectAnimator anim = ObjectAnimator// .ofFloat(view, "zhy", 1.0F, 0.0F)// .setDuration(500);// anim.start(); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //在这里给我们提供了时间接点刷新view,这里会让效果看起来更流畅,究其原因应该是统计学算好的人眼流畅时间结点 float cVal = (Float) animation.getAnimatedValue(); view.setAlpha(cVal); view.setScaleX(cVal); view.setScaleY(cVal); } }); }
Android中多线程编程中AsyncTask类的详细解释
1.Android单线程模型
2.耗时操作放在非主线程中执行
Android主线程和子线程之间的通信封装类:AsyncTask类
1.子线程中更新UI
2.封装、简化异步操作。
3.AsyncTask机制:底层是通过线程池来工作的,当一个线程没有执行完毕,后边的线程是无法执行的。必须等前边的线程执行完毕后,后边的线程才能执行。
AsyncTask类使用注意事项:
1.在UI线程中创建AsyncTask的实例
2.必须在UI线程中调用AsyncTask的execute()方法
3.重写的四个方法是系统自动调用的,不应手动调用。
4.每个AsyncTask只能被执行一次。多次调用将会引发异常。
AsyncTask类和Handler类在异步任务中的比较:
(1).AsyncTask:
优点:简单快捷。过程可控。
缺点:在多个异步任务处理进行UI更新的时候,就显得复杂起来。
(2).Handler类:
优点:结构清晰,功能定义明确。对于多个后台任务时,简单,清晰。
缺点:在单个后台异步处理时,代码相对来说过于多。
以下是从网络中获取图片并展示的例子:
(1).MainActivity.java类:
package com.chengdong.su.asynctaskdemo; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.widget.ImageView; import android.widget.ProgressBar; import com.chengdong.su.asynctaskdemo.interfaces.AsyncT; import com.chengdong.su.asynctaskdemo1.R; public class MainActivity extends Activity { private ImageView mView = null; private ProgressBar mProgressBar = null; private String URL = "http://pic.nipic.com/2007-11-09/2007119122519868_2.jpg"; private AsyncT mT = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); mT = new AsyncT(this); mT.execute(URL); } /** * Activity退出的时候,Task的任务也相应的退出 */ @Override protected void onPause() { super.onPause(); if (mT != null && mT.getStatus() != AsyncTask.Status.FINISHED) { mT.cancel(true); } } private void init() { mView = (ImageView) findViewById(R.id.iv_show); mProgressBar = (ProgressBar) findViewById(R.id.pb_show); } public ImageView getView() { return mView; } public ProgressBar getProgressBar() { return mProgressBar; } }
(2).AsyncT.java类:
package com.chengdong.su.asynctaskdemo.interfaces;
import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import com.chengdong.su.asynctaskdemo.MainActivity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.text.TextUtils; import android.util.Log; import android.view.View; /** * * @author scd * */ public class AsyncT extends AsyncTask<String, Void, Bitmap> { private String TAG = "AsyncT"; private MainActivity mContext; /** * 构造方法 * * @param mContext */ public AsyncT(MainActivity mContext) { super(); this.mContext = mContext; } /** * 在主线程中运行 */ @Override protected void onPreExecute() { super.onPreExecute(); // 显示 mContext.getProgressBar().setVisibility(View.VISIBLE); } /** * 在子线程中运行 */ @Override protected Bitmap doInBackground(String... params) { /** ** 若要实现页面退出时,异步任务也要停止,那么此处就需要根据业务来 进行处理进行判断 if(isCancelled()){ } 此处省略没实现。 **/ String url = params[0]; HttpURLConnection connection = null; InputStream in = null; Bitmap bitmap = null; // 从网络中获取图片 try { // 进行网络连接 connection = (HttpURLConnection) new URL(url).openConnection(); // 获得输入流 in = connection.getInputStream(); // 存放到缓存中 BufferedInputStream bin = new BufferedInputStream(in); // 休眠 Thread.sleep(3000); // 解析缓存中的输入流 bitmap = BitmapFactory.decodeStream(bin); // 关闭数据流 in.close(); bin.close(); return bitmap; } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return null; } /** * 在UI线程中运行 */ @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); mContext.getProgressBar().setVisibility(View.GONE); mContext.getView().setImageBitmap(result); } /** * 在UI线程中运行 */ @Override protected void onProgressUpdate(Void... values) { super.onProgressUpdate(values); } }
另外一篇关于 Android 多线程AsyncTask详解
本篇随笔将讲解一下Android的多线程的知识,以及如何通过AsyncTask机制来实现线程之间的通信。
一、Android当中的多线程
在Android当中,当一个应用程序的组件启动的时候,并且没有其他的应用程序组件在运行时,Android系统就会为该应用程序组件开辟一个新的线程来执行。默认的情况下,在一个相同Android应用程序当中,其里面的组件都是运行在同一个线程里面的,这个线程我们称之为Main线程。当我们通过某个组件来启动另一个组件的时候,这个时候默认都是在同一个线程当中完成的。当然,我们可以自己来管理我们的Android应用的线程,我们可以根据我们自己的需要来给应用程序创建额外的线程。
二、Main Thread 和 Worker Thread
在Android当中,通常将线程分为两种,一种叫做Main Thread,除了Main Thread之外的线程都可称为Worker Thread。
当一个应用程序运行的时候,Android操作系统就会给该应用程序启动一个线程,这个线程就是我们的Main Thread,这个线程非常的重要,它主要用来加载我们的UI界面,完成系统和我们用户之间的交互,并将交互后的结果又展示给我们用户,所以Main Thread又被称为UI Thread。
Android系统默认不会给我们的应用程序组件创建一个额外的线程,所有的这些组件默认都是在同一个线程中运行。然而,某些时候当我们的应用程序需要完成一个耗时的操作的时候,例如访问网络或者是对数据库进行查询时,此时我们的UI Thread就会被阻塞。例如,当我们点击一个Button,然后希望其从网络中获取一些数据,如果此操作在UI Thread当中完成的话,当我们点击Button的时候,UI线程就会处于阻塞的状态,此时,我们的系统不会调度任何其它的事件,更糟糕的是,当我们的整个现场如果阻塞时间超过5秒钟(官方是这样说的),这个时候就会出现 ANR (Application Not Responding)的现象,此时,应用程序会弹出一个框,让用户选择是否退出该程序。对于Android开发来说,出现ANR的现象是绝对不能被允许的。
另外,由于我们的Android UI控件是线程不安全的,所以我们不能在UI Thread之外的线程当中对我们的UI控件进行操作。因此在Android的多线程编程当中,我们有两条非常重要的原则必须要遵守:
绝对不能在UI Thread当中进行耗时的操作,不能阻塞我们的UI Thread
不能在UI Thread之外的线程当中操纵我们的UI元素
三、如何处理UI Thread 和 Worker Thread之间的通信
既然在Android当中有两条重要的原则要遵守,那么我们可能就有疑问了?我们既不能在主线程当中处理耗时的操作,又不能在工作线程中来访问我们的UI控件,那么我们比如从网络中要下载一张图片,又怎么能将其更新到UI控件上呢?这就关系到了我们的主线程和工作线程之间的通信问题了。在Android当中,提供了两种方式来解决线程直接的通信问题,一种是通过Handler的机制(这种方式在后面的随笔中将详细介绍),还有一种就是今天要详细讲解的 AsyncTask 机制。
四、AsyncTask
AsyncTask:异步任务,从字面上来说,就是在我们的UI主线程运行的时候,异步的完成一些操作。AsyncTask允许我们的执行一个异步的任务在后台。我们可以将耗时的操作放在异步任务当中来执行,并随时将任务执行的结果返回给我们的UI线程来更新我们的UI控件。通过AsyncTask我们可以轻松的解决多线程之间的通信问题。
怎么来理解AsyncTask呢?通俗一点来说,AsyncTask就相当于Android给我们提供了一个多线程编程的一个框架,其介于Thread和Handler之间,我们如果要定义一个AsyncTask,就需要定义一个类来继承AsyncTask这个抽象类,并实现其唯一的一个 doInBackgroud 抽象方法。要掌握AsyncTask,我们就必须要一个概念,总结起来就是: 3个泛型,4个步骤。
3个泛型指的是什么呢?我们来看看AsyncTask这个抽象类的定义,当我们定义一个类来继承AsyncTask这个类的时候,我们需要为其指定3个泛型参数:
AsyncTask <Params, Progress, Result>
Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型
Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型
Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型
我们在定义一个类继承AsyncTask类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成Void,例如:
AsyncTask <Void, Void, Void>
4个步骤:当我们执行一个异步任务的时候,其需要按照下面的4个步骤分别执行
onPreExecute(): 这个方法是在执行异步任务之前的时候执行,并且是在UI Thread当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出要给ProgressDialog
doInBackground(Params... params): 在onPreExecute()方法执行完之后,会马上执行这个方法,这个方法就是来处理异步任务的方法,Android操作系统会在后台的线程池当中开启一个worker thread来执行我们的这个方法,所以这个方法是在worker thread当中执行的,这个方法执行完之后就可以将我们的执行结果发送给我们的最后一个 onPostExecute 方法,在这个方法里,我们可以从网络当中获取数据等一些耗时的操作
onProgressUpdate(Progess... values): 这个方法也是在UI Thread当中执行的,我们在异步任务执行的时候,有时候需要将执行的进度返回给我们的UI界面,例如下载一张网络图片,我们需要时刻显示其下载的进度,就可以使用这个方法来更新我们的进度。这个方法在调用之前,我们需要在 doInBackground 方法中调用一个 publishProgress(Progress) 的方法来将我们的进度时时刻刻传递给 onProgressUpdate 方法来更新
onPostExecute(Result... result): 当我们的异步任务执行完之后,就会将结果返回给这个方法,这个方法也是在UI Thread当中调用的,我们可以将返回的结果显示在UI控件上
为什么我们的AsyncTask抽象类只有一个 doInBackground 的抽象方法呢??原因是,我们如果要做一个异步任务,我们必须要为其开辟一个新的Thread,让其完成一些操作,而在完成这个异步任务时,我可能并不需要弹出要给ProgressDialog,我并不需要随时更新我的ProgressDialog的进度条,我也并不需要将结果更新给我们的UI界面,所以除了 doInBackground 方法之外的三个方法,都不是必须有的,因此我们必须要实现的方法是 doInBackground 方法。
五、通过AsyncTask来从网络上下载一张图片
下面我们就通过两个代码示例,来看看如何通过AsyncTask来从网络上下载一张图片,并更新到我们的ImageView控件上。
①下载图片时,弹出一个ProgressDialog,但是不显示实时进度
我们来看看布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="200dp" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:scaleType="fitCenter"/> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/imageView" android:layout_centerHorizontal="true" android:layout_marginTop="41dp" android:text="从网络上下载一张图片" /> </RelativeLayout>
就是很简单的一个ImageView控件和一个Button控件,当点击Button控件时,弹出一个ProgressDialog,然后开启一个异步任务,从网络中下载一张图片,并更新到我们的ImageView上。这里还要注意一点,如果我们要使用手机访问网络,必须还要给其授权才行,在后续的学习当中,将会详细讲解Android当中的授权的知识。我们来看看
AndroidManifest.xml文件:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.xiaoluo.android_asynctast" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <!-- 授权手机能够访问网络 --> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.xiaoluo.android_asynctast.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
接下来我们来看看我们的Activity代码:
public class MainActivity extends Activity { private Button button; private ImageView imageView; private ProgressDialog progressDialog; private final String IMAGE_PATH = "http://developer.android.com/images/home/kk-hero.jpg"; // private final String IMAGE_PATH2 = "http://ww2.sinaimg.cn/mw690/69c7e018jw1e6hd0vm3pej20fa0a674c.jpg"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button)findViewById(R.id.button); imageView = (ImageView)findViewById(R.id.imageView); // 弹出要给ProgressDialog progressDialog = new ProgressDialog(MainActivity.this); progressDialog.setTitle("提示信息"); progressDialog.setMessage("正在下载中,请稍后......"); // 设置setCancelable(false); 表示我们不能取消这个弹出框,等下载完成之后再让弹出框消失 progressDialog.setCancelable(false); // 设置ProgressDialog样式为圆圈的形式 progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 在UI Thread当中实例化AsyncTask对象,并调用execute方法 new MyAsyncTask().execute(IMAGE_PATH); } }); } /** * 定义一个类,让其继承AsyncTask这个类 * Params: String类型,表示传递给异步任务的参数类型是String,通常指定的是URL路径 * Progress: Integer类型,进度条的单位通常都是Integer类型 * Result:byte[]类型,表示我们下载好的图片以字节数组返回 * @author xiaoluo * */ public class MyAsyncTask extends AsyncTask<String, Integer, byte[]> { @Override protected void onPreExecute() { super.onPreExecute(); // 在onPreExecute()中我们让ProgressDialog显示出来 progressDialog.show(); } @Override protected byte[] doInBackground(String... params) { // 通过Apache的HttpClient来访问请求网络中的一张图片 HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(params[0]); byte[] image = new byte[]{}; try { HttpResponse httpResponse = httpClient.execute(httpGet); HttpEntity httpEntity = httpResponse.getEntity(); if(httpEntity != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { image = EntityUtils.toByteArray(httpEntity); } } catch (Exception e) { e.printStackTrace(); } finally { httpClient.getConnectionManager().shutdown(); } return image; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } @Override protected void onPostExecute(byte[] result) { super.onPostExecute(result); // 将doInBackground方法返回的byte[]解码成要给Bitmap Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length); // 更新我们的ImageView控件 imageView.setImageBitmap(bitmap); // 使ProgressDialog框消失 progressDialog.dismiss(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } }
我们来看看效果图:
②带有进度条更新的下载一张网络图片
下面这个代码示例,将会在下载图片的时候,显示进度条的更新,配置文件都不变,我们来看看Activity代码:
public class MainActivity extends Activity { private Button button; private ImageView imageView; private ProgressDialog progressDialog; private final String IMAGE_PATH = "http://developer.android.com/images/home/kk-hero.jpg"; // private final String IMAGE_PATH2 = "http://ww2.sinaimg.cn/mw690/69c7e018jw1e6hd0vm3pej20fa0a674c.jpg"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button)findViewById(R.id.button); imageView = (ImageView)findViewById(R.id.imageView); // 弹出要给ProgressDialog progressDialog = new ProgressDialog(MainActivity.this); progressDialog.setTitle("提示信息"); progressDialog.setMessage("正在下载中,请稍后......"); // 设置setCancelable(false); 表示我们不能取消这个弹出框,等下载完成之后再让弹出框消失 progressDialog.setCancelable(false); // 设置ProgressDialog样式为水平的样式 progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new MyAsyncTask().execute(IMAGE_PATH); } }); } /** * 定义一个类,让其继承AsyncTask这个类 * Params: String类型,表示传递给异步任务的参数类型是String,通常指定的是URL路径 * Progress: Integer类型,进度条的单位通常都是Integer类型 * Result:byte[]类型,表示我们下载好的图片以字节数组返回 * @author xiaoluo * */ public class MyAsyncTask extends AsyncTask<String, Integer, byte[]> { @Override protected void onPreExecute() { super.onPreExecute(); // 在onPreExecute()中我们让ProgressDialog显示出来 progressDialog.show(); } @Override protected byte[] doInBackground(String... params) { // 通过Apache的HttpClient来访问请求网络中的一张图片 HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(params[0]); byte[] image = new byte[]{}; try { HttpResponse httpResponse = httpClient.execute(httpGet); HttpEntity httpEntity = httpResponse.getEntity(); InputStream inputStream = null; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); if(httpEntity != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { // 得到文件的总长度 long file_length = httpEntity.getContentLength(); // 每次读取后累加的长度 long total_length = 0; int length = 0; // 每次读取1024个字节 byte[] data = new byte[1024]; inputStream = httpEntity.getContent(); while(-1 != (length = inputStream.read(data))) { // 每读一次,就将total_length累加起来 total_length += length; // 边读边写到ByteArrayOutputStream当中 byteArrayOutputStream.write(data, 0, length); // 得到当前图片下载的进度 int progress = ((int)(total_length/(float)file_length) * 100); // 时刻将当前进度更新给onProgressUpdate方法 publishProgress(progress); } } image = byteArrayOutputStream.toByteArray(); inputStream.close(); byteArrayOutputStream.close(); } catch (Exception e) { e.printStackTrace(); } finally { httpClient.getConnectionManager().shutdown(); } return image; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); // 更新ProgressDialog的进度条 progressDialog.setProgress(values[0]); } @Override protected void onPostExecute(byte[] result) { super.onPostExecute(result); // 将doInBackground方法返回的byte[]解码成要给Bitmap Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length); // 更新我们的ImageView控件 imageView.setImageBitmap(bitmap); // 使ProgressDialog框消失 progressDialog.dismiss(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } }
我们来看看效果图:
这样我们就能够通过AsyncTask来实现从网络中下载一张图片,然后将其更新到UI控件中,并时时刻刻的更新当前的进度这个功能了。
六、AsyncTask的重要知识点
在上面两节已经详细讲解了AsyncTask的工作原理了,这里我们还要补充一下AsyncTask的一些其他知识点:
1.Cancelling a Task
我们可以在任何时刻来取消我们的异步任务的执行,通过调用 cancel(boolean)方法,调用完这个方法后系统会随后调用 isCancelled() 方法并且返回true。如果调用了这个方法,那么在 doInBackgroud() 方法执行完之后,就不会调用 onPostExecute() 方法了,取而代之的是调用 onCancelled() 方法。为了确保Task已经被取消了,我们需要经常调用 isCancelled() 方法来判断,如果有必要的话。
2.在使用AsyncTask做异步任务的时候必须要遵循的原则:
AsyncTask类必须在UI Thread当中加载,在Android Jelly_Bean版本后这些都是自动完成的
AsyncTask的对象必须在UI Thread当中实例化
execute方法必须在UI Thread当中调用
不要手动的去调用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法,这些都是由Android系统自动调用的
AsyncTask任务只能被执行一次
到此,有关AsyncTask的总结就到此为止了,本篇随笔主要讲解了Android中的多线程知识,并且详细地讲解了 AsyncTask 异步任务的概念和实现机制,并通过实例来了解 AsyncTask 的执行过程,最后还补充了 AsyncTask 的一些重要知识点,包括如何取消一个 AsyncTask 以及,我们在使用 AsyncTask 时所必须遵循的规则。
相关文章
- 下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
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
- 这篇文章主要介绍了iOS新版微信底部返回横条问题的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
- 下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
- java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
- TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
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
- 下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
- 首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
- 下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
- 这篇文章主要介绍了iOS新版微信底部工具栏遮挡问题完美解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
用Intel HAXM给Android模拟器Emulator加速
Android 模拟器 Emulator 速度真心不给力,, 现在我们来介绍使用 Intel HAXM 技术为 Android 模拟器加速,使模拟器运行度与真机比肩。 周末试玩了一下在Eclipse中使...2016-09-20- 在安卓开发时我碰到一个问题就是需要实现全屏,但又需要我们来判断出用户是使用了全屏或非全屏了,下面我分别找了两段代码,大家可参考。 先来看一个android屏幕全屏实...2016-09-20
Android开发中布局中的onClick简单完成多控件时的监听的利与弊
本文章来为各位介绍一篇关于Android开发中布局中的onClick简单完成多控件时的监听的利与弊的例子,希望这个例子能够帮助到各位朋友. 首先在一个控件加上这么一句:and...2016-09-20