Dialog、Toast的Window和ViewRootImpl

2024-02-09 07:08

本文主要是介绍Dialog、Toast的Window和ViewRootImpl,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • Dialog
    • Dialog的构造
    • Dialog添加View
    • Dialog的展现
  • Toast
    • Toast的构造
    • Toast添加View
    • Toast的展示
    • Toast的消失
  • Dialog和Toast在异步线程的展现
  • 总结

前言

文章Activity中的Window的setContentView、遇见LayoutInflater&Factory、ViewRootImpl的独白,我不是一个View(布局篇) 分别讲述了Activity的setContentView添加ViewLayoutInflater布局解析以及添加Window。文章内容都是站在Activity的角度来进行代码解析的,因此我们不再对Dialog和Toast与Activity做具体分析,主要来看看它们与Activity有什么不同之处源码:android-22

Dialog

Dialog的构造

public class Dialog implements DialogInterface, Window.Callback,KeyEvent.Callback, OnCreateContextMenuListener,Window.OnWindowDismissedCallback{//只有Activity的Context可以启动Dialog,因为Dialog展示的时候需要主题资源也就是ContextThemeWrapper。Dialog(Context context, int theme, boolean createContextThemeWrapper) {if (createContextThemeWrapper) {if (theme == 0) {TypedValue outValue = new TypedValue();context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,outValue, true);theme = outValue.resourceId;}mContext = new ContextThemeWrapper(context, theme);} else {mContext = context;}//因为每个上下文环境获取的系统服务都是相同的实例,这里获取的WindowManager是Activity的WindowManager。mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);//创建Dialog的PhoneWindow对象。Window w = PolicyManager.makeNewWindow(mContext);mWindow = w;w.setCallback(this);w.setOnWindowDismissedCallback(this);w.setWindowManager(mWindowManager, null, null);w.setGravity(Gravity.CENTER);//Handler中的Looper默认为当前线程的LoopermListenersHandler = new ListenersHandler(this);}
}

Dialog添加View

和Activity相同通过setContentView初始化 Window 中的 DecorView,并对页面 View 进行add。详细讲述请移动到Activity中的Window的setContentView

public class Dialog implements DialogInterface, Window.Callback,KeyEvent.Callback, OnCreateContextMenuListener,Window.OnWindowDismissedCallback{/*** Set the screen content from a layout resource.  The resource will be* inflated, adding all top-level views to the screen.* * @param layoutResID Resource ID to be inflated.*/public void setContentView(int layoutResID) {mWindow.setContentView(layoutResID);}
}

Dialog的展现

Dialog 的展现和 Activity 不同是因为两者的声明周期不同,Activity 的声明周期是有 AMS 调用而 Dialog 是应用程序自己调用的。ViewRootImpl的初始化在 Activity 会在onResume()方法之后,而是 Dialog 被调用show方法时触发的。

public class Dialog implements DialogInterface, Window.Callback,KeyEvent.Callback, OnCreateContextMenuListener,Window.OnWindowDismissedCallback{/*** Start the dialog and display it on screen.  The window is placed in the* application layer and opaque.  Note that you should not override this* method to do initialization when the dialog is shown, instead implement* that in {@link #onStart}.*/public void show() {if (mShowing) {if (mDecor != null) {if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);}mDecor.setVisibility(View.VISIBLE);}return;}mCanceled = false;//判断是否调用onCreate方法if (!mCreated) {dispatchOnCreate(null);}//调用onStart方法onStart();//获取DecorView对象实例mDecor = mWindow.getDecorView();if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {final ApplicationInfo info = mContext.getApplicationInfo();mWindow.setDefaultIcon(info.icon);mWindow.setDefaultLogo(info.logo);mActionBar = new WindowDecorActionBar(this);}//更新Window属性参数WindowManager.LayoutParams l = mWindow.getAttributes();if ((l.softInputMode& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {WindowManager.LayoutParams nl = new WindowManager.LayoutParams();nl.copyFrom(l);nl.softInputMode |=WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;l = nl;}try {//Windowmanger添加Window、ViewRootImpl初始化并绑定WindowmWindowManager.addView(mDecor, l);mShowing = true;//OnShowListener监听回调sendShowMessage();} finally {}}
}

