In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces the relevant knowledge of "what is the startup process of App". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
Preface
First of all, new has a daughter.
Var mDdaughter = new daughter ("6 years old", "pretty and cute", "healthy and cute", "favorite to play with little genius phone watch and her father") to answer the little interviewer
Daughter, you can think of the watch as a kindergarten with a teacher, a monitor, a class cadre, and a lot of children.
One teacher: teacher Z (Zygote process)
A monitor: Xiao A (ActivityManagerService)
A class cadre: Xiao L (Launcher desktop application)
A lot of kids: all apps, including music kids, chat kids, calendar kids and so on.
The startup process of the app is like a child being woken up. After turning on the phone, teacher Z will wake up the monitor and the class cadre (SystemServer#ActivityManagerService,Launcher) in turn. After Xiao L wakes up, he will find out which children are in the watch, what they look like (icon,name), family information (package name, androidmanifest), and so on, and then paste the photos (icon) of the children on his body one by one. For example, there are music children, chat children, calendar children, in fact, it is the table on your watch.
At this time you click to open a music child (startActivity), Xiao L will inform the monitor Xiao A (Binder), Xiao A knows, let Xiao L have a rest (Paused), and then go to find teacher Z. Teacher Z is responsible for waking up the music children (fork process, starting ActivityThread). After getting up, the music children will find Xiao A to take her to wash her face and brush her teeth (start ApplicationThread,Activity). When it's all done, you can perform all kinds of performances, singing and dancing.
Fifteen years later mDdaughter.grow (15)
MDdaughter.study ("Android")
Fifteen years later, my daughter is 21 years old and is studying Android, considering whether she should follow her father's career.
On this day, she came to me with a puzzled look on her face: "Dad, what exactly is the process of starting this app? I still don't understand it after watching it for a long time. Why don't you tell me more about it?" "all right, don't worry. I'll tell you more about it this time."
Answer Android female programmer
Remember the story I told you when I was a child, the Android system is like a kindergarten, there is a big friend named Launcher, who will post a lot of other children's business cards. This Launcher is our desktop, it knows the information of all the applications in the system through PackageManagerService, and shows it, of course, it is also an application.
By clicking on an application icon, which triggers the click event, it is finally executed to the startActivity method. This coincides with the step of starting Activity.
So what did this startActivity do? How did you wake up this app through many hurdles?
First of all, introduce the important members of the system, who play an important role in the app startup process.
System members introduce the init process. After the Android system starts, Zygote is not the first process, but the root process of linux, the init process, and then the init process will start the Zygote process. The Zygote process, the parent process of all android processes, and, of course, the SystemServer process SystemServer process, as the name suggests, the system service process, which is responsible for everything large and small in the system, also starts the ActivityManagerService,PackageManagerService,WindowManagerService and the binder thread pool. ActivityManagerService is mainly responsible for the startup, switching, scheduling of the four major components of the system and the management and scheduling of application processes. For the startup of some processes, it will be passed to AMS through the Binder communication mechanism, and then processed to Zygote. PackageManagerService, mainly responsible for some operations of the application package, such as installing, uninstalling, parsing AndroidManifest.xml, scanning file information, and so on. WindowManagerService, mainly responsible for some window-related services, such as window startup, add, delete and so on. Launcher, a desktop application, also belongs to an application, and it also has its own Activity, which starts by default as soon as it is powered on, and starts implicitly by setting the Category of Intent.CATEGORY_HOME.
Figure out these members, follow me to see how to get through the five hurdles, and finally start an App.
The first level: cross-process communication, telling the system my requirements
First, I want to tell the system that my Launcher is going to start an application, call the Activity.startActivityForResult method, and eventually go to the mInstrumentation.execStartActivity method. Because Launcher itself is in a separate process, it needs to tell the system service across processes that I want to start App. Find the Service you want to notify, named ActivityTaskManagerService, and use AIDL to communicate with him through Binder.
Here is a brief introduction to ActivityTaskManagerService (ATMS for short). Originally, these communication work belongs to ActivityManagerService, but now part of the work is assigned to ATMS, which mainly includes the scheduling work of four major components. Is also started directly by the SystemServer process, the relevant source code can be seen in the ActivityManagerService.Lifecycle.startService method, interested friends can see for themselves.
Then let's talk about cross-process communication. The relevant code is as follows:
/ / Instrumentation.java
Int result = ActivityTaskManager.getService ()
.startActivity (whoThread, who.getBasePackageName (), intent
Intent.resolveTypeIfNeeded (who.getContentResolver ())
Token, target! = null? Target.mEmbeddedID: null
RequestCode, 0, null, options)
/ / ActivityTaskManager.java
Public static IActivityTaskManager getService () {
Return IActivityTaskManagerSingleton.get ()
}
Private static final Singleton IActivityTaskManagerSingleton =
New Singleton () {
@ Override
Protected IActivityTaskManager create () {
Final IBinder b = ServiceManager.getService (Context.ACTIVITY_TASK_SERVICE)
Return IActivityTaskManager.Stub.asInterface (b)
}
}
/ / ActivityTaskManagerService.java
Public class ActivityTaskManagerService extends IActivityTaskManager.Stub
Public static final class Lifecycle extends SystemService {
Private final ActivityTaskManagerService mService
Public Lifecycle (Context context) {
Super (context)
MService = new ActivityTaskManagerService (context)
}
@ Override
Public void onStart () {
PublishBinderService (Context.ACTIVITY_TASK_SERVICE, mService)
MService.start ()
}
}
We are all familiar with startActivity, and we usually use it to start Activity. This is the way to start an application. We also bring intent information to indicate which Activity is to be launched.
Another thing to note is that there is a checkStartActivityResult method after startActivity, which is used to check the results of starting Activity. When starting Activity fails, an exception is thrown through this method, such as our common problem: not registered with AndroidManifest.xml.
Public static void checkStartActivityResult (int res, Object intent) {
Switch (res) {
Case ActivityManager.START_INTENT_NOT_RESOLVED:
Case ActivityManager.START_CLASS_NOT_FOUND:
If (intent instanceof Intent & ((Intent) intent) .getComponent ()! = null)
Throw new ActivityNotFoundException (
"Unable to find explicit activity class"
+ ((Intent) intent) .getComponent () .toShortString ()
+ "; have you declared this activity in your AndroidManifest.xml?")
Throw new ActivityNotFoundException (
"No Activity found to handle" + intent)
Case ActivityManager.START_PERMISSION_DENIED:
Throw new SecurityException ("Not allowed to start activity"
+ intent)
Case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
Throw new AndroidRuntimeException (
"FORWARD_RESULT_FLAG used while also requesting a result")
Case ActivityManager.START_NOT_ACTIVITY:
Throw new IllegalArgumentException (
"PendingIntent is not an activity")
/ /...
}
Second level: inform Launcher that you can have a rest.
When ATMS receives the message to start, it informs the previous application that Launcher can take a break and enter the Paused state.
/ / ActivityStack.java
Private boolean resumeTopActivityInnerLocked (ActivityRecord prev, ActivityOptions options) {
/ /...
ActivityRecord next = topRunningActivityLocked (true / * focusableOnly * /)
/ /...
Boolean pausing = getDisplay () .pauseBackStacks (userLeaving, next, false)
If (mResumedActivity! = null) {
If (DEBUG_STATES) Slog.d (TAG_STATES
"resumeTopActivityLocked: Pausing" + mResumedActivity)
Pausing | = startPausingLocked (userLeaving, false, next, false)
}
/ /...
If (next.attachedToProcess ()) {
/ / the application has been started
Try {
/ /...
Transaction.setLifecycleStateRequest (
ResumeActivityItem.obtain (next.app.getReportedProcState ()
GetDisplay () .mDisplayContent.isNextTransitionForward ())
MService.getLifecycleManager () scheduleTransaction (transaction)
/ /...
} catch (Exception e) {
/ /...
MStackSupervisor.startSpecificActivityLocked (next, true, false)
Return true
}
/ /...
/ / From this point on, if something goes wrong there is no way
/ / to recover the activity.
Try {
Next.completeResumeLocked ()
} catch (Exception e) {
/ / If any exception gets thrown, toss away this
/ / activity and try the next one.
Slog.w (TAG, "Exception thrown during resume of" + next, e)
RequestFinishActivityLocked (next.appToken, Activity.RESULT_CANCELED, null
"resume-exception", true)
Return true
}
} else {
/ / Cold start process
MStackSupervisor.startSpecificActivityLocked (next, true, true)
}
}
There are two classes that have not been seen before:
ActivityStack, is the stack management of Activity, which is equivalent to the Activity management class written by ourselves in our usual project, which is used to manage the state of Activity, such as stack order and so on. ActivityRecord, which represents a specific Activity, stores all kinds of information about that Activity.
The startPausingLocked method is to put the previous application, in this case, Launcher, into the Paused state. Then it will determine whether the application is started, and if it has already started, it will follow the ResumeActivityItem method. Look at this name, combined with the premise that the application has been started, whether you have guessed what it is? Yes, this is used to control the onResume lifecycle method of Activity, not only the onResume but also the onStart method. For more information, please see the handleResumeActivity method source code of ActivityThread.
If the application doesn't start, it will go on to the startSpecificActivityLocked method and take a look.
The third level: whether the process has been started, otherwise the process is created
After Launcher enters Paused, ActivityTaskManagerService will determine whether the application process to be opened has been started, and if so, just start Activity directly, that is, the Activity process within the application. If the process is not started, you need to create a process.
Here are two questions:
How to determine whether the application process exists? If an application has been started, a WindowProcessController message is saved in ATMS. This information, including processName and uid,uid, is the id of the application and can be obtained through applicationInfo.uid. ProcessName is the process name, usually the package name. Therefore, to determine whether there is an application process, it is based on processName and uid to determine whether there is a corresponding WindowProcessController, and the thread in WindowProcessController is not empty. The code is as follows: / / ActivityStackSupervisor.java
Void startSpecificActivityLocked (ActivityRecord r, boolean andResume, boolean checkConfig) {
/ / Is this activity's application already running?
Final WindowProcessController wpc =
MService.getProcessController (r.processName, r.info.applicationInfo.uid)
Boolean knownToBeDead = false
If (wpc! = null & & wpc.hasThread ()) {
/ / the application process exists
Try {
RealStartActivityLocked (r, wpc, andResume, checkConfig)
Return
}
}
}
/ / WindowProcessController.java
IApplicationThread getThread () {
Return mThread
}
Boolean hasThread () {
Return mThread! = null
}
Another question is how to create a process? Do you remember Mr. Z? Yes, it is the Zygote process. As mentioned earlier, he is the parent of all processes, so inform Zygote to fork a new process to serve this application. / / ZygoteProcess.java
Private Process.ProcessStartResult attemptUsapSendArgsAndGetResult (
ZygoteState zygoteState, String msgStr)
Throws ZygoteStartFailedEx, IOException {
Try (LocalSocket usapSessionSocket = zygoteState.getUsapSessionSocket ()) {
Final BufferedWriter usapWriter =
New BufferedWriter (
New OutputStreamWriter (usapSessionSocket.getOutputStream ())
Zygote.SOCKET_BUFFER_SIZE)
Final DataInputStream usapReader =
New DataInputStream (usapSessionSocket.getInputStream ())
UsapWriter.write (msgStr)
UsapWriter.flush ()
Process.ProcessStartResult result = new Process.ProcessStartResult ()
Result.pid = usapReader.readInt ()
/ / USAPs can't be used to spawn processes that need wrappers.
Result.usingWrapper = false
If (result.pid > = 0) {
Return result
} else {
Throw new ZygoteStartFailedEx ("USAP specialization failed")
}
}
}
As you can see, this is actually communicating through socket and Zygote, and BufferedWriter is used to read and receive messages. Here the message of the new process is passed to Zygote, and the Zygote performs the fork process and returns the pid of the new process.
Maybe someone will ask again? What is fork? Why is it that socket does IPC communication instead of Bindler?
First, fork () is a method that is the main method for creating processes on Unix-like operating systems. Used to create a child process (equivalent to a copy of the current process). So why do you use socket instead of Binder in fork? Mainly because fork does not allow multithreading, Binder communication happens to be multithreading.
Questions always arise, and curious friends will always ask, why doesn't fork allow multithreading?
Prevent deadlocks. In fact, if you think about it, multi-thread + multi-process, it doesn't sound very reliable. Suppose that thread An in multithreading calls a lock lock, and another thread B calls fork to create a child process, but the child process does not have thread A, but the lock itself is fork, then no one can open the lock. As soon as another thread in the sub-process lock the lock, it is deadlocked. Level 4: ActivityThread makes a brilliant debut.
I just mentioned that the fork process is performed by Zygote and the pid of the new process is returned. In fact, the ActivityThread object is also instantiated in the process. Let's take a look at how it works:
/ / RuntimeInit.java
Protected static Runnable findStaticMain (String className, String [] argv
ClassLoader classLoader) {
Class cl
Try {
Cl = Class.forName (className, true, classLoader)
} catch (ClassNotFoundException ex) {
Throw new RuntimeException (
"Missing class when invoking static main" + className
Ex)
}
Method m
Try {
M = cl.getMethod ("main", new Class [] {String [] .class})
} catch (NoSuchMethodException ex) {
Throw new RuntimeException (
"Missing static main on" + className, ex)
} catch (SecurityException ex) {
Throw new RuntimeException (
"Problem getting static main on" + className, ex)
}
/ /...
Return new MethodAndArgsCaller (m, argv)
}
It's a reflex! The main method of ActivityThread is called through reflection. ActivityThread should be familiar to all of you, representing the main thread of Android, and the main method is also the main entry of app. This is not right! It is called when you create a new process, but it is not the main entry. Take a look at this main entrance.
Public static void main (String [] args) {
/ /...
Looper.prepareMainLooper ()
ActivityThread thread = new ActivityThread ()
Thread.attach (false, startSeq)
/ /...
If (false) {
Looper.myLooper () .setMessageLogging (new
LogPrinter (Log.DEBUG, "ActivityThread"))
}
/ /...
Looper.loop ()
Throw new RuntimeException ("Main thread loop unexpectedly exited")
}
The main method mainly creates the ActivityThread, creates the Looper object of the main thread, and starts the loop loop. In addition to this, tell AMS that I am awake and that the process has been created! That is, the attach method in the above code, and finally go to the AMSattachApplicationLocked method to see what this method does:
/ / ActivitymanagerService.java
Private final boolean attachApplicationLocked (IApplicationThread thread
Int pid, int callingUid, long startSeq) {
/ /...
ProcessRecord app
/ /...
Thread.bindApplication (processName, appInfo, providers, null, profilerInfo
Null, testMode
MBinderTransactionTrackingEnabled, enableTrackAllocation
IsRestrictedBackupMode | |! normalMode, app.isPersistent ()
New Configuration (app.getWindowProcessController () .getConfiguration ())
App.compat, getCommonServicesLocked (app.isolated)
MCoreSettingsObserver.getCoreSettingsLocked ()
BuildSerial, autofillOptions, contentCaptureOptions)
/ /...
App.makeActive (thread, mProcessStats)
/ /...
/ / See if the top visible activity is waiting to run in this process...
If (normalMode) {
Try {
DidSomething = mAtmInternal.attachApplication (app.getWindowProcessController ())
} catch (Exception e) {
Slog.wtf (TAG, "Exception thrown launching activities in" + app, e)
BadApp = true
}
}
/ /...
}
/ / ProcessRecord.java
Public void makeActive (IApplicationThread _ thread, ProcessStatsService tracker) {
/ /...
Thread = _ thread
MWindowProcessController.setThread (thread)
}
Three main things have been done here:
The bindApplication method is mainly used to start Application. MakeActive method, which sets the thread in the WindowProcessController, which is used to determine whether the process exists or not. The attachApplication method starts the root Activity. Level 5: create an Application
Then look at the above, as we are familiar with, after the application starts, it should be to start Applicaiton, start Activity. See what's going on:
/ / ActivityThread#ApplicationThread
Public final void bindApplication (String processName, ApplicationInfo appInfo
List providers, ComponentName instrumentationName
ProfilerInfo profilerInfo, Bundle instrumentationArgs
IInstrumentationWatcher instrumentationWatcher
IUiAutomationConnection instrumentationUiConnection, int debugMode
Boolean enableBinderTracking, boolean trackAllocation
Boolean isRestrictedBackupMode, boolean persistent, Configuration config
CompatibilityInfo compatInfo, Map services, Bundle coreSettings
String buildSerial, AutofillOptions autofillOptions
ContentCaptureOptions contentCaptureOptions) {
AppBindData data = new AppBindData ()
Data.processName = processName
Data.appInfo = appInfo
Data.providers = providers
Data.instrumentationName = instrumentationName
Data.instrumentationArgs = instrumentationArgs
Data.instrumentationWatcher = instrumentationWatcher
Data.instrumentationUiAutomationConnection = instrumentationUiConnection
Data.debugMode = debugMode
Data.enableBinderTracking = enableBinderTracking
Data.trackAllocation = trackAllocation
Data.restrictedBackupMode = isRestrictedBackupMode
Data.persistent = persistent
Data.config = config
Data.compatInfo = compatInfo
Data.initProfilerInfo = profilerInfo
Data.buildSerial = buildSerial
Data.autofillOptions = autofillOptions
Data.contentCaptureOptions = contentCaptureOptions
SendMessage (H.BIND_APPLICATION, data)
}
Public void handleMessage (Message msg) {
If (DEBUG_MESSAGES) Slog.v (TAG, "> handling:" + codeToString (msg.what))
Switch (msg.what) {
Case BIND_APPLICATION:
Trace.traceBegin (Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication")
AppBindData data = (AppBindData) msg.obj
HandleBindApplication (data)
Trace.traceEnd (Trace.TRACE_TAG_ACTIVITY_MANAGER)
Break
}
}
You can see that there is a Handler class of the main thread, which is used to handle all kinds of messages that need to be processed by the main thread, including BIND_SERVICE,LOW_MEMORY,DUMP_HEAP, and so on. Then take a look at handleBindApplication:
Private void handleBindApplication (AppBindData data) {
/ /...
Try {
Final ClassLoader cl = instrContext.getClassLoader ()
MInstrumentation = (Instrumentation)
Cl.loadClass (data.instrumentationName.getClassName (). NewInstance ()
}
/ /...
Application app
Final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites ()
Final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy ()
Try {
/ / If the app is being launched for full backup or restore, bring it up in
/ / a restricted environment with the base application class.
App = data.info.makeApplication (data.restrictedBackupMode, null)
MInitialApplication = app
/ / don't bring up providers in restricted mode; they may depend on the
/ / app's custom Application class
If (! data.restrictedBackupMode) {
If (! ArrayUtils.isEmpty (data.providers)) {
InstallContentProviders (app, data.providers)
}
}
/ / Do this after providers, since instrumentation tests generally start their
/ / test thread at this point, and we don't want that racing.
Try {
MInstrumentation.onCreate (data.instrumentationArgs)
}
/ /...
Try {
MInstrumentation.callApplicationOnCreate (app)
} catch (Exception e) {
If (! mInstrumentation.onException (app, e)) {
Throw new RuntimeException (
"Unable to create application" + app.getClass () .getName ()
+ ":" + e.toString (), e)
}
}
}
/ /...
}
There is a lot of information here, look at it a little bit:
First, the Instrumentation is created, which is the first step in startActivity at the beginning of the above. Each application has an Instrumentation to manage the process, such as when you want to create an Activity, it will be executed into this class first. The makeApplication method, which created Application, has finally come to this point. Eventually you go to the newApplication method, which executes the attach method of Application. Public Application newApplication (ClassLoader cl, String className, Context context)
Throws InstantiationException, IllegalAccessException
ClassNotFoundException {
Application app = getFactory (context.getPackageName ())
.instantiate Application (cl, className)
App.attach (context)
Return app
}
With the attach method in place, when was the onCreate method called? Coming right away:
Instrumentation.callApplicationOnCreate (app)
Public void callApplicationOnCreate (Application app) {
App.onCreate ()
}
That is, create the order of Application- > attach- > onCreate calls.
Wait, there is an important line of code before onCreate:
InstallContentProviders
Here is the relevant code to start Provider, the specific logic will not be analyzed.
Level 6: start Activity
After talking about bindApplication, it's time to talk about the follow-up. As mentioned in the fifth pass above, the bindApplication method is followed by the attachApplication method, which will eventually be executed to the handleLaunchActivity method of ActivityThread:
Public Activity handleLaunchActivity (ActivityClientRecord r
PendingTransactionActions pendingActions, Intent customIntent) {
/ /...
WindowManagerGlobal.initialize ()
/ /...
Final Activity a = performLaunchActivity (r, customIntent)
/ /...
Return a
}
First of all, initialize the WindowManagerGlobal. What is this? Yes, it is WindowManagerService, and it is also ready for the follow-up window display.
Continue to look at performLaunchActivity:
Private Activity performLaunchActivity (ActivityClientRecord r, Intent customIntent) {
/ / create a ContextImpl
ContextImpl appContext = createBaseContextForActivity (r)
Activity activity = null
Try {
Java.lang.ClassLoader cl = appContext.getClassLoader ()
/ / create an Activity
Activity = mInstrumentation.newActivity (
Cl, component.getClassName (), r.intent)
}
Try {
If (activity! = null) {
/ / complete the initialization of some important data of activity
Activity.attach (appContext, this, getInstrumentation (), r.token
R.ident, app, r.intent, r.activityInfo, title, r.parent
R.embeddedID, r.lastNonConfigurationInstances, config
R.referrer, r.voiceInteractor, window, r.configCallback
R.assistToken)
If (customIntent! = null) {
Activity.mIntent = customIntent
}
/ / set the theme of activity
Int theme = r.activityInfo.getThemeResource ()
If (theme! = 0) {
Activity.setTheme (theme)
}
/ / call the onCreate method of activity
If (r.isPersistable ()) {
MInstrumentation.callActivityOnCreate (activity, r.state, r.persistentState)
} else {
MInstrumentation.callActivityOnCreate (activity, r.state)
}
}
}
Return activity
}
Wow, finally see the onCreate method. Steady, or take a look at this code step by step.
First of all, the ContextImpl object is created. Some friends of ContextImpl may not know what it is. ContextImpl inherits from Context, which is actually the context we usually use. Some students may say, this is not right, ah, access to the context is clearly the Context object. Let's follow the source code and have a look.
/ / Activity.java
Context mBase
@ Override
Public Executor getMainExecutor () {
Return mBase.getMainExecutor ()
}
@ Override
Public Context getApplicationContext () {
Return mBase.getApplicationContext ()
}
As you can see here, the context we usually use is this mBase, so just find out what this mBase is:
Protected void attachBaseContext (Context base) {
If (mBase! = null) {
Throw new IllegalStateException ("Base context already set")
}
MBase = base
}
/ / look up layer by layer
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) {
AttachBaseContext (context)
MWindow = new PhoneWindow (this, window, activityConfigCallback)
MWindow.setWindowControllerCallback (this)
MWindow.setCallback (this)
MWindow.setOnWindowDismissedCallback (this)
MWindow.getLayoutInflater () setPrivateFactory (this)
If (info.softInputMode! = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
MWindow.setSoftInputMode (info.softInputMode)
}
}
Isn't this the attach in the performLaunchActivity method at the beginning? What a coincidence, so this ContextImpl is the context we usually use.
By the way, what else did attach do? Create a new PhoneWindow, establish your own association with Window, and set up setSoftInputMode and so on.
After the ContextImpl is created, the object of the Activity is created through the class loader, the theme of the activity is set, and finally the onCreate method of activity is called.
This is the end of the content of "what is the startup process of App". Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.