昨天同事问我一个问题,在Thread中去更新一个Button的状态可行吗?我说当然不行。他告诉我说,那为什么他的程序不会奔溃,我过去一看果然没有奔溃,甚是奇怪。难道是我记错了?于是我Google了一下。发现Android的开发文档中确实说这样是不行的啊。
http://developer.android.com/guide/components/processes-and-threads.html
public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start(); }
it violates the second rule of the single-threaded model: do not access the Android UI toolkit from outside the UI thread—this sample modifies the ImageView from the worker thread instead of the UI thread. This can result in undefined and unexpected behavior, which can be difficult and time-consuming to track down.
再看看我同事的代码:
public class MainActivity extends Activity { private Button btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button)findViewById(R.id.btn); startAThread(); } private void startAThread() { new Thread(new Runnable() { @Override public void run() { btn.setText("Hello Michael!"); } }).start(); } }
没有发现什么大的区别,唯一的区别就在于执行的地方不太一样,Google官方文档是在onClick的时候去执行,而同事的代码是在onCreate的时候执行。于是我做了一些修改,增加一个onClick事件:
public class MainActivity extends Activity { private Button btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button)findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startAThread(); } }); startAThread(); } private void startAThread() { new Thread(new Runnable() { @Override public void run() { btn.setText("Hello Michael!"); } }).start(); } }
果然,程序在onClick的时候crash了。
但是为什么呢?在onCreate的时候去不会crash呢?
我们看一下错误:
这个错误相信大家都有遇到过,意思就是说不能在非UI线程中去更新UI控件。顺着这个错误,
到源码中去查找一下ViewRootImpl.java这个文件。你可以看到这个异常:
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
这个方法就是用来判断更新UI控件的时候是否在UI线程中进行,如果不是的话就会报错。也就是onCreate的时候这个判断没有被执行到,因此onCreate中启动的Thread并不会导致程序的crash。
不知道这算不算Google的一个bug呢,虽然这样没有什么问题,但是也不建议大家这样做。因为这样可能会卡住UI线程,而且也没有必要这样做,Activity.runOnUiThread,View.Post,或者麻烦一点用AsyncTask,Handler这样的方式来处理都是Google比较推荐的做法。
差别:
1、initWithFormat是实例办法
只能经由过程 NSString* str = [[NSString alloc] initWithFormat:@"%@",@"Hello World"] 调用,然则必须手动release来开释内存资料
2、stringWithFormat是类办法
自动释放内存
可以直接用 NSString* str = [NSString stringWithFormat:@"%@",@"Hello World"] 调用,内存经管上是autorelease的,不用手动显式release
别的国外有个贴子对此有专门评论辩论(http://www.iphonedevsdk.com/forum/iphone-sdk-development/29249-nsstring-initwithformat-vs-stringwithformat.html)
并且提出了一个常见错误:
label.text = [[NSString alloc] initWithFormat:@"%@",@"abc"];
最后在dealloc中将label给release掉
然则仍然会产生内存泄漏!
原因在于:用label.text = ...时,实际是隐式调用的label的setText办法,这会retain label内部的字符串变量text(哪怕这个字符串的内容跟传进来的字符串内容雷同,但体系仍然当成二个不合的字符串对象),所以最后release label时,实际上只开释了label内部的text字符串,然则最初用initWithFormat生成的字符串并未开释,终极造成了泄漏。
解决办法有二个:
1、
NSString * str = [[NSString alloc] initWithFormat:@"%@",@"abc"];
label.text = str;
[str release]
最后在dealloc中再[label release]
2、
label.text = [NSString stringWithFormat:@"%@",@"abc"];
然后剩下的工作交给NSAutoreleasePool
最后,若是你不断定你的代码是否有内存泄漏题目,可以用Xcode中的Build-->Build And Analyze 做初步的搜检
。有冲突的话,git会提示那个文件中有冲突,比如有如下冲突:
<<<<<<< HEAD:test.c
printf (“test1″);
=======
printf (“test2″);
>>>>>>> issueFix:test.c
可以看到 ======= 隔开的上半部分,是 HEAD(即 master 分支,在运行 merge 命令时检出的分支)中的内容,下半部分是在 issueFix 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。比如你可以通过把这段内容替换为下面这样来解决:
printf (“test2″);
这个各采纳了两个分支中的一部分内容,而且删除了 <<<<<<<,=======,和>>>>>>> 这些行。在解决了所有文件里的所有冲突后,运行 git add 将把它们标记为已解决(resolved)。然后使用git commit命令进行提交,merge就算完成了