Toast

Toast的构造

public class Toast {final Context mContext;final TN mTN;//int mDuration;//展示时间View mNextView;//所展示的View/*** Construct an empty Toast object.  You must call {@link #setView} before you* can call {@link #show}.** @param context  The context to use.  Usually your {@link android.app.Application}*                 or {@link android.app.Activity} object.*///Context可以为Application也可以为Activity,public Toast(Context context) {mContext = context;mTN = new TN();mTN.mY = context.getResources().getDimensionPixelSize(com.android.internal.R.dimen.toast_y_offset);mTN.mGravity = context.getResources().getInteger(com.android.internal.R.integer.config_toastDefaultGravity);}//NotificationManagerService的客户端IBinder对private static INotificationManager sService;private static class TN extends ITransientNotification.Stub {/***部分代码省略***/private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();//Handler中的Looper默认为当前线程的Looperfinal Handler mHandler = new Handler(); TN() {// XXX This should be changed to use a Dialog, with a Theme.Toast// defined that sets up the layout params appropriately.final WindowManager.LayoutParams params = mParams;params.height = WindowManager.LayoutParams.WRAP_CONTENT;params.width = WindowManager.LayoutParams.WRAP_CONTENT;params.format = PixelFormat.TRANSLUCENT;params.windowAnimations = com.android.internal.R.style.Animation_Toast;//设置Window类型为Toastparams.type = WindowManager.LayoutParams.TYPE_TOAST;params.setTitle("Toast");params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;}}   
}

transient_notification.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="?android:attr/toastFrameBackground"><TextViewandroid:id="@android:id/message"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:layout_gravity="center_horizontal"android:textAppearance="@style/TextAppearance.Toast"android:textColor="@color/bright_foreground_dark"android:shadowColor="#BB000000"android:shadowRadius="2.75"/></LinearLayout>

Toast添加View

从Toast的调用我们开始分析Toast.makeText(MainActivity.this , "Hello World" , Toast.LENGTH_SHORT);我们主要看makeText方法。

