ChildProcessService.java revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.content.app; 6 7import android.app.Service; 8import android.content.Context; 9import android.content.Intent; 10import android.graphics.SurfaceTexture; 11import android.os.Bundle; 12import android.os.IBinder; 13import android.os.ParcelFileDescriptor; 14import android.os.Process; 15import android.os.RemoteException; 16import android.util.Log; 17import android.view.Surface; 18 19import org.chromium.base.CalledByNative; 20import org.chromium.base.JNINamespace; 21import org.chromium.content.browser.ChildProcessConnection; 22import org.chromium.content.common.IChildProcessCallback; 23import org.chromium.content.common.IChildProcessService; 24import org.chromium.content.browser.ChildProcessLauncher; 25import org.chromium.content.common.ProcessInitException; 26 27import java.util.ArrayList; 28import java.util.concurrent.atomic.AtomicReference; 29 30/** 31 * This is the base class for child services; the [Non]SandboxedProcessService0, 1.. etc 32 * subclasses provide the concrete service entry points, to enable the browser to connect 33 * to more than one distinct process (i.e. one process per service number, up to limit of N). 34 * The embedding application must declare these service instances in the application section 35 * of its AndroidManifest.xml, for example with N entries of the form:- 36 * <service android:name="org.chromium.content.app.[Non]SandboxedProcessServiceX" 37 * android:process=":[non]sandboxed_processX" /> 38 * for X in 0...N-1 (where N is {@link ChildProcessLauncher#MAX_REGISTERED_SERVICES}) 39 */ 40@JNINamespace("content") 41public class ChildProcessService extends Service { 42 private static final String MAIN_THREAD_NAME = "ChildProcessMain"; 43 private static final String TAG = "ChildProcessService"; 44 private IChildProcessCallback mCallback; 45 46 // This is the native "Main" thread for the renderer / utility process. 47 private Thread mMainThread; 48 // Parameters received via IPC, only accessed while holding the mMainThread monitor. 49 private String[] mCommandLineParams; 50 private int mCpuCount; 51 private long mCpuFeatures; 52 // Pairs IDs and file descriptors that should be registered natively. 53 private ArrayList<Integer> mFileIds; 54 private ArrayList<ParcelFileDescriptor> mFileFds; 55 56 private static AtomicReference<Context> sContext = new AtomicReference<Context>(null); 57 private boolean mLibraryInitialized = false; 58 59 // Binder object used by clients for this service. 60 private final IChildProcessService.Stub mBinder = new IChildProcessService.Stub() { 61 // NOTE: Implement any IChildProcessService methods here. 62 @Override 63 public int setupConnection(Bundle args, IChildProcessCallback callback) { 64 mCallback = callback; 65 synchronized (mMainThread) { 66 // Allow the command line to be set via bind() intent or setupConnection, but 67 // the FD can only be transferred here. 68 if (mCommandLineParams == null) { 69 mCommandLineParams = args.getStringArray( 70 ChildProcessConnection.EXTRA_COMMAND_LINE); 71 } 72 // We must have received the command line by now 73 assert mCommandLineParams != null; 74 mCpuCount = args.getInt(ChildProcessConnection.EXTRA_CPU_COUNT); 75 mCpuFeatures = args.getLong(ChildProcessConnection.EXTRA_CPU_FEATURES); 76 assert mCpuCount > 0; 77 mFileIds = new ArrayList<Integer>(); 78 mFileFds = new ArrayList<ParcelFileDescriptor>(); 79 for (int i = 0;; i++) { 80 String fdName = ChildProcessConnection.EXTRA_FILES_PREFIX + i 81 + ChildProcessConnection.EXTRA_FILES_FD_SUFFIX; 82 ParcelFileDescriptor parcel = args.getParcelable(fdName); 83 if (parcel == null) { 84 // End of the file list. 85 break; 86 } 87 mFileFds.add(parcel); 88 String idName = ChildProcessConnection.EXTRA_FILES_PREFIX + i 89 + ChildProcessConnection.EXTRA_FILES_ID_SUFFIX; 90 mFileIds.add(args.getInt(idName)); 91 } 92 mMainThread.notifyAll(); 93 } 94 return Process.myPid(); 95 } 96 }; 97 98 /* package */ static Context getContext() { 99 return sContext.get(); 100 } 101 102 @Override 103 public void onCreate() { 104 Log.i(TAG, "Creating new ChildProcessService pid=" + Process.myPid()); 105 if (sContext.get() != null) { 106 Log.e(TAG, "ChildProcessService created again in process!"); 107 } 108 sContext.set(this); 109 super.onCreate(); 110 111 mMainThread = new Thread(new Runnable() { 112 @Override 113 public void run() { 114 try { 115 try { 116 LibraryLoader.loadNow(); 117 } catch (ProcessInitException e) { 118 Log.e(TAG, "Failed to load native library, exiting child process", e); 119 return; 120 } 121 synchronized (mMainThread) { 122 while (mCommandLineParams == null) { 123 mMainThread.wait(); 124 } 125 } 126 LibraryLoader.initialize(mCommandLineParams); 127 synchronized (mMainThread) { 128 mLibraryInitialized = true; 129 mMainThread.notifyAll(); 130 while (mFileIds == null) { 131 mMainThread.wait(); 132 } 133 } 134 assert mFileIds.size() == mFileFds.size(); 135 int[] fileIds = new int[mFileIds.size()]; 136 int[] fileFds = new int[mFileFds.size()]; 137 for (int i = 0; i < mFileIds.size(); ++i) { 138 fileIds[i] = mFileIds.get(i); 139 fileFds[i] = mFileFds.get(i).detachFd(); 140 } 141 ContentMain.initApplicationContext(sContext.get().getApplicationContext()); 142 nativeInitChildProcess(sContext.get().getApplicationContext(), 143 ChildProcessService.this, fileIds, fileFds, 144 mCpuCount, mCpuFeatures); 145 ContentMain.start(); 146 nativeExitChildProcess(); 147 } catch (InterruptedException e) { 148 Log.w(TAG, MAIN_THREAD_NAME + " startup failed: " + e); 149 } catch (ProcessInitException e) { 150 Log.w(TAG, MAIN_THREAD_NAME + " startup failed: " + e); 151 } 152 } 153 }, MAIN_THREAD_NAME); 154 mMainThread.start(); 155 } 156 157 @Override 158 public void onDestroy() { 159 Log.i(TAG, "Destroying ChildProcessService pid=" + Process.myPid()); 160 super.onDestroy(); 161 if (mCommandLineParams == null) { 162 // This process was destroyed before it even started. Nothing more to do. 163 return; 164 } 165 synchronized (mMainThread) { 166 try { 167 while (!mLibraryInitialized) { 168 // Avoid a potential race in calling through to native code before the library 169 // has loaded. 170 mMainThread.wait(); 171 } 172 } catch (InterruptedException e) { 173 } 174 } 175 // Try to shutdown the MainThread gracefully, but it might not 176 // have chance to exit normally. 177 nativeShutdownMainThread(); 178 } 179 180 @Override 181 public IBinder onBind(Intent intent) { 182 // We call stopSelf() to request that this service be stopped as soon as the client 183 // unbinds. Otherwise the system may keep it around and available for a reconnect. The 184 // child processes do not currently support reconnect; they must be initialized from 185 // scratch every time. 186 stopSelf(); 187 188 synchronized (mMainThread) { 189 mCommandLineParams = intent.getStringArrayExtra( 190 ChildProcessConnection.EXTRA_COMMAND_LINE); 191 mMainThread.notifyAll(); 192 } 193 194 return mBinder; 195 } 196 197 /** 198 * Called from native code to share a surface texture with another child process. 199 * Through using the callback object the browser is used as a proxy to route the 200 * call to the correct process. 201 * 202 * @param pid Process handle of the child process to share the SurfaceTexture with. 203 * @param surfaceObject The Surface or SurfaceTexture to share with the other child process. 204 * @param primaryID Used to route the call to the correct client instance. 205 * @param secondaryID Used to route the call to the correct client instance. 206 */ 207 @SuppressWarnings("unused") 208 @CalledByNative 209 private void establishSurfaceTexturePeer( 210 int pid, Object surfaceObject, int primaryID, int secondaryID) { 211 if (mCallback == null) { 212 Log.e(TAG, "No callback interface has been provided."); 213 return; 214 } 215 216 Surface surface = null; 217 boolean needRelease = false; 218 if (surfaceObject instanceof Surface) { 219 surface = (Surface)surfaceObject; 220 } else if (surfaceObject instanceof SurfaceTexture) { 221 surface = new Surface((SurfaceTexture)surfaceObject); 222 needRelease = true; 223 } else { 224 Log.e(TAG, "Not a valid surfaceObject: " + surfaceObject); 225 return; 226 } 227 try { 228 mCallback.establishSurfacePeer(pid, surface, primaryID, secondaryID); 229 } catch (RemoteException e) { 230 Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e); 231 return; 232 } finally { 233 if (needRelease) { 234 surface.release(); 235 } 236 } 237 } 238 239 @SuppressWarnings("unused") 240 @CalledByNative 241 private Surface getViewSurface(int surfaceId) { 242 if (mCallback == null) { 243 Log.e(TAG, "No callback interface has been provided."); 244 return null; 245 } 246 247 try { 248 return mCallback.getViewSurface(surfaceId); 249 } catch (RemoteException e) { 250 Log.e(TAG, "Unable to call establishSurfaceTexturePeer: " + e); 251 return null; 252 } 253 } 254 255 /** 256 * The main entry point for a child process. This should be called from a new thread since 257 * it will not return until the child process exits. See child_process_service.{h,cc} 258 * 259 * @param applicationContext The Application Context of the current process. 260 * @param service The current ChildProcessService object. 261 * @param fileIds A list of file IDs that should be registered for access by the renderer. 262 * @param fileFds A list of file descriptors that should be registered for access by the 263 * renderer. 264 */ 265 private static native void nativeInitChildProcess(Context applicationContext, 266 ChildProcessService service, int[] extraFileIds, int[] extraFileFds, 267 int cpuCount, long cpuFeatures); 268 269 /** 270 * Force the child process to exit. 271 */ 272 private static native void nativeExitChildProcess(); 273 274 private native void nativeShutdownMainThread(); 275} 276