android-更新UI的几种方式
更新UI的几种方式
- 前言
- 更新UI的4种方法
- Handler
- view.post
- runOnUiThread
- AsyncTask
前言
在android中,为了避免在子线程中更新UI造成多线程安全问题(View中更新UI的方法大多不是同步方法),就将UI的更新切换到主线程更新,使用的就是android的Handler机制。在android中可以直接使用Handler进行更新UI,也可以使用Handler的实现进行更新UI,接下来我们盘点下android中更新UI的几种方法,也算是对Handler学习的使用。
更新UI的4种方法
这里介绍更新UI的4种方法,Handler、view.post、runOnUiThread、AsyncTask
,首先介绍其使用方法,后面在介绍其相关源码。
首先我们知道在android中使用子线程直接更新UI会抛出异常,对于抛出异常的类就是ViewRootImpl
类的void checkThread()
方法,这个方法就是判断两个线程是否是一个线程。mThread
是在ViewRootImpl
的构造方法
中调用 Thread.currentThread()创建
,获取的当前线程主线程。Thread.currentThread()
的作用就是返回一个当前正在执行的线程
,这是一个Thread类的native方法。当构造方中的主线程不等于当前运行的线程时,说明是在非主线程更新的UI,将抛出异常。
void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}}
接下来我们看更新UI的几种写法,虽然有点像孔乙己教茴香豆的茴字怎么写,但这不是重点,重点是后面我们要看的其源码具体是怎么玩的。
public class RefreshViewActivity extends Activity implements View.OnClickListener {/*** Handler*/private Button mBtHandler;/*** postView*/private Button mBtPostView;/*** AsyncTask*/private Button mBtAsyncTask;/*** runOnUiThread*/private Button mBtRunOnUiThread;private EditText mEtData;private Handler handler;/*** 取消AsyncTask*/private Button mBtAsyncTaskClean;private ProgressBar mProgressBar;private MyTask mTask;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_refresh);initView();/*** 造成Handler内存泄漏两个原因** 1、Handler内部类的创建方式导致隐式持有外部Activity的引用,当Handler所伴随的线程无法及时发送消息,但此时又关闭了Activity* 那么线程将持有handler,handler由持有Activity,导致内存无法回收造成泄漏* 2、当使用postDelayed延迟发送消息,导致Message占用MessageQueue、message、handler,activity一条消息链,导致Activity无法回收* 避免内存泄漏 使用静态内部类 或弱引用并在Activity销毁的时候回收Message*/handler = new MyHandler(this);/*** AsyncTask子类的实例必须在UI线程中创建* AsyncTask也会有和Handler相识的内存泄漏问题*/mTask = new MyTask();}private class MyHandler extends Handler {WeakReference<RefreshViewActivity> weakReference;Context context;public MyHandler(RefreshViewActivity activity) {context = activity;weakReference = new WeakReference(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (weakReference.get() != null) {if (msg.what == 1) {mEtData.setText("handler");}}}}private void initView() {mBtHandler = (Button) findViewById(R.id.bt_handler);mBtHandler.setOnClickListener(this);mBtPostView = (Button) findViewById(R.id.bt_postView);mBtPostView.setOnClickListener(this);mBtAsyncTask = (Button) findViewById(R.id.bt_asyncTask);mBtAsyncTask.setOnClickListener(this);mBtRunOnUiThread = (Button) findViewById(R.id.bt_runOnUiThread);mBtRunOnUiThread.setOnClickListener(this);mEtData = findViewById(R.id.et_data);mBtAsyncTaskClean = (Button) findViewById(R.id.bt_asyncTask_clean);mBtAsyncTaskClean.setOnClickListener(this);mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.bt_handler:onHandler();break;case R.id.bt_postView:onPostView();break;case R.id.bt_runOnUiThread:onRunOnUiThread();break;case R.id.bt_asyncTask:/*** :* 手动调用execute(Params... params) 从而执行异步线程任务* 注:* a. 必须在UI线程中调用* b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常* c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()* d. 不能手动调用上述方法*/mTask.execute();break;case R.id.bt_asyncTask_clean:/*** 取消一个正在执行的任务,onCancelled方法将会被调用* 先点击取消销毁后,再执行会报错*/mTask.cancel(true);break;}}private void onRunOnUiThread() {/*** 执行完run()方法Thread就进入到死亡状态,当run()方法存在柱塞的时候,要注意回收线程*/Thread thread = new Thread(new Runnable() {@Overridepublic void run() {runOnUiThread(new Runnable() {@Overridepublic void run() {mEtData.setText("runOnUiThread");}});}});thread.start();}private void onPostView() {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {mEtData.post(new Runnable() {@Overridepublic void run() {mEtData.setText("onPostView");}});}});thread.start();}private void onHandler() {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {Message message = handler.obtainMessage();message.what = 1;handler.sendMessage(message);}});thread.start();}/*** 步骤1:创建AsyncTask子类* 注:* a. 继承AsyncTask类* b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替* 此处指定为:输入参数 = String类型、执行进度 = Integer类型、执行结果 = String类型* c. 根据需求,在AsyncTask子类内实现核心方法*/private class MyTask extends AsyncTask<String, Integer, String> {/*** 方法1:onPreExecute()* 作用:执行 线程任务前的操作*/@Overrideprotected void onPreExecute() {mEtData.setText("加载中");// 执行前显示提示}/*** 方法2:doInBackground()* 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果* 此处通过计算从而模拟“加载进度”的情况** @param strings* @return*/@Overrideprotected String doInBackground(String... strings) {try {int count = 0;int length = 1;while (count < 99) {count += length;// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()publishProgress(count);// 模拟耗时任务//中途取消的时候会 java.lang.InterruptedExceptionThread.sleep(50);}} catch (InterruptedException e) {e.printStackTrace();}return null;}/*** 方法3:onProgressUpdate()* 作用:在主线程 显示线程任务执行的进度** @param progresses*/@Overrideprotected void onProgressUpdate(Integer... progresses) {mProgressBar.setProgress(progresses[0]);mEtData.setText("loading..." + progresses[0] + "%");}/*** 方法4:onPostExecute()* 作用:接收线程任务执行结果、将执行结果显示到UI组件** @param result*/@Overrideprotected void onPostExecute(String result) {// 执行完毕后,则更新UImEtData.setText("加载完毕");}/*** 方法5:onCancelled()* 作用:将异步任务设置为:取消状态*/@Overrideprotected void onCancelled() {mEtData.setText("已取消");mProgressBar.setProgress(0);}}@Overrideprotected void onDestroy() {super.onDestroy();handler.removeCallbacksAndMessages(null);}
}
上面的代码很简单XML文件我就不贴出来了,写了4种更新UI的方法,以及Handler关于内存泄漏的原因和解决方法,以及AsyncTask的使用注意事项,AsyncTask部分代码参考于其他博客。接下来我们看看其相关源码。
Handler
对于handler的理解可以看之前写的
android-Handler源码解析
android-重新理解Handler
view.post
根据方法调用方法我基本知道post(Runnable action)方法位于view类,下面的代码意思就是
1、当 attachInfo不为空
的时候,直接调用其handler的post(Runnable action)
方法
2、当attachInfo为空
,会调用getRunQueue().post(action)将其加入到队列
当中,直到后面再调用,HandlerActionQueue可以自行看看,很简单,就是一个数组,将Runnable封装后保存。
1、这里我们要知道AttachInfo什么时候创建
2、加入队列后什么什么时候再调用
3、以及什么时候mRunQueue == null(后面学习View的时候再讲解)
private HandlerActionQueue mRunQueue;public boolean post(Runnable action) {final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {return attachInfo.mHandler.post(action);}// Postpone the runnable until we know on which thread it needs to run.// Assume that the runnable will be successfully placed after attach.getRunQueue().post(action);return true;}
...private HandlerActionQueue getRunQueue() {if (mRunQueue == null) {mRunQueue = new HandlerActionQueue();}return mRunQueue;}
...
我看看attachInfo.mHandler.post(action)
;其实调用的是handler的post(action)
方法,那么我们回顾下。
当我们直接调用handler的post(Runnable r)方法,就是延迟0秒发送了一个Message, 并将传递的Runnable,赋值给了 m.callback
public class Handler {...public final boolean post(Runnable r){return sendMessageDelayed(getPostMessage(r), 0);}private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}...}
AttachInfo是View的静态内部类,AttachInfo
的handler
就是在其构造方法中传递过来的
,首先看看AttachInfo是在哪里得到的,在View中找到两处,再看看什么时候调用这两个方法。对于这两个方法,好像很难在AS中直接找到,所以最好是下载源码直接在工具里面找,可以找到在ViewRootImpl中有相关代码
。
private HandlerActionQueue mRunQueue;void dispatchAttachedToWindow(AttachInfo info, int visibility) {mAttachInfo = info;...if (mRunQueue != null) {mRunQueue.executeActions(info.mHandler);mRunQueue = null;}...}
void dispatchDetachedFromWindow() {...mAttachInfo = null;...}
在ViewRootImpl
类的构造方法中,可以看到AttachInfo的初始化
,ViewRootImpl是View和Window的纽带,后面在介绍View的相关知识在详细讲解。先看看这次讲解的部分代码
在下面代码中我们可以看到,
1、AttachInfo的创建是在ViewRootImpl的构造方法中
,在ViewRootImpl
中有一个Handler的实现类ViewRootHandler
,其获取的就是MainLooper,也是这类创建的Handler实例并传递到AttachInfo中的。
2、得到AttachInfo后要进行传递到到View
中,看ViewRootImpl
的performTraversals()方法
,其中有一句host.dispatchAttachedToWindow(mAttachInfo, 0)
;host就是一个View
,这样在dispatchAttachedToWindow()
方法里就得到了AttachInfo。
3、在 attachInfo.mHandler.post(action)
调用后,Looper开始消息循环,最后得到消息,调用handler的dispatchMessage(Message message)方法,如果msg.callback != null,则直接调用其run()方法,开始更新UI
4、对于添加到缓存中的Runnable,则是在dispatchAttachedToWindow方法中被调用
,首先对mRunQueue判空,然后执行mRunQueue.executeActions(info.mHandler)
;请看上面源码。
public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {...public ViewRootImpl(Context context, Display display) {mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);}private final static int MSG_INVALIDATE = 1;
...final class ViewRootHandler extends Handler {@Overridepublic String getMessageName(Message message) {switch (message.what) {case MSG_INVALIDATE:return "MSG_INVALIDATE";...case MSG_DRAW_FINISHED:return "MSG_DRAW_FINISHED";}return super.getMessageName(message);}@Overridepublic boolean sendMessageAtTime(Message msg, long uptimeMillis) {if (msg.what == MSG_REQUEST_KEYBOARD_SHORTCUTS && msg.obj == null) {// Debugging for b/27963013throw new NullPointerException("Attempted to call MSG_REQUEST_KEYBOARD_SHORTCUTS with null receiver:");}return super.sendMessageAtTime(msg, uptimeMillis);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_INVALIDATE:((View) msg.obj).invalidate();break;...case MSG_DRAW_FINISHED: {pendingDrawFinished();} break;}}}final ViewRootHandler mHandler = new ViewRootHandler();
...
}
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
runOnUiThread
runOnUiThread是Activity类的方法,看了下源码。这就过分了,直接Handler更新,很简单。不过这个Activity类真是绝了,实现了很多接口。我们看看相关代码
源码很简单,判断下是否等于mUiThread,不等于就使用mHandler更新,等于就直接调用action.run(),mUiThread的获取在在attach(…)方法中。
public class Activity extends ContextThemeWrapperimplements LayoutInflater.Factory2,Window.Callback, KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks2,Window.OnWindowDismissedCallback, WindowControllerCallback,AutofillManager.AutofillClient {... final Handler mHandler = new Handler();
...public final void runOnUiThread(Runnable action) {if (Thread.currentThread() != mUiThread) {mHandler.post(action);} else {action.run();}}...final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window, ActivityConfigCallback activityConfigCallback) {...mUiThread = Thread.currentThread();...}
...}
AsyncTask
对于AsyncTask其实是一个比较要学习的东西,因为其包含了两个很重要的东西,第一个就是ThreadPool
,然后就是Handler
。那这里我们主要介绍的是Handler的相关知识。在后面再学习Thread和ThreadPool的时候再后头介绍AsyncTask。
在上面使用AsyncTask中我们new一个内部类MyTask,并且在 doInBackground()方法中调用publishProgress(count)更新进度,
mTask.execute()开始执行异步操作, mTask.cancel(true)取消任务。
比较关键的是在AsyncTask的构造方法
1、首先获取一个运行在主线程的Handler
2、构造一个可执行的异步线程FutureTask(mFuture)
,FutureTask这个异步任务主要是可以取消
,这个在我们mTask.cancel(true)
就会执行其相关方法。
3、执行 外部执行mTask.execute(),会得到sDefaultExecutor和参数
,sDefaultExecutor是执行SerialExecutor静态内部类
,后赋值给sDefaultExecutor
的,也就是最后执行的是SerialExecutor
。在 executeOnExecutor(Executor exec,
Params… params)方法中可以看到,先调用 onPreExecute()方法
,在真正的执行异步任务exec.execute(mFuture)
,
4、再看真正的执行execute的 SerialExecutor类,在SerialExecutor
中的execute
方法,传递过来一个r
,调用了 r.run()
;这里要知道r
,就是FutureTask
,这个 r.run()
后就调用scheduleNext()
开始真正启动了异步任务,mTasks取出一个Runnable将其交给mActive,然后再交由THREAD_POOL_EXECUTOR线程池来执行
5、r.run()执行后,其实要看看FutureTask类中的run方法,下面贴出源码。在newFutureTask
的时候已经将WorkerRunnable
传递进去了,WorkerRunnable其实是一个Callable
,在FutureTask的run()方法里面有看到result = c.call();
这样其实调用的WorkerRunnable的call()方法
,这样就执行到了doInBackground()
方法,在我们实现的doInBackground中调用 publishProgress(count)更新进度UI
,并且执行postResult(result)
,发送消息给Handler
。Handler根据msg.whatk开始执行 result.mTask.finish(result.mData[0])
,也就是AsyncTask
的finish(Result result)
方法。
6、finish(Result result)方法比较简单,判断是否是取消
,是取消执行 onCancelled(result)
;,不是取消就完成执行回调 onPostExecute(result)
7、执行mTask.cancel(true)
,首先将取消置为true,(mCancelled.set(true))
。取消操作大家去看FutureTask源码可以看到,肯定会调用done()
方法,后头再看AsyncTask中new的FutureTask,其中done()也会调用postResultIfNotInvoked()
,其中会判断当前异步 任务是否真正开始,如果异步已经开始则不能取消
,如果没有开始则发送消息给handler,同样执行result.mTask.finish(result.mData[0])
,这样就再回到上面6
进行判断。
到这里AsyncTask的基本逻辑就全部走了一遍,主要没有讲解线程池这一块内容,对于AsyncTask的实现方式走了流水其实并不够,后面要学习其设计优雅的地方,这样才是学到了精华。走流水其实只是学习的第一步,还是要消化精华。
public abstract class AsyncTask<Params, Progress, Result> {public static final Executor SERIAL_EXECUTOR = new SerialExecutor();// AsyncTask默认使用SERIAL_EXECUTOR作为它的Executor,所以默认情况下AsyncTask是串行而非并行执行的private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;// 通过handler发布result的message code private static final int MESSAGE_POST_RESULT = 0x1;// 通过handler发布progress的message codeprivate static final int MESSAGE_POST_PROGRESS = 0x2;// 任务是否被取消的标识private final AtomicBoolean mCancelled = new AtomicBoolean();// 任务是否真正开始了的标识private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
...private static Handler getMainHandler() {synchronized (AsyncTask.class) {if (sHandler == null) {sHandler = new InternalHandler(Looper.getMainLooper());}return sHandler;}}
...public AsyncTask() {this((Looper) null);}public AsyncTask(@Nullable Handler handler) {this(handler != null ? handler.getLooper() : null);}/*** Creates a new asynchronous task. This constructor must be invoked on the UI thread.** @hide*/public AsyncTask(@Nullable Looper callbackLooper) {mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()? getMainHandler(): new Handler(callbackLooper);mWorker = new WorkerRunnable<Params, Result>() {public Result call() throws Exception {// 将任务开始标识设为truemTaskInvoked.set(true);Result result = null;// 将call方法设置为后台线程级别 try {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);// 在线程池中执行doInBackground()方法,并返回resultresult = doInBackground(mParams);Binder.flushPendingCommands();} catch (Throwable tr) {mCancelled.set(true);throw tr;} finally {// 将结果交由postResult()方法postResult(result);}return result;}};mFuture = new FutureTask<Result>(mWorker) {@Overrideprotected void done() {try {postResultIfNotInvoked(get());} catch (InterruptedException e) {android.util.Log.w(LOG_TAG, e);} catch (ExecutionException e) {throw new RuntimeException("An error occurred while executing doInBackground()",e.getCause());} catch (CancellationException e) {postResultIfNotInvoked(null);}}};}
...@MainThreadpublic final AsyncTask<Params, Progress, Result> execute(Params... params) {return executeOnExecutor(sDefaultExecutor, params);}@MainThreadpublic final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {if (mStatus != Status.PENDING) {switch (mStatus) {case RUNNING:throw new IllegalStateException("Cannot execute task:"+ " the task is already running.");case FINISHED:throw new IllegalStateException("Cannot execute task:"+ " the task has already been executed "+ "(a task can be executed only once)");}}mStatus = Status.RUNNING;onPreExecute();mWorker.mParams = params;exec.execute(mFuture);return this;}private static class SerialExecutor implements Executor {// 一个双端队列,用来存放任务Rnnable类final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();// 当前正在执行的Runnable Runnable mActive; 我们调用SerialExecutor的execute()会将Runnable再次封装,将其放入到双端队列mTasks的最后面。判断mActive是否为null来判断当前是否有任务在执行,如果没有任务在执行那么从任务队列中去一个任务去执行,如果有任务在执行则等待这个任务执行完毕后在finally中去取下一个任务public synchronized void execute(final Runnable r) {mTasks.offer(new Runnable() {public void run() {try {r.run();} finally {scheduleNext();}}});if (mActive == null) {scheduleNext();}}// mTasks取出一个Runnable将其交给mActive,然后再交由THREAD_POOL_EXECUTOR线程池来执行protected synchronized void scheduleNext() {if ((mActive = mTasks.poll()) != null) {THREAD_POOL_EXECUTOR.execute(mActive);}}}
...public final boolean cancel(boolean mayInterruptIfRunning) {mCancelled.set(true);return mFuture.cancel(mayInterruptIfRunning);}private void postResultIfNotInvoked(Result result) {final boolean wasTaskInvoked = mTaskInvoked.get();if (!wasTaskInvoked) {postResult(result);}}private Result postResult(Result result) {@SuppressWarnings("unchecked")Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,new AsyncTaskResult<Result>(this, result));message.sendToTarget();return result;}private static class InternalHandler extends Handler {public InternalHandler(Looper looper) {super(looper);}@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})@Overridepublic void handleMessage(Message msg) {AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;switch (msg.what) {case MESSAGE_POST_RESULT:// There is only one resultresult.mTask.finish(result.mData[0]);break;case MESSAGE_POST_PROGRESS:result.mTask.onProgressUpdate(result.mData);break;}}}...private void finish(Result result) {if (isCancelled()) {onCancelled(result);} else {onPostExecute(result);}mStatus = Status.FINISHED;}...}
public class FutureTask<V> implements RunnableFuture<V> {...public FutureTask(Callable<V> callable) {if (callable == null)throw new NullPointerException();this.callable = callable;this.state = NEW; // ensure visibility of callable}public void run() {if (state != NEW ||!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))return;try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;setException(ex);}if (ran)set(result);}} finally {// runner must be non-null until state is settled to// prevent concurrent calls to run()runner = null;// state must be re-read after nulling runner to prevent// leaked interruptsint s = state;if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}}...}
线程之AsyncTask的完全解析
android-更新UI的几种方式相关推荐
- android ui 最新教程,Android更新UI的五种方式,androidui五种
Android更新UI的五种方式,androidui五种handler.post activity.runOnUiThread view.post handler+Thread AsyncTask 例 ...
- [Android开发]Android更新UI的五种方式
Android更新UI的五种方式: 1.handler.post 2.activity.runOnUiThread 3.view.post 4.handler+Thread 5.AsyncTask 下 ...
- Android 更新UI的几种方式
1.Activity的 runOnUiThread textView = (TextView) findViewById( R.id.tv ); new Thread(new Runnable() { ...
- Android 更新UI的两种方法——handler和runOnUiThread()
Android 更新UI的两种方法--handler和runOnUiThread() 在Android开发过程中,常需要更新界面的UI.而更新UI是要主线程来更新的,即UI线程更新.如果在主线线程之外 ...
- 【转】探讨android更新UI的几种方法----不错
原文网址:http://www.cnblogs.com/wenjiang/p/3180324.html 作为IT新手,总以为只要有时间,有精力,什么东西都能做出来.这种念头我也有过,但很快就熄灭了,因 ...
- AndroidStudio子线程更新UI的几种方式
在安卓开发中,大部分情况下是不能在子线程直接更新UI的,只能在UI线程更新UI,其根本原因在于加入在一个Activity中有多个线程去更新UI,且没有加锁机制,可能会产生界面混乱的情况,但是如果都加锁 ...
- Android更新Ui的几种方法
2019独角兽企业重金招聘Python工程师标准>>> 常用的几种方法简单概括有: - handler.sendMessage(); - handler.post(); - 在act ...
- AndroidUI——后台线程更新UI的几种方式
开发Android程序时经常会用到后台线程请求服务器数据,当请求完数据后更新UI时,经常遇到回调函数中和UI相关的语句无法执行,甚至有时候会抛异常. 下面的例子我会讲到三种方法更新UI,例子非常简单, ...
- [UE4]更新UI的三种方式
一.函数绑定 二.属性绑定 只会列出匹配的数据类型. 三.事件驱动更新 啦啦啦啦啦 结论:函数和属性绑定的原理都是每帧都去调用绑定的函数/属性,效率比较低下,一般不推荐使用.事件驱动更新的效率最好,性 ...
- android开启gps功能,android 打开GPS的几种方式
1.在讨论打开gps的之前先看下如何检测gps的开关情况: 方式一: boolean gpsEnabled = locationManager.isProviderEnabled(LocationMa ...
最新文章
- HDOJ1540 - Tunnel Warfare 线段树区间合并
- BZOJ 4291: [PA2015]Kieszonkowe 水题
- 怎么添加本地音乐_网易云音乐:60G免费云盘+隐藏彩蛋、技巧
- SAP Fiori Elements - how is enableAutoBinding set for SmartTable
- 关联式容器(map,set,multimap,multiset)
- Oracle 10g、11g :RAC关闭、启动、重启步骤
- vue2 枚举类型转换
- linux 好用的命令行软件,比较好用的linux命令
- C/C++ Socket编程Http下载的简单实现
- 软件开发模型_20202021企业软件开发流程(5)软件开发过程模型瀑布模型(2)软件设计、编码...
- File类里的静态字段
- 第一单元 用python学习微积分(三) 求导四则运算及三角函数(下)- 三角函数
- 科普贴,告诉大家SGLTE、SVLTE、CSFB、SRLTE的意思
- 春天来了,该播种了。久久荒芜的博客重新耕种起来
- linux查看所有文件
- 利用π/4=1-1/3+1/5-1/7+1/9+……,编程计算π近似值,直到最后一项的绝对值小于10的负5次方为止,输出π的值并统计累加的项数。
- Win10早期版本下月终止服务、百万医疗设备存在漏洞风险|11月10日全球网络安全热点
- C#中的true和false运算符
- 茶饮行业舆情管理方案
- windows cmd 命令大全