public class Toast {/*** Make a standard toast that just contains a text view.** @param context  The context to use.  Usually your {@link android.app.Application}*                 or {@link android.app.Activity} object.* @param text     The text to show.  Can be formatted text.* @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or*                 {@link #LENGTH_LONG}**/public static Toast makeText(Context context, CharSequence text, @Duration int duration) {Toast result = new Toast(context);//获取布局解析器LayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//解析transient_notification.xml生成对应的ViewView v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);//找到View中的id为message的TextViewTextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);//对Textview进行文字赋值tv.setText(text);//展示的Toast所用的Viewresult.mNextView = v;//设置间隔时间result.mDuration = duration;return result;}
}

主要是对Toast内部成员变量mNextViewmDuration进行初始化。

Toast的展示

toast

Toast 内部的 TN ( ITransientNotification 客户端对象)加入到 INotificationManager 服务端的 Binder 兑现的 mToastQueue 队列中。再由服务端循环遍历 mToastQueue 队列中ToastRecord对象,处理一个移除一个,每次处理的都是 List 的第一个ToastRecord对象。

public class Toast {//INotificationManager的客户端的Binder对象private static INotificationManager sService;static private INotificationManager getService() {if (sService != null) {return sService;}//获取INotificationManager的客户端的Binder对象sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));return sService;}/*** Show the view for the specified duration.*/public void show() {//mNextView不能为空if (mNextView == null) {throw new RuntimeException("setView must have been called");}//service初始哈INotificationManager service = getService();//获取当前Context对应的包名String pkg = mContext.getOpPackageName();TN tn = mTN;tn.mNextView = mNextView;try {//将TN加入INotificationManager中的mToastQueue队列service.enqueueToast(pkg, tn, mDuration);} catch (RemoteException e) {// Empty}}
}

NotificationManagerService在服务端处理ITransientNotification客户端传过来的enqueueToast事件。

public class NotificationManagerService extends SystemService {//是否是系统调用private static boolean isCallerSystem() {return isUidSystem(Binder.getCallingUid());}private final IBinder mService = new INotificationManager.Stub() {@Overridepublic void enqueueToast(String pkg, ITransientNotification callback, int duration){if (DBG) {Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback+ " duration=" + duration);}if (pkg == null || callback == null) {Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);return ;}//判断是否是系统调动或者是Android系统应用程序进行调用final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));//Toast或者通知权限被禁用if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {if (!isSystemToast) {Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");return;}}//mToastQueue加锁synchronized (mToastQueue) {int callingPid = Binder.getCallingPid();long callingId = Binder.clearCallingIdentity();try {ToastRecord record;//寻找当前callback在mToastQueue中的索引,没找到则返回-1int index = indexOfToastLocked(pkg, callback);// If it's already in the queue, we update it in place, we don't// move it to the end of the queue.//index>=0表示mToastQueue中有该callback的索引,record进行更新展示时间if (index >= 0) {record = mToastQueue.get(index);record.update(duration);} else {// Limit the number of toasts that any given package except the android// package can enqueue.  Prevents DOS attacks and deals with leaks.//不是系统的Toastif (!isSystemToast) {int count = 0;final int N = mToastQueue.size();for (int i=0; i<N; i++) {final ToastRecord r = mToastQueue.get(i);//判断当前的Toast是不是同一个包发出的if (r.pkg.equals(pkg)) {count++;//当前包的需要展示的Toast缓存数量>=50if (count >= MAX_PACKAGE_NOTIFICATIONS) {Slog.e(TAG, "Package has already posted " + count+ " toasts. Not showing more. Package=" + pkg);return;}}}}//根据callback等信息构造ToastRecord对象record = new ToastRecord(callingPid, pkg, callback, duration);//将新的ToastRecord对象加入到队列总mToastQueue.add(record);//加入之后当前的索引是lenth-1index = mToastQueue.size() - 1;//将当前包对应的线程切换为前台线程keepProcessAliveLocked(callingPid);}// If it's at index 0, it's the current toast.  It doesn't matter if it's// new or just been updated.  Call back and tell it to show itself.// If the callback fails, this will remove it from the list, so don't// assume that it's valid after this.//如果之前队列中没有正在处理的消息,那么处理当前这个ToastRecordif (index == 0) {showNextToastLocked();}} finally {Binder.restoreCallingIdentity(callingId);}}}}
}

NotificationManagerService使用先进先出(FIFO)的方式处理 mToastQueue 队列中的消息。

