- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { UIImage *image = [UIImage imageNamed:@"test.png"]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; CGRect frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height); button.frame = frame; [button addTarget:self action:@selector(accessoryViewButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; [button setBackgroundImage:image forState:UIControlStateNormal]; cell.accessoryView = button; }
根据按钮的位置获得行数:
- (void)accessoryViewButtonClicked:(id)sender { CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.tableView]; NSIndexPath *hitIndex = [self.tableView indexPathForRowAtPoint:hitPoint]; //... }
——彭晓林
欢迎交流:196568501(QQ)
注:此方法适合于SPI 扩展,I2C扩展 UART 等总线的扩展
1. MCU 为主控器
2. CLK 为时钟, DATA 为数据
3. CS1-C4 为片选
4. HUB为扩展芯片
5. CLK1-CLK4 为扩展时钟, DATA1-DATA4 为扩展数据
Android 中最常见的引起线程不安全的操作就是在非主线程中操作线程的UI,在 AsyncTask 出现之前,一般采用Handler 机制异步操作UI。
做过Java的朋友都知道,java的异步线程源自于开源的Concurrent框架,AsyncTask 也正是移植自Concurrent框架。
关于 AsyncTask 和 Handler 的比较和各自用法,参考附录文档即可,在此不作累述,下面描述一种比较特别的场景:在程序启动后的第一个欢迎界面读取配置信息。
几个程序的声明:
异步任务执行类:
public class ExecuteAsyncTask extends AsyncTask<BaseTask, Integer, Object>
private TaskListener mListener;
基类:
public class BasicActivity extends Activity implements TaskListener
protected void executeTask(BaseTask baseTask, TaskListener taskListener)
{
ExecuteAsyncTask mAsynctask = new ExecuteAsyncTask(baseTask, taskListener);
mAsynctask.execute((BaseTask) null);
}
@Override
public void onTaskStart(Object object)
{}
@Override
public void onTaskFinish(Object object)
{}
欢迎界面:
public class WelcomeActivity extends BaseActivity
public void onTaskFinish(Object object)
{}
主界面:
public class MainActivity extends BaseActivity
public void onTaskFinish(Object object)
{}
如果我们直接用 Handler 发送一条延时执行的消息,来发起一个联网读取配置信息的任务(AsyncTask );信息获取成功后再通知软件,进入程序主界面。这时候会造成线程堵塞,也就是同步操作,如果网络异常,则会出现黑屏的情况。所以Handler需要与线程配合使用,提供两种写法:
第一种写法:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.welcome_layout); HandlerThread thread = new HandlerThread(WelcomeActivity.class.getSimpleName()); thread.start(); mHandler = new MyHandler(thread.getLooper()); mHandler.sendEmptyMessage(MESSAGE_WHAT_CONFIG)
第二种写法:
class AsyncConfigThread extends Thread { public void run() { mHandler.sendEmptyMessageDelayed(MESSAGE_WHAT_CONFIG, 500); } } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.welcome_layout); new AsyncConfigThread().start();
第二种写法比较常见,略过。
注意看,第一种写法中新创建了一个HandlerThread,然后将它的Looper传入到Handler中,相当于重新申请了一个消息队列;运行后发现线程安全问题:
WelcomeActivity(Thread id = 1) and BasicActivity(Thread id = 1)
MainActivity(Thread id = 10) and BasicActivity(Thread id = 1)
10-25 23:38:21.794 E/BaseActivity(24991): onCreate############################thread id = 1
10-25 23:38:25.494 E/WelcomeActivity(24991): executeTask############################thread id = 1
10-25 23:38:22.004 E/BaseActivity(24991): executeTask############################thread id = 10//新的消息队列线程号
10-25 23:38:27.384 I/ActivityManager( 1563): Starting: Intent { cmp=com.tfb.test/com.tfb.activity.MainActivity } from pid 24991
10-25 23:38:27.404 E/BaseActivity(24991): onCreate############################thread id = 1
10-25 23:38:27.564 E/BaseActivity(24991): executeTask############################thread id = 1
10-25 23:38:27.574 E/BaseActivity(24991): executeTask############################thread id = 1
10-25 23:38:27.574 E/BaseActivity(24991): executeTask############################thread id = 1
10-25 23:38:27.574 E/MainActivity(24991): ############################thread id = 1//此时线程号还是1,正确
10-25 23:38:29.704 E/MainActivity(24991): ############################thread id = 10//此时线程号发生了变化。。。
10-25 23:38:29.704 E/BaseActivity(24991): executeTask############################thread id = 1//BaseActivity的线程号一直保持1
10-25 23:48:35.754 E/AndroidRuntime(28680): FATAL EXCEPTION:
WelcomeActivity
10-25 23:48:35.754 E/AndroidRuntime(28680): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.ViewRoot.checkThread(ViewRoot.java:2932)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.ViewRoot.invalidateChild(ViewRoot.java:642)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:668)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.ViewGroup.invalidateChild(ViewGroup.java:2511)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.View.invalidate(View.java:5279)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.widget.TextView.setPadding(TextView.java:1595)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.View.setBackgroundDrawable(View.java:7571)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.View.setBackgroundResource(View.java:7535)
10-25 23:48:35.754 E/AndroidRuntime(28680): at com.tfb.activity.MainActivity.onTaskFinished(MainActivity.java:445)
注意看异常信息,MainActivity.java抛出与WelcomeActivity的线程安全异常,在第二个程序(都是基于BasicActivity的异步消息onTaskFinished()中抛出),很有意思;更有意思的是,同样的代码,在Android 4.0以上系统不存在这样的线程安全问题,只在Android 2.3.x上才会存在线程安全问题。:D
我们在使用这些应用技术之前,还是要熟悉下Looper的消息机制:
Android通过Looper、Handler来实现消息循环机制。Android的消息循环是针对线程的,每个线程都可以有自己的消息队列和消息循环。Android系统中的Looper负责管理线程的消息队列和消息循环。通过Looper.myLooper()得到当前线程的Looper对象,通过Looper.getMainLooper()得到当前进程的主线程的Looper对象。
Android系统中的视图组件并不是线程安全的,如果要更新视图,必须在主线程中更新,不可以在子线程中执行更新的操作。
参考文档:
浅析Android中的消息机制 http://blog.csdn.net/liuhe688/article/details/6407225
详解Android中AsyncTask的使用 http://blog.csdn.net/liuhe688/article/details/6532519
AsyncTask和Handler对比 http://www.cnblogs.com/devinzhang/archive/2012/02/13/2350070.html