更新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的静态内部类,AttachInfohandler就是在其构造方法中传递过来的,首先看看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中,看ViewRootImplperformTraversals()方法,其中有一句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]),也就是AsyncTaskfinish(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的几种方式相关推荐

  1. android ui 最新教程,Android更新UI的五种方式,androidui五种

    Android更新UI的五种方式,androidui五种handler.post activity.runOnUiThread view.post handler+Thread AsyncTask 例 ...

  2. [Android开发]Android更新UI的五种方式

    Android更新UI的五种方式: 1.handler.post 2.activity.runOnUiThread 3.view.post 4.handler+Thread 5.AsyncTask 下 ...

  3. Android 更新UI的几种方式

    1.Activity的 runOnUiThread textView = (TextView) findViewById( R.id.tv ); new Thread(new Runnable() { ...

  4. Android 更新UI的两种方法——handler和runOnUiThread()

    Android 更新UI的两种方法--handler和runOnUiThread() 在Android开发过程中,常需要更新界面的UI.而更新UI是要主线程来更新的,即UI线程更新.如果在主线线程之外 ...

  5. 【转】探讨android更新UI的几种方法----不错

    原文网址:http://www.cnblogs.com/wenjiang/p/3180324.html 作为IT新手,总以为只要有时间,有精力,什么东西都能做出来.这种念头我也有过,但很快就熄灭了,因 ...

  6. AndroidStudio子线程更新UI的几种方式

    在安卓开发中,大部分情况下是不能在子线程直接更新UI的,只能在UI线程更新UI,其根本原因在于加入在一个Activity中有多个线程去更新UI,且没有加锁机制,可能会产生界面混乱的情况,但是如果都加锁 ...

  7. Android更新Ui的几种方法

    2019独角兽企业重金招聘Python工程师标准>>> 常用的几种方法简单概括有: - handler.sendMessage(); - handler.post(); - 在act ...

  8. AndroidUI——后台线程更新UI的几种方式

    开发Android程序时经常会用到后台线程请求服务器数据,当请求完数据后更新UI时,经常遇到回调函数中和UI相关的语句无法执行,甚至有时候会抛异常. 下面的例子我会讲到三种方法更新UI,例子非常简单, ...

  9. [UE4]更新UI的三种方式

    一.函数绑定 二.属性绑定 只会列出匹配的数据类型. 三.事件驱动更新 啦啦啦啦啦 结论:函数和属性绑定的原理都是每帧都去调用绑定的函数/属性,效率比较低下,一般不推荐使用.事件驱动更新的效率最好,性 ...

  10. android开启gps功能,android 打开GPS的几种方式

    1.在讨论打开gps的之前先看下如何检测gps的开关情况: 方式一: boolean gpsEnabled = locationManager.isProviderEnabled(LocationMa ...

最新文章

  1. HDOJ1540 - Tunnel Warfare 线段树区间合并
  2. BZOJ 4291: [PA2015]Kieszonkowe 水题
  3. 怎么添加本地音乐_网易云音乐:60G免费云盘+隐藏彩蛋、技巧
  4. SAP Fiori Elements - how is enableAutoBinding set for SmartTable
  5. 关联式容器(map,set,multimap,multiset)
  6. Oracle 10g、11g :RAC关闭、启动、重启步骤
  7. vue2 枚举类型转换
  8. linux 好用的命令行软件,比较好用的linux命令
  9. C/C++ Socket编程Http下载的简单实现
  10. 软件开发模型_20202021企业软件开发流程(5)软件开发过程模型瀑布模型(2)软件设计、编码...
  11. File类里的静态字段
  12. 第一单元 用python学习微积分(三) 求导四则运算及三角函数(下)- 三角函数
  13. 科普贴,告诉大家SGLTE、SVLTE、CSFB、SRLTE的意思
  14. 春天来了,该播种了。久久荒芜的博客重新耕种起来
  15. linux查看所有文件
  16. 利用π/4=1-1/3+1/5-1/7+1/9+……,编程计算π近似值,直到最后一项的绝对值小于10的负5次方为止,输出π的值并统计累加的项数。
  17. Win10早期版本下月终止服务、百万医疗设备存在漏洞风险|11月10日全球网络安全热点
  18. C#中的true和false运算符
  19. 茶饮行业舆情管理方案
  20. windows cmd 命令大全

热门文章

  1. 『C++』endl、ends和flush的区别
  2. 电气器件系列十六:热电偶、热电阻
  3. 自动驾驶轨迹规划--算法综述
  4. Optimistic Concurrency VS. Pessimistic Concurrency Control
  5. 密集芯片的焊接技巧:从LQFP64说起
  6. mounted和created的区别
  7. java网络编程1-查询Internet地址
  8. 光伏龙头们掀起垂直一体化狂潮
  9. 360插件化方案RePlugin学习笔记-汇总
  10. 炼数成金--支持向量机 笔记