  • 服务端的处理
public class NotificationManagerService extends SystemService {void showNextToastLocked() {//获取队列第一个ToastRecordToastRecord record = mToastQueue.get(0);while (record != null) {if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);try {//调用客户端Binder对应的TN.show方法。record.callback.show();scheduleTimeoutLocked(record);return;} catch (RemoteException e) {Slog.w(TAG, "Object died trying to show notification " + record.callback+ " in package " + record.pkg);// remove it from the list and let the process die//当前Toast客户端Binder方法调用抛出异常//移除当前ToastRecordint index = mToastQueue.indexOf(record);if (index >= 0) {mToastQueue.remove(index);}//切换当前ToastRecord进程keepProcessAliveLocked(record.pid);//遍历对象变为列表下一个oastRecord对象if (mToastQueue.size() > 0) {record = mToastQueue.get(0);} else {record = null;}}}}
}
  • 客户端的处理
private static class TN extends ITransientNotification.Stub {/*** schedule handleShow into the right thread*/@Overridepublic void show() {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.post(mShow);//利用Handler执行mShow}final Runnable mShow = new Runnable() {@Overridepublic void run() {handleShow();}};//展示Toastpublic void handleShow() {if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView+ " mNextView=" + mNextView);//判断mNextView是否展示过if (mView != mNextView) {// remove the old view if necessary//移除当前展示的ToasthandleHide();mView = mNextView;//获取当前的应用程序的上下文环境Context context = mView.getContext().getApplicationContext();//获取当前包名String packageName = mView.getContext().getOpPackageName();if (context == null) {context = mView.getContext();}//获取上下文环境的WindowManagerImplmWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);// We can resolve the Gravity here by using the Locale for getting// the layout directionfinal Configuration config = mView.getContext().getResources().getConfiguration();final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());//设置参数的重力防线mParams.gravity = gravity;if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {mParams.horizontalWeight = 1.0f;}if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {mParams.verticalWeight = 1.0f;}//设置参数的坐标和偏移量mParams.x = mX;mParams.y = mY;mParams.verticalMargin = mVerticalMargin;mParams.horizontalMargin = mHorizontalMargin;mParams.packageName = packageName;//如果mView添加过,那么先把mView从WindowManager中移除。if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);mWM.removeView(mView);}if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);//把需要展示的View添加在WindowManager中mWM.addView(mView, mParams);trySendAccessibilityEvent();}}
}

Toast的消失

系统的 Toasthide 都是在 INotificationManager 的服务端 Binder 中发起的,但最终的执行都是在 INotificationManager 的客户端 Binder 中执行的。

