ZygoteInit.java revision dde331cebd87982faded6818ad5f9927ff994c96
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.os;
18
19import android.content.pm.ActivityInfo;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
22import android.graphics.drawable.Drawable;
23import android.net.LocalServerSocket;
24import android.os.Debug;
25import android.os.FileUtils;
26import android.os.Process;
27import android.os.SystemClock;
28import android.os.SystemProperties;
29import android.util.EventLog;
30import android.util.Log;
31
32import dalvik.system.VMRuntime;
33import dalvik.system.Zygote;
34
35import libcore.io.IoUtils;
36
37import java.io.BufferedReader;
38import java.io.FileDescriptor;
39import java.io.IOException;
40import java.io.InputStream;
41import java.io.InputStreamReader;
42import java.lang.reflect.InvocationTargetException;
43import java.lang.reflect.Method;
44import java.lang.reflect.Modifier;
45import java.util.ArrayList;
46
47/**
48 * Startup class for the zygote process.
49 *
50 * Pre-initializes some classes, and then waits for commands on a UNIX domain
51 * socket. Based on these commands, forks off child processes that inherit
52 * the initial state of the VM.
53 *
54 * Please see {@link ZygoteConnection.Arguments} for documentation on the
55 * client protocol.
56 *
57 * @hide
58 */
59public class ZygoteInit {
60
61    private static final String TAG = "Zygote";
62
63    private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";
64
65    private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
66    private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
67
68    /** when preloading, GC after allocating this many bytes */
69    private static final int PRELOAD_GC_THRESHOLD = 50000;
70
71    public static final String USAGE_STRING =
72            " <\"start-system-server\"|\"\" for startSystemServer>";
73
74    private static LocalServerSocket sServerSocket;
75
76    /**
77     * Used to pre-load resources.  We hold a global reference on it so it
78     * never gets destroyed.
79     */
80    private static Resources mResources;
81
82    /**
83     * The number of times that the main Zygote loop
84     * should run before calling gc() again.
85     */
86    static final int GC_LOOP_COUNT = 10;
87
88    /**
89     * If true, zygote forks for each peer. If false, a select loop is used
90     * inside a single process. The latter is preferred.
91     */
92    private static final boolean ZYGOTE_FORK_MODE = false;
93
94    /**
95     * The name of a resource file that contains classes to preload.
96     */
97    private static final String PRELOADED_CLASSES = "preloaded-classes";
98
99    /** Controls whether we should preload resources during zygote init. */
100    private static final boolean PRELOAD_RESOURCES = true;
101
102    /**
103     * Invokes a static "main(argv[]) method on class "className".
104     * Converts various failing exceptions into RuntimeExceptions, with
105     * the assumption that they will then cause the VM instance to exit.
106     *
107     * @param loader class loader to use
108     * @param className Fully-qualified class name
109     * @param argv Argument vector for main()
110     */
111    static void invokeStaticMain(ClassLoader loader,
112            String className, String[] argv)
113            throws ZygoteInit.MethodAndArgsCaller {
114        Class<?> cl;
115
116        try {
117            cl = loader.loadClass(className);
118        } catch (ClassNotFoundException ex) {
119            throw new RuntimeException(
120                    "Missing class when invoking static main " + className,
121                    ex);
122        }
123
124        Method m;
125        try {
126            m = cl.getMethod("main", new Class[] { String[].class });
127        } catch (NoSuchMethodException ex) {
128            throw new RuntimeException(
129                    "Missing static main on " + className, ex);
130        } catch (SecurityException ex) {
131            throw new RuntimeException(
132                    "Problem getting static main on " + className, ex);
133        }
134
135        int modifiers = m.getModifiers();
136        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
137            throw new RuntimeException(
138                    "Main method is not public and static on " + className);
139        }
140
141        /*
142         * This throw gets caught in ZygoteInit.main(), which responds
143         * by invoking the exception's run() method. This arrangement
144         * clears up all the stack frames that were required in setting
145         * up the process.
146         */
147        throw new ZygoteInit.MethodAndArgsCaller(m, argv);
148    }
149
150    /**
151     * Registers a server socket for zygote command connections
152     *
153     * @throws RuntimeException when open fails
154     */
155    private static void registerZygoteSocket() {
156        if (sServerSocket == null) {
157            int fileDesc;
158            try {
159                String env = System.getenv(ANDROID_SOCKET_ENV);
160                fileDesc = Integer.parseInt(env);
161            } catch (RuntimeException ex) {
162                throw new RuntimeException(
163                        ANDROID_SOCKET_ENV + " unset or invalid", ex);
164            }
165
166            try {
167                sServerSocket = new LocalServerSocket(
168                        createFileDescriptor(fileDesc));
169            } catch (IOException ex) {
170                throw new RuntimeException(
171                        "Error binding to local socket '" + fileDesc + "'", ex);
172            }
173        }
174    }
175
176    /**
177     * Waits for and accepts a single command connection. Throws
178     * RuntimeException on failure.
179     */
180    private static ZygoteConnection acceptCommandPeer() {
181        try {
182            return new ZygoteConnection(sServerSocket.accept());
183        } catch (IOException ex) {
184            throw new RuntimeException(
185                    "IOException during accept()", ex);
186        }
187    }
188
189    /**
190     * Close and clean up zygote sockets. Called on shutdown and on the
191     * child's exit path.
192     */
193    static void closeServerSocket() {
194        try {
195            if (sServerSocket != null) {
196                sServerSocket.close();
197            }
198        } catch (IOException ex) {
199            Log.e(TAG, "Zygote:  error closing sockets", ex);
200        }
201
202        sServerSocket = null;
203    }
204
205    private static final int UNPRIVILEGED_UID = 9999;
206    private static final int UNPRIVILEGED_GID = 9999;
207
208    private static final int ROOT_UID = 0;
209    private static final int ROOT_GID = 0;
210
211    /**
212     * Sets effective user ID.
213     */
214    private static void setEffectiveUser(int uid) {
215        int errno = setreuid(ROOT_UID, uid);
216        if (errno != 0) {
217            Log.e(TAG, "setreuid() failed. errno: " + errno);
218        }
219    }
220
221    /**
222     * Sets effective group ID.
223     */
224    private static void setEffectiveGroup(int gid) {
225        int errno = setregid(ROOT_GID, gid);
226        if (errno != 0) {
227            Log.e(TAG, "setregid() failed. errno: " + errno);
228        }
229    }
230
231    static void preload() {
232        preloadClasses();
233        preloadResources();
234    }
235
236    /**
237     * Performs Zygote process initialization. Loads and initializes
238     * commonly used classes.
239     *
240     * Most classes only cause a few hundred bytes to be allocated, but
241     * a few will allocate a dozen Kbytes (in one case, 500+K).
242     */
243    private static void preloadClasses() {
244        final VMRuntime runtime = VMRuntime.getRuntime();
245
246        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(
247                PRELOADED_CLASSES);
248        if (is == null) {
249            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
250        } else {
251            Log.i(TAG, "Preloading classes...");
252            long startTime = SystemClock.uptimeMillis();
253
254            // Drop root perms while running static initializers.
255            setEffectiveGroup(UNPRIVILEGED_GID);
256            setEffectiveUser(UNPRIVILEGED_UID);
257
258            // Alter the target heap utilization.  With explicit GCs this
259            // is not likely to have any effect.
260            float defaultUtilization = runtime.getTargetHeapUtilization();
261            runtime.setTargetHeapUtilization(0.8f);
262
263            // Start with a clean slate.
264            System.gc();
265            runtime.runFinalizationSync();
266            Debug.startAllocCounting();
267
268            try {
269                BufferedReader br
270                    = new BufferedReader(new InputStreamReader(is), 256);
271
272                int count = 0;
273                String line;
274                while ((line = br.readLine()) != null) {
275                    // Skip comments and blank lines.
276                    line = line.trim();
277                    if (line.startsWith("#") || line.equals("")) {
278                        continue;
279                    }
280
281                    try {
282                        if (false) {
283                            Log.v(TAG, "Preloading " + line + "...");
284                        }
285                        Class.forName(line);
286                        if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
287                            if (false) {
288                                Log.v(TAG,
289                                    " GC at " + Debug.getGlobalAllocSize());
290                            }
291                            System.gc();
292                            runtime.runFinalizationSync();
293                            Debug.resetGlobalAllocSize();
294                        }
295                        count++;
296                    } catch (ClassNotFoundException e) {
297                        Log.w(TAG, "Class not found for preloading: " + line);
298                    } catch (Throwable t) {
299                        Log.e(TAG, "Error preloading " + line + ".", t);
300                        if (t instanceof Error) {
301                            throw (Error) t;
302                        }
303                        if (t instanceof RuntimeException) {
304                            throw (RuntimeException) t;
305                        }
306                        throw new RuntimeException(t);
307                    }
308                }
309
310                Log.i(TAG, "...preloaded " + count + " classes in "
311                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
312            } catch (IOException e) {
313                Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
314            } finally {
315                IoUtils.closeQuietly(is);
316                // Restore default.
317                runtime.setTargetHeapUtilization(defaultUtilization);
318
319                Debug.stopAllocCounting();
320
321                // Bring back root. We'll need it later.
322                setEffectiveUser(ROOT_UID);
323                setEffectiveGroup(ROOT_GID);
324            }
325        }
326    }
327
328    /**
329     * Load in commonly used resources, so they can be shared across
330     * processes.
331     *
332     * These tend to be a few Kbytes, but are frequently in the 20-40K
333     * range, and occasionally even larger.
334     */
335    private static void preloadResources() {
336        final VMRuntime runtime = VMRuntime.getRuntime();
337
338        Debug.startAllocCounting();
339        try {
340            System.gc();
341            runtime.runFinalizationSync();
342            mResources = Resources.getSystem();
343            mResources.startPreloading();
344            if (PRELOAD_RESOURCES) {
345                Log.i(TAG, "Preloading resources...");
346
347                long startTime = SystemClock.uptimeMillis();
348                TypedArray ar = mResources.obtainTypedArray(
349                        com.android.internal.R.array.preloaded_drawables);
350                int N = preloadDrawables(runtime, ar);
351                ar.recycle();
352                Log.i(TAG, "...preloaded " + N + " resources in "
353                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
354
355                startTime = SystemClock.uptimeMillis();
356                ar = mResources.obtainTypedArray(
357                        com.android.internal.R.array.preloaded_color_state_lists);
358                N = preloadColorStateLists(runtime, ar);
359                ar.recycle();
360                Log.i(TAG, "...preloaded " + N + " resources in "
361                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
362            }
363            mResources.finishPreloading();
364        } catch (RuntimeException e) {
365            Log.w(TAG, "Failure preloading resources", e);
366        } finally {
367            Debug.stopAllocCounting();
368        }
369    }
370
371    private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) {
372        int N = ar.length();
373        for (int i=0; i<N; i++) {
374            if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
375                if (false) {
376                    Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
377                }
378                System.gc();
379                runtime.runFinalizationSync();
380                Debug.resetGlobalAllocSize();
381            }
382            int id = ar.getResourceId(i, 0);
383            if (false) {
384                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
385            }
386            if (id != 0) {
387                if (mResources.getColorStateList(id) == null) {
388                    throw new IllegalArgumentException(
389                            "Unable to find preloaded color resource #0x"
390                            + Integer.toHexString(id)
391                            + " (" + ar.getString(i) + ")");
392                }
393            }
394        }
395        return N;
396    }
397
398
399    private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
400        int N = ar.length();
401        for (int i=0; i<N; i++) {
402            if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
403                if (false) {
404                    Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
405                }
406                System.gc();
407                runtime.runFinalizationSync();
408                Debug.resetGlobalAllocSize();
409            }
410            int id = ar.getResourceId(i, 0);
411            if (false) {
412                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
413            }
414            if (id != 0) {
415                if (mResources.getDrawable(id) == null) {
416                    throw new IllegalArgumentException(
417                            "Unable to find preloaded drawable resource #0x"
418                            + Integer.toHexString(id)
419                            + " (" + ar.getString(i) + ")");
420                }
421            }
422        }
423        return N;
424    }
425
426    /**
427     * Runs several special GCs to try to clean up a few generations of
428     * softly- and final-reachable objects, along with any other garbage.
429     * This is only useful just before a fork().
430     */
431    /*package*/ static void gc() {
432        final VMRuntime runtime = VMRuntime.getRuntime();
433
434        /* runFinalizationSync() lets finalizers be called in Zygote,
435         * which doesn't have a HeapWorker thread.
436         */
437        System.gc();
438        runtime.runFinalizationSync();
439        System.gc();
440        runtime.runFinalizationSync();
441        System.gc();
442        runtime.runFinalizationSync();
443    }
444
445    /**
446     * Finish remaining work for the newly forked system server process.
447     */
448    private static void handleSystemServerProcess(
449            ZygoteConnection.Arguments parsedArgs)
450            throws ZygoteInit.MethodAndArgsCaller {
451
452        closeServerSocket();
453
454        // set umask to 0077 so new files and directories will default to owner-only permissions.
455        FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);
456
457        if (parsedArgs.niceName != null) {
458            Process.setArgV0(parsedArgs.niceName);
459        }
460
461        if (parsedArgs.invokeWith != null) {
462            WrapperInit.execApplication(parsedArgs.invokeWith,
463                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
464                    null, parsedArgs.remainingArgs);
465        } else {
466            /*
467             * Pass the remaining arguments to SystemServer.
468             */
469            RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs);
470        }
471
472        /* should never reach here */
473    }
474
475    /**
476     * Prepare the arguments and fork for the system server process.
477     */
478    private static boolean startSystemServer()
479            throws MethodAndArgsCaller, RuntimeException {
480        /* Hardcoded command line to start the system server */
481        String args[] = {
482            "--setuid=1000",
483            "--setgid=1000",
484            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007",
485            "--capabilities=130104352,130104352",
486            "--runtime-init",
487            "--nice-name=system_server",
488            "com.android.server.SystemServer",
489        };
490        ZygoteConnection.Arguments parsedArgs = null;
491
492        int pid;
493
494        try {
495            parsedArgs = new ZygoteConnection.Arguments(args);
496            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
497            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
498
499            /* Request to fork the system server process */
500            pid = Zygote.forkSystemServer(
501                    parsedArgs.uid, parsedArgs.gid,
502                    parsedArgs.gids,
503                    parsedArgs.debugFlags,
504                    null,
505                    parsedArgs.permittedCapabilities,
506                    parsedArgs.effectiveCapabilities);
507        } catch (IllegalArgumentException ex) {
508            throw new RuntimeException(ex);
509        }
510
511        /* For child process */
512        if (pid == 0) {
513            handleSystemServerProcess(parsedArgs);
514        }
515
516        return true;
517    }
518
519    public static void main(String argv[]) {
520        try {
521            // Start profiling the zygote initialization.
522            SamplingProfilerIntegration.start();
523
524            registerZygoteSocket();
525            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
526                SystemClock.uptimeMillis());
527            preload();
528            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
529                SystemClock.uptimeMillis());
530
531            // Finish profiling the zygote initialization.
532            SamplingProfilerIntegration.writeZygoteSnapshot();
533
534            // Do an initial gc to clean up after startup
535            gc();
536
537            // If requested, start system server directly from Zygote
538            if (argv.length != 2) {
539                throw new RuntimeException(argv[0] + USAGE_STRING);
540            }
541
542            if (argv[1].equals("start-system-server")) {
543                startSystemServer();
544            } else if (!argv[1].equals("")) {
545                throw new RuntimeException(argv[0] + USAGE_STRING);
546            }
547
548            Log.i(TAG, "Accepting command socket connections");
549
550            if (ZYGOTE_FORK_MODE) {
551                runForkMode();
552            } else {
553                runSelectLoopMode();
554            }
555
556            closeServerSocket();
557        } catch (MethodAndArgsCaller caller) {
558            caller.run();
559        } catch (RuntimeException ex) {
560            Log.e(TAG, "Zygote died with exception", ex);
561            closeServerSocket();
562            throw ex;
563        }
564    }
565
566    /**
567     * Runs the zygote in accept-and-fork mode. In this mode, each peer
568     * gets its own zygote spawner process. This code is retained for
569     * reference only.
570     *
571     * @throws MethodAndArgsCaller in a child process when a main() should
572     * be executed.
573     */
574    private static void runForkMode() throws MethodAndArgsCaller {
575        while (true) {
576            ZygoteConnection peer = acceptCommandPeer();
577
578            int pid;
579
580            pid = Zygote.fork();
581
582            if (pid == 0) {
583                // The child process should handle the peer requests
584
585                // The child does not accept any more connections
586                try {
587                    sServerSocket.close();
588                } catch (IOException ex) {
589                    Log.e(TAG, "Zygote Child: error closing sockets", ex);
590                } finally {
591                    sServerSocket = null;
592                }
593
594                peer.run();
595                break;
596            } else if (pid > 0) {
597                peer.closeSocket();
598            } else {
599                throw new RuntimeException("Error invoking fork()");
600            }
601        }
602    }
603
604    /**
605     * Runs the zygote process's select loop. Accepts new connections as
606     * they happen, and reads commands from connections one spawn-request's
607     * worth at a time.
608     *
609     * @throws MethodAndArgsCaller in a child process when a main() should
610     * be executed.
611     */
612    private static void runSelectLoopMode() throws MethodAndArgsCaller {
613        ArrayList<FileDescriptor> fds = new ArrayList();
614        ArrayList<ZygoteConnection> peers = new ArrayList();
615        FileDescriptor[] fdArray = new FileDescriptor[4];
616
617        fds.add(sServerSocket.getFileDescriptor());
618        peers.add(null);
619
620        int loopCount = GC_LOOP_COUNT;
621        while (true) {
622            int index;
623
624            /*
625             * Call gc() before we block in select().
626             * It's work that has to be done anyway, and it's better
627             * to avoid making every child do it.  It will also
628             * madvise() any free memory as a side-effect.
629             *
630             * Don't call it every time, because walking the entire
631             * heap is a lot of overhead to free a few hundred bytes.
632             */
633            if (loopCount <= 0) {
634                gc();
635                loopCount = GC_LOOP_COUNT;
636            } else {
637                loopCount--;
638            }
639
640
641            try {
642                fdArray = fds.toArray(fdArray);
643                index = selectReadable(fdArray);
644            } catch (IOException ex) {
645                throw new RuntimeException("Error in select()", ex);
646            }
647
648            if (index < 0) {
649                throw new RuntimeException("Error in select()");
650            } else if (index == 0) {
651                ZygoteConnection newPeer = acceptCommandPeer();
652                peers.add(newPeer);
653                fds.add(newPeer.getFileDesciptor());
654            } else {
655                boolean done;
656                done = peers.get(index).runOnce();
657
658                if (done) {
659                    peers.remove(index);
660                    fds.remove(index);
661                }
662            }
663        }
664    }
665
666    /**
667     * The Linux syscall "setreuid()"
668     * @param ruid real uid
669     * @param euid effective uid
670     * @return 0 on success, non-zero errno on fail
671     */
672    static native int setreuid(int ruid, int euid);
673
674    /**
675     * The Linux syscall "setregid()"
676     * @param rgid real gid
677     * @param egid effective gid
678     * @return 0 on success, non-zero errno on fail
679     */
680    static native int setregid(int rgid, int egid);
681
682    /**
683     * Invokes the linux syscall "setpgid"
684     *
685     * @param pid pid to change
686     * @param pgid new process group of pid
687     * @return 0 on success or non-zero errno on fail
688     */
689    static native int setpgid(int pid, int pgid);
690
691    /**
692     * Invokes the linux syscall "getpgid"
693     *
694     * @param pid pid to query
695     * @return pgid of pid in question
696     * @throws IOException on error
697     */
698    static native int getpgid(int pid) throws IOException;
699
700    /**
701     * Invokes the syscall dup2() to copy the specified descriptors into
702     * stdin, stdout, and stderr. The existing stdio descriptors will be
703     * closed and errors during close will be ignored. The specified
704     * descriptors will also remain open at their original descriptor numbers,
705     * so the caller may want to close the original descriptors.
706     *
707     * @param in new stdin
708     * @param out new stdout
709     * @param err new stderr
710     * @throws IOException
711     */
712    static native void reopenStdio(FileDescriptor in,
713            FileDescriptor out, FileDescriptor err) throws IOException;
714
715    /**
716     * Toggles the close-on-exec flag for the specified file descriptor.
717     *
718     * @param fd non-null; file descriptor
719     * @param flag desired close-on-exec flag state
720     * @throws IOException
721     */
722    static native void setCloseOnExec(FileDescriptor fd, boolean flag)
723            throws IOException;
724
725    /**
726     * Retrieves the permitted capability set from another process.
727     *
728     * @param pid &gt;=0 process ID or 0 for this process
729     * @throws IOException on error
730     */
731    static native long capgetPermitted(int pid)
732            throws IOException;
733
734    /**
735     * Sets the permitted and effective capability sets of this process.
736     *
737     * @param permittedCapabilities permitted set
738     * @param effectiveCapabilities effective set
739     * @throws IOException on error
740     */
741    static native void setCapabilities(
742            long permittedCapabilities,
743            long effectiveCapabilities) throws IOException;
744
745    /**
746     * Invokes select() on the provider array of file descriptors (selecting
747     * for readability only). Array elements of null are ignored.
748     *
749     * @param fds non-null; array of readable file descriptors
750     * @return index of descriptor that is now readable or -1 for empty array.
751     * @throws IOException if an error occurs
752     */
753    static native int selectReadable(FileDescriptor[] fds) throws IOException;
754
755    /**
756     * Creates a file descriptor from an int fd.
757     *
758     * @param fd integer OS file descriptor
759     * @return non-null; FileDescriptor instance
760     * @throws IOException if fd is invalid
761     */
762    static native FileDescriptor createFileDescriptor(int fd)
763            throws IOException;
764
765    /**
766     * Class not instantiable.
767     */
768    private ZygoteInit() {
769    }
770
771    /**
772     * Helper exception class which holds a method and arguments and
773     * can call them. This is used as part of a trampoline to get rid of
774     * the initial process setup stack frames.
775     */
776    public static class MethodAndArgsCaller extends Exception
777            implements Runnable {
778        /** method to call */
779        private final Method mMethod;
780
781        /** argument array */
782        private final String[] mArgs;
783
784        public MethodAndArgsCaller(Method method, String[] args) {
785            mMethod = method;
786            mArgs = args;
787        }
788
789        public void run() {
790            try {
791                mMethod.invoke(null, new Object[] { mArgs });
792            } catch (IllegalAccessException ex) {
793                throw new RuntimeException(ex);
794            } catch (InvocationTargetException ex) {
795                Throwable cause = ex.getCause();
796                if (cause instanceof RuntimeException) {
797                    throw (RuntimeException) cause;
798                } else if (cause instanceof Error) {
799                    throw (Error) cause;
800                }
801                throw new RuntimeException(ex);
802            }
803        }
804    }
805}
806