  • 服务端
public class NotificationManagerService extends SystemService {private final class WorkerHandler extends Handler{@Overridepublic void handleMessage(Message msg){switch (msg.what){case MESSAGE_TIMEOUT://调用当前的Toast的hidehandleTimeout((ToastRecord)msg.obj);break;case MESSAGE_SAVE_POLICY_FILE:handleSavePolicyFile();break;case MESSAGE_SEND_RANKING_UPDATE:handleSendRankingUpdate();break;case MESSAGE_LISTENER_HINTS_CHANGED:handleListenerHintsChanged(msg.arg1);break;case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED:handleListenerInterruptionFilterChanged(msg.arg1);break;}}}//让当前Toast展示一段时间后消失private void scheduleTimeoutLocked(ToastRecord r){//移除mHandler关于这个TaostRecord的所有MessagemHandler.removeCallbacksAndMessages(r);Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;//发送一个delayed=duration的MESSAGE_TIMEOUT事件mHandler.sendMessageDelayed(m, delay);}//使Toast消失private void handleTimeout(ToastRecord record){if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);synchronized (mToastQueue) {//找当前ToastRecord在mToastQueue队列中的索引int index = indexOfToastLocked(record.pkg, record.callback);if (index >= 0) {cancelToastLocked(index);}}}//调用当前索引=index的ToastRecord.callback.hidevoid cancelToastLocked(int index) {ToastRecord record = mToastQueue.get(index);try {调用客户端Binder对应的TN.hide方法。record.callback.hide();} catch (RemoteException e) {Slog.w(TAG, "Object died trying to hide notification " + record.callback+ " in package " + record.pkg);// don't worry about this, we're about to remove it from// the list anyway}//移除处理完的ToastRecordmToastQueue.remove(index);keepProcessAliveLocked(record.pid);if (mToastQueue.size() > 0) {// Show the next one. If the callback fails, this will remove// it from the list, so don't assume that the list hasn't changed// after this point.//处理队列中的下一个ToastRecordshowNextToastLocked();}}
}
  • 客户端
private static class TN extends ITransientNotification.Stub {/*** schedule handleHide into the right thread*/@Overridepublic void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);//利用Handler执行mHide}final Runnable mHide = new Runnable() {@Overridepublic void run() {handleHide();// Don't do this in handleHide() because it is also invoked by handleShow()mNextView = null;}};public void handleHide() {if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);if (mView != null) {// note: checking parent() just to make sure the view has// been added...  i have seen cases where we get here when// the view isn't yet added, so let's try not to crash.if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);//调用WindowManager的removeView移除mViewmWM.removeView(mView);}mView = null;}}
}

Dialog和Toast在异步线程的展现

ViewRootImpl的独白,我不是一个View(布局篇) 这篇文章说明了为什么我们一般禁止在非 UI线程 中刷新 View ,以及怎么安全的在异步线程操作UI

发生了对任务执行线程的校验,而且当前执行任务的线程与创建 ViewRootImpl 的线程不一样;。

那么 ToastDialogView异步展现,与异步操作UI是否一致呢?

首先测试一下异步展现 DialogToast :

//Toast展现
new Thread(new Runnable() {@Overridepublic void run() {//Looper.prepare();Toast.makeText(TestActivity.this, "test", Toast.LENGTH_SHORT).show();//Looper.loop();}
}).start();
//Dialog的展现
new Thread(new Runnable() {@Overridepublic void run() {//Looper.prepare();new MyDialog(TestActivity.this, "test").show();//Looper.loop();}
}).start();

崩溃日志:

//Toast崩溃日志
17:30:04.211#[androidcode@]#30824#E#AndroidRuntime #FATAL EXCEPTION: Thread-2Process: com.tzx.androidcode, PID: 30513java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()at android.widget.Toast$TN.<init>(Toast.java:394)at android.widget.Toast.<init>(Toast.java:114)at android.widget.Toast.makeText(Toast.java:277)at android.widget.Toast.makeText(Toast.java:267)at com.tzx.androidcode.activity.TestActivity$1.run(TestActivity.java:72)at java.lang.Thread.run(Thread.java:764)
//Dialog的崩溃日志
17:33:07.961#[androidcode@]#31514#E#AndroidRuntime #FATAL EXCEPTION: Thread-2Process: com.tzx.androidcode, PID: 31438java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()at android.os.Handler.<init>(Handler.java:203)at android.os.Handler.<init>(Handler.java:117)at android.app.Dialog.<init>(Dialog.java:123)at android.app.Dialog.<init>(Dialog.java:149)at com.tzx.rollaction.test.BaseDailog.<init>(BaseDailog.java:23)at com.tzx.rollaction.test.MyDialog.<init>(MyDialog.java:20)at com.tzx.androidcode.activity.TestActivity$2.run(TestActivity.java:86)at java.lang.Thread.run(Thread.java:764)

我们可以看到都是提示 当前的Handler的Looper没有调用prepare
我们在上面进行源码阅读的时候都看到了 Toast.TNDialog 构造的时候的 Handler 都是默认当前线程的 Looper
如果当前线程的 Looper 没有 prepare 那么必定会抛异常,如果仅仅执行了 prepare 那么崩溃不会产生了,但是依旧不展示。因为整个 Looper 还没有开始,里面的 Message 都未进行处理。最后我们将代码中注释的 Looper.prepare();Looper.loop(); 打开就可以正常在异步线程进行 ToastDialog 的展现。

所以 ToastDialog 的异步展现其实主要是与其线程的 Looper 队列有关。 ToastDialog 展示的时候进行的 ViewRootImpl 的创建,这个执行UI操作的也是这个线程,所以展现不会发现异常。如果对 Dialog 进行异步刷新UI ,那么他的限制和 View 的异步刷新是相同的。

总结

通过分析ActivityDialogToast通过对 ViewRootImpl 的更细节的分析,所有添加在窗口上的 View 都有一个 ViewRootImpl 作为它的 Parent ,处理View的布局、事件处理等。

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦

想阅读作者的更多文章,可以查看我 个人博客 和公共号:
振兴书城

这篇关于Dialog、Toast的Window和ViewRootImpl的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/693490

相关文章

Window Server创建2台服务器的故障转移群集的图文教程

《WindowServer创建2台服务器的故障转移群集的图文教程》本文主要介绍了在WindowsServer系统上创建一个包含两台成员服务器的故障转移群集,文中通过图文示例介绍的非常详细,对大家的... 目录一、 准备条件二、在ServerB安装故障转移群集三、在ServerC安装故障转移群集,操作与Ser

Window Server2016加入AD域的方法步骤

《WindowServer2016加入AD域的方法步骤》:本文主要介绍WindowServer2016加入AD域的方法步骤,包括配置DNS、检测ping通、更改计算机域、输入账号密码、重启服务... 目录一、 准备条件二、配置ServerB加入ServerA的AD域(test.ly)三、查看加入AD域后的变

Window Server2016 AD域的创建的方法步骤

《WindowServer2016AD域的创建的方法步骤》本文主要介绍了WindowServer2016AD域的创建的方法步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、准备条件二、在ServerA服务器中常见AD域管理器:三、创建AD域,域地址为“test.ly”

js window.addEventListener 是什么?

window.addEventListener 是 JavaScript 中的一个方法,用于向指定对象(在这个情况下是 window 对象,代表浏览器窗口)添加事件监听器,以便在该对象上发生特定事件时执行相应的函数(称为事件处理函数或事件监听器)。 这个方法接受三个参数: 事件类型(type):一个字符串,表示要监听的事件类型。例如,"click" 表示鼠标点击事件,"load" 表示页面加

Qt中window frame的影响

window frame 在创建图形化界面的时候,会创建窗口主体,上面会多出一条,周围多次一圈细边,这就叫window frame窗口框架,这是操作系统自带的。 这个对geometry的一些属性有一定影响,主要体现在Qt坐标系体系: 窗口当中包含一个按钮,这个按钮的坐标系是以父元素为参考,那么这个参考是widget本体作为参考,还是window frame作为参考,这两种参考体系都存在

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.B

一个bug日志 FATAL EXCEPTION: main03-25 14:24:07.724: E/AndroidRuntime(4135): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.syyx.jingubang.ky/com.anguotech.android.activity.Init

最初的window

不知你是否也是一个常年在MFC下编程的程序员,有的时候是否忘记了在MFC之前是如何写画窗口的了呢,或者你从来都只是机械的在MFC下面写代码,已经麻木了。其实有一个很简单的方法,或许能够帮你更清楚的了解WINDOW是怎么产生的。 随便用什么版本的VS,在创建win32工程的时候,直接创建WINDOW类型的就OK了。然后,来研究下产生的源代码吧。 // Global Variables:H

VC环境下window网络程序:UDP Socket程序

最近在学Windows网络编程,正好在做UDPsocket的程序,贴上来: 服务器框架函数:              socket();    bind();    recfrom();  sendto();  closesocket(); 客户机框架函数:            socket();      recfrom();  sendto();  closesocket();

Window下编译OpenJDK17

本文详细介绍Window下如何编译OpenJDK17,包含源码路径,各工具下载地址,严格按照文章中的步骤来操作,你将获得一个由自己亲手编译出的jdk。  一、下载OpenJDK17源码 下载地址:GitHub - openjdk/jdk at jdk-17+35 说明: 1、kkgithub为github的国内镜像,能够提高下载速度  2、下载下来的源码存放路径:无中文、无空格

Vite + Vue3 +Vant4出现Toast is not a function

今天写前端的时候出现了这个问题搞了我一会 搜集原因: 1:是vant版本的问题,Toast()的方法是vant3版本的写法,而我用的是vant4,vant4中的写法改成了showToast()方法,改正过来 import {showToast} from "vant";  发现还是报错,说是找不到对应的样式文件 2:Vant 从 4.0 版本开始不再支持 babel-plugin-i