1 /* 2 * Copyright (C) 2006 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 17 package android.content.res; 18 19 import android.os.ParcelFileDescriptor; 20 import android.util.Config; 21 import android.util.Log; 22 import android.util.TypedValue; 23 24 import java.io.FileNotFoundException; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.util.HashMap; 28 29 /** 30 * Provides access to an application's raw asset files; see {@link Resources} 31 * for the way most applications will want to retrieve their resource data. 32 * This class presents a lower-level API that allows you to open and read raw 33 * files that have been bundled with the application as a simple stream of 34 * bytes. 35 */ 36 public final class AssetManager { 37 /* modes used when opening an asset */ 38 39 /** 40 * Mode for {@link #open(String, int)}: no specific information about how 41 * data will be accessed. 42 */ 43 public static final int ACCESS_UNKNOWN = 0; 44 /** 45 * Mode for {@link #open(String, int)}: Read chunks, and seek forward and 46 * backward. 47 */ 48 public static final int ACCESS_RANDOM = 1; 49 /** 50 * Mode for {@link #open(String, int)}: Read sequentially, with an 51 * occasional forward seek. 52 */ 53 public static final int ACCESS_STREAMING = 2; 54 /** 55 * Mode for {@link #open(String, int)}: Attempt to load contents into 56 * memory, for fast small reads. 57 */ 58 public static final int ACCESS_BUFFER = 3; 59 60 private static final String TAG = "AssetManager"; 61 private static final boolean localLOGV = Config.LOGV || false; 62 63 private static final boolean DEBUG_REFS = false; 64 65 private static final Object sSync = new Object(); 66 private static AssetManager sSystem = null; 67 68 private final TypedValue mValue = new TypedValue(); 69 private final long[] mOffsets = new long[2]; 70 71 // For communication with native code. 72 private int mObject; 73 private int mNObject; // used by the NDK 74 75 private StringBlock mStringBlocks[] = null; 76 77 private int mNumRefs = 1; 78 private boolean mOpen = true; 79 private HashMap<Integer, RuntimeException> mRefStacks; 80 81 /** 82 * Create a new AssetManager containing only the basic system assets. 83 * Applications will not generally use this method, instead retrieving the 84 * appropriate asset manager with {@link Resources#getAssets}. Not for 85 * use by applications. 86 * {@hide} 87 */ 88 public AssetManager() { 89 synchronized (this) { 90 if (DEBUG_REFS) { 91 mNumRefs = 0; 92 incRefsLocked(this.hashCode()); 93 } 94 init(); 95 if (localLOGV) Log.v(TAG, "New asset manager: " + this); 96 ensureSystemAssets(); 97 } 98 } 99 100 private static void ensureSystemAssets() { 101 synchronized (sSync) { 102 if (sSystem == null) { 103 AssetManager system = new AssetManager(true); 104 system.makeStringBlocks(false); 105 sSystem = system; 106 } 107 } 108 } 109 110 private AssetManager(boolean isSystem) { 111 if (DEBUG_REFS) { 112 synchronized (this) { 113 mNumRefs = 0; 114 incRefsLocked(this.hashCode()); 115 } 116 } 117 init(); 118 if (localLOGV) Log.v(TAG, "New asset manager: " + this); 119 } 120 121 /** 122 * Return a global shared asset manager that provides access to only 123 * system assets (no application assets). 124 * {@hide} 125 */ 126 public static AssetManager getSystem() { 127 ensureSystemAssets(); 128 return sSystem; 129 } 130 131 /** 132 * Close this asset manager. 133 */ 134 public void close() { 135 synchronized(this) { 136 //System.out.println("Release: num=" + mNumRefs 137 // + ", released=" + mReleased); 138 if (mOpen) { 139 mOpen = false; 140 decRefsLocked(this.hashCode()); 141 } 142 } 143 } 144 145 /** 146 * Retrieve the string value associated with a particular resource 147 * identifier for the current configuration / skin. 148 */ 149 /*package*/ final CharSequence getResourceText(int ident) { 150 synchronized (this) { 151 TypedValue tmpValue = mValue; 152 int block = loadResourceValue(ident, tmpValue, true); 153 if (block >= 0) { 154 if (tmpValue.type == TypedValue.TYPE_STRING) { 155 return mStringBlocks[block].get(tmpValue.data); 156 } 157 return tmpValue.coerceToString(); 158 } 159 } 160 return null; 161 } 162 163 /** 164 * Retrieve the string value associated with a particular resource 165 * identifier for the current configuration / skin. 166 */ 167 /*package*/ final CharSequence getResourceBagText(int ident, int bagEntryId) { 168 synchronized (this) { 169 TypedValue tmpValue = mValue; 170 int block = loadResourceBagValue(ident, bagEntryId, tmpValue, true); 171 if (block >= 0) { 172 if (tmpValue.type == TypedValue.TYPE_STRING) { 173 return mStringBlocks[block].get(tmpValue.data); 174 } 175 return tmpValue.coerceToString(); 176 } 177 } 178 return null; 179 } 180 181 /** 182 * Retrieve the string array associated with a particular resource 183 * identifier. 184 * @param id Resource id of the string array 185 */ 186 /*package*/ final String[] getResourceStringArray(final int id) { 187 String[] retArray = getArrayStringResource(id); 188 return retArray; 189 } 190 191 192 /*package*/ final boolean getResourceValue(int ident, 193 TypedValue outValue, 194 boolean resolveRefs) 195 { 196 int block = loadResourceValue(ident, outValue, resolveRefs); 197 if (block >= 0) { 198 if (outValue.type != TypedValue.TYPE_STRING) { 199 return true; 200 } 201 outValue.string = mStringBlocks[block].get(outValue.data); 202 return true; 203 } 204 return false; 205 } 206 207 /** 208 * Retrieve the text array associated with a particular resource 209 * identifier. 210 * @param id Resource id of the string array 211 */ 212 /*package*/ final CharSequence[] getResourceTextArray(final int id) { 213 int[] rawInfoArray = getArrayStringInfo(id); 214 int rawInfoArrayLen = rawInfoArray.length; 215 final int infoArrayLen = rawInfoArrayLen / 2; 216 int block; 217 int index; 218 CharSequence[] retArray = new CharSequence[infoArrayLen]; 219 for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) { 220 block = rawInfoArray[i]; 221 index = rawInfoArray[i + 1]; 222 retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null; 223 } 224 return retArray; 225 } 226 227 /*package*/ final boolean getThemeValue(int theme, int ident, 228 TypedValue outValue, boolean resolveRefs) { 229 int block = loadThemeAttributeValue(theme, ident, outValue, resolveRefs); 230 if (block >= 0) { 231 if (outValue.type != TypedValue.TYPE_STRING) { 232 return true; 233 } 234 StringBlock[] blocks = mStringBlocks; 235 if (blocks == null) { 236 ensureStringBlocks(); 237 } 238 outValue.string = blocks[block].get(outValue.data); 239 return true; 240 } 241 return false; 242 } 243 244 /*package*/ final void ensureStringBlocks() { 245 if (mStringBlocks == null) { 246 synchronized (this) { 247 if (mStringBlocks == null) { 248 makeStringBlocks(true); 249 } 250 } 251 } 252 } 253 254 private final void makeStringBlocks(boolean copyFromSystem) { 255 final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0; 256 final int num = getStringBlockCount(); 257 mStringBlocks = new StringBlock[num]; 258 if (localLOGV) Log.v(TAG, "Making string blocks for " + this 259 + ": " + num); 260 for (int i=0; i<num; i++) { 261 if (i < sysNum) { 262 mStringBlocks[i] = sSystem.mStringBlocks[i]; 263 } else { 264 mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); 265 } 266 } 267 } 268 269 /*package*/ final CharSequence getPooledString(int block, int id) { 270 //System.out.println("Get pooled: block=" + block 271 // + ", id=#" + Integer.toHexString(id) 272 // + ", blocks=" + mStringBlocks); 273 return mStringBlocks[block-1].get(id); 274 } 275 276 /** 277 * Open an asset using ACCESS_STREAMING mode. This provides access to 278 * files that have been bundled with an application as assets -- that is, 279 * files placed in to the "assets" directory. 280 * 281 * @param fileName The name of the asset to open. This name can be 282 * hierarchical. 283 * 284 * @see #open(String, int) 285 * @see #list 286 */ 287 public final InputStream open(String fileName) throws IOException { 288 return open(fileName, ACCESS_STREAMING); 289 } 290 291 /** 292 * Open an asset using an explicit access mode, returning an InputStream to 293 * read its contents. This provides access to files that have been bundled 294 * with an application as assets -- that is, files placed in to the 295 * "assets" directory. 296 * 297 * @param fileName The name of the asset to open. This name can be 298 * hierarchical. 299 * @param accessMode Desired access mode for retrieving the data. 300 * 301 * @see #ACCESS_UNKNOWN 302 * @see #ACCESS_STREAMING 303 * @see #ACCESS_RANDOM 304 * @see #ACCESS_BUFFER 305 * @see #open(String) 306 * @see #list 307 */ 308 public final InputStream open(String fileName, int accessMode) 309 throws IOException { 310 synchronized (this) { 311 if (!mOpen) { 312 throw new RuntimeException("Assetmanager has been closed"); 313 } 314 int asset = openAsset(fileName, accessMode); 315 if (asset != 0) { 316 AssetInputStream res = new AssetInputStream(asset); 317 incRefsLocked(res.hashCode()); 318 return res; 319 } 320 } 321 throw new FileNotFoundException("Asset file: " + fileName); 322 } 323 324 public final AssetFileDescriptor openFd(String fileName) 325 throws IOException { 326 synchronized (this) { 327 if (!mOpen) { 328 throw new RuntimeException("Assetmanager has been closed"); 329 } 330 ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets); 331 if (pfd != null) { 332 return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); 333 } 334 } 335 throw new FileNotFoundException("Asset file: " + fileName); 336 } 337 338 /** 339 * Return a String array of all the assets at the given path. 340 * 341 * @param path A relative path within the assets, i.e., "docs/home.html". 342 * 343 * @return String[] Array of strings, one for each asset. These file 344 * names are relative to 'path'. You can open the file by 345 * concatenating 'path' and a name in the returned string (via 346 * File) and passing that to open(). 347 * 348 * @see #open 349 */ 350 public native final String[] list(String path) 351 throws IOException; 352 353 /** 354 * {@hide} 355 * Open a non-asset file as an asset using ACCESS_STREAMING mode. This 356 * provides direct access to all of the files included in an application 357 * package (not only its assets). Applications should not normally use 358 * this. 359 * 360 * @see #open(String) 361 */ 362 public final InputStream openNonAsset(String fileName) throws IOException { 363 return openNonAsset(0, fileName, ACCESS_STREAMING); 364 } 365 366 /** 367 * {@hide} 368 * Open a non-asset file as an asset using a specific access mode. This 369 * provides direct access to all of the files included in an application 370 * package (not only its assets). Applications should not normally use 371 * this. 372 * 373 * @see #open(String, int) 374 */ 375 public final InputStream openNonAsset(String fileName, int accessMode) 376 throws IOException { 377 return openNonAsset(0, fileName, accessMode); 378 } 379 380 /** 381 * {@hide} 382 * Open a non-asset in a specified package. Not for use by applications. 383 * 384 * @param cookie Identifier of the package to be opened. 385 * @param fileName Name of the asset to retrieve. 386 */ 387 public final InputStream openNonAsset(int cookie, String fileName) 388 throws IOException { 389 return openNonAsset(cookie, fileName, ACCESS_STREAMING); 390 } 391 392 /** 393 * {@hide} 394 * Open a non-asset in a specified package. Not for use by applications. 395 * 396 * @param cookie Identifier of the package to be opened. 397 * @param fileName Name of the asset to retrieve. 398 * @param accessMode Desired access mode for retrieving the data. 399 */ 400 public final InputStream openNonAsset(int cookie, String fileName, int accessMode) 401 throws IOException { 402 synchronized (this) { 403 if (!mOpen) { 404 throw new RuntimeException("Assetmanager has been closed"); 405 } 406 int asset = openNonAssetNative(cookie, fileName, accessMode); 407 if (asset != 0) { 408 AssetInputStream res = new AssetInputStream(asset); 409 incRefsLocked(res.hashCode()); 410 return res; 411 } 412 } 413 throw new FileNotFoundException("Asset absolute file: " + fileName); 414 } 415 416 public final AssetFileDescriptor openNonAssetFd(String fileName) 417 throws IOException { 418 return openNonAssetFd(0, fileName); 419 } 420 421 public final AssetFileDescriptor openNonAssetFd(int cookie, 422 String fileName) throws IOException { 423 synchronized (this) { 424 if (!mOpen) { 425 throw new RuntimeException("Assetmanager has been closed"); 426 } 427 ParcelFileDescriptor pfd = openNonAssetFdNative(cookie, 428 fileName, mOffsets); 429 if (pfd != null) { 430 return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); 431 } 432 } 433 throw new FileNotFoundException("Asset absolute file: " + fileName); 434 } 435 436 /** 437 * Retrieve a parser for a compiled XML file. 438 * 439 * @param fileName The name of the file to retrieve. 440 */ 441 public final XmlResourceParser openXmlResourceParser(String fileName) 442 throws IOException { 443 return openXmlResourceParser(0, fileName); 444 } 445 446 /** 447 * Retrieve a parser for a compiled XML file. 448 * 449 * @param cookie Identifier of the package to be opened. 450 * @param fileName The name of the file to retrieve. 451 */ 452 public final XmlResourceParser openXmlResourceParser(int cookie, 453 String fileName) throws IOException { 454 XmlBlock block = openXmlBlockAsset(cookie, fileName); 455 XmlResourceParser rp = block.newParser(); 456 block.close(); 457 return rp; 458 } 459 460 /** 461 * {@hide} 462 * Retrieve a non-asset as a compiled XML file. Not for use by 463 * applications. 464 * 465 * @param fileName The name of the file to retrieve. 466 */ 467 /*package*/ final XmlBlock openXmlBlockAsset(String fileName) 468 throws IOException { 469 return openXmlBlockAsset(0, fileName); 470 } 471 472 /** 473 * {@hide} 474 * Retrieve a non-asset as a compiled XML file. Not for use by 475 * applications. 476 * 477 * @param cookie Identifier of the package to be opened. 478 * @param fileName Name of the asset to retrieve. 479 */ 480 /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName) 481 throws IOException { 482 synchronized (this) { 483 if (!mOpen) { 484 throw new RuntimeException("Assetmanager has been closed"); 485 } 486 int xmlBlock = openXmlAssetNative(cookie, fileName); 487 if (xmlBlock != 0) { 488 XmlBlock res = new XmlBlock(this, xmlBlock); 489 incRefsLocked(res.hashCode()); 490 return res; 491 } 492 } 493 throw new FileNotFoundException("Asset XML file: " + fileName); 494 } 495 496 /*package*/ void xmlBlockGone(int id) { 497 synchronized (this) { 498 decRefsLocked(id); 499 } 500 } 501 502 /*package*/ final int createTheme() { 503 synchronized (this) { 504 if (!mOpen) { 505 throw new RuntimeException("Assetmanager has been closed"); 506 } 507 int res = newTheme(); 508 incRefsLocked(res); 509 return res; 510 } 511 } 512 513 /*package*/ final void releaseTheme(int theme) { 514 synchronized (this) { 515 deleteTheme(theme); 516 decRefsLocked(theme); 517 } 518 } 519 520 protected void finalize() throws Throwable { 521 try { 522 if (DEBUG_REFS && mNumRefs != 0) { 523 Log.w(TAG, "AssetManager " + this 524 + " finalized with non-zero refs: " + mNumRefs); 525 if (mRefStacks != null) { 526 for (RuntimeException e : mRefStacks.values()) { 527 Log.w(TAG, "Reference from here", e); 528 } 529 } 530 } 531 destroy(); 532 } finally { 533 super.finalize(); 534 } 535 } 536 537 public final class AssetInputStream extends InputStream { 538 public final int getAssetInt() { 539 return mAsset; 540 } 541 private AssetInputStream(int asset) 542 { 543 mAsset = asset; 544 mLength = getAssetLength(asset); 545 } 546 public final int read() throws IOException { 547 return readAssetChar(mAsset); 548 } 549 public final boolean markSupported() { 550 return true; 551 } 552 public final int available() throws IOException { 553 long len = getAssetRemainingLength(mAsset); 554 return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len; 555 } 556 public final void close() throws IOException { 557 synchronized (AssetManager.this) { 558 if (mAsset != 0) { 559 destroyAsset(mAsset); 560 mAsset = 0; 561 decRefsLocked(hashCode()); 562 } 563 } 564 } 565 public final void mark(int readlimit) { 566 mMarkPos = seekAsset(mAsset, 0, 0); 567 } 568 public final void reset() throws IOException { 569 seekAsset(mAsset, mMarkPos, -1); 570 } 571 public final int read(byte[] b) throws IOException { 572 return readAsset(mAsset, b, 0, b.length); 573 } 574 public final int read(byte[] b, int off, int len) throws IOException { 575 return readAsset(mAsset, b, off, len); 576 } 577 public final long skip(long n) throws IOException { 578 long pos = seekAsset(mAsset, 0, 0); 579 if ((pos+n) > mLength) { 580 n = mLength-pos; 581 } 582 if (n > 0) { 583 seekAsset(mAsset, n, 0); 584 } 585 return n; 586 } 587 588 protected void finalize() throws Throwable 589 { 590 close(); 591 } 592 593 private int mAsset; 594 private long mLength; 595 private long mMarkPos; 596 } 597 598 /** 599 * Add an additional set of assets to the asset manager. This can be 600 * either a directory or ZIP file. Not for use by applications. Returns 601 * the cookie of the added asset, or 0 on failure. 602 * {@hide} 603 */ 604 public native final int addAssetPath(String path); 605 606 /** 607 * Add multiple sets of assets to the asset manager at once. See 608 * {@link #addAssetPath(String)} for more information. Returns array of 609 * cookies for each added asset with 0 indicating failure, or null if 610 * the input array of paths is null. 611 * {@hide} 612 */ 613 public final int[] addAssetPaths(String[] paths) { 614 if (paths == null) { 615 return null; 616 } 617 618 int[] cookies = new int[paths.length]; 619 for (int i = 0; i < paths.length; i++) { 620 cookies[i] = addAssetPath(paths[i]); 621 } 622 623 return cookies; 624 } 625 626 /** 627 * Determine whether the state in this asset manager is up-to-date with 628 * the files on the filesystem. If false is returned, you need to 629 * instantiate a new AssetManager class to see the new data. 630 * {@hide} 631 */ 632 public native final boolean isUpToDate(); 633 634 /** 635 * Change the locale being used by this asset manager. Not for use by 636 * applications. 637 * {@hide} 638 */ 639 public native final void setLocale(String locale); 640 641 /** 642 * Get the locales that this asset manager contains data for. 643 */ 644 public native final String[] getLocales(); 645 646 /** 647 * Change the configuation used when retrieving resources. Not for use by 648 * applications. 649 * {@hide} 650 */ 651 public native final void setConfiguration(int mcc, int mnc, String locale, 652 int orientation, int touchscreen, int density, int keyboard, 653 int keyboardHidden, int navigation, int screenWidth, int screenHeight, 654 int screenLayout, int uiMode, int majorVersion); 655 656 /** 657 * Retrieve the resource identifier for the given resource name. 658 */ 659 /*package*/ native final int getResourceIdentifier(String type, 660 String name, 661 String defPackage); 662 663 /*package*/ native final String getResourceName(int resid); 664 /*package*/ native final String getResourcePackageName(int resid); 665 /*package*/ native final String getResourceTypeName(int resid); 666 /*package*/ native final String getResourceEntryName(int resid); 667 668 private native final int openAsset(String fileName, int accessMode); 669 private final native ParcelFileDescriptor openAssetFd(String fileName, 670 long[] outOffsets) throws IOException; 671 private native final int openNonAssetNative(int cookie, String fileName, 672 int accessMode); 673 private native ParcelFileDescriptor openNonAssetFdNative(int cookie, 674 String fileName, long[] outOffsets) throws IOException; 675 private native final void destroyAsset(int asset); 676 private native final int readAssetChar(int asset); 677 private native final int readAsset(int asset, byte[] b, int off, int len); 678 private native final long seekAsset(int asset, long offset, int whence); 679 private native final long getAssetLength(int asset); 680 private native final long getAssetRemainingLength(int asset); 681 682 /** Returns true if the resource was found, filling in mRetStringBlock and 683 * mRetData. */ 684 private native final int loadResourceValue(int ident, TypedValue outValue, 685 boolean resolve); 686 /** Returns true if the resource was found, filling in mRetStringBlock and 687 * mRetData. */ 688 private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, 689 boolean resolve); 690 /*package*/ static final int STYLE_NUM_ENTRIES = 6; 691 /*package*/ static final int STYLE_TYPE = 0; 692 /*package*/ static final int STYLE_DATA = 1; 693 /*package*/ static final int STYLE_ASSET_COOKIE = 2; 694 /*package*/ static final int STYLE_RESOURCE_ID = 3; 695 /*package*/ static final int STYLE_CHANGING_CONFIGURATIONS = 4; 696 /*package*/ static final int STYLE_DENSITY = 5; 697 /*package*/ native static final boolean applyStyle(int theme, 698 int defStyleAttr, int defStyleRes, int xmlParser, 699 int[] inAttrs, int[] outValues, int[] outIndices); 700 /*package*/ native final boolean retrieveAttributes( 701 int xmlParser, int[] inAttrs, int[] outValues, int[] outIndices); 702 /*package*/ native final int getArraySize(int resource); 703 /*package*/ native final int retrieveArray(int resource, int[] outValues); 704 private native final int getStringBlockCount(); 705 private native final int getNativeStringBlock(int block); 706 707 /** 708 * {@hide} 709 */ 710 public native final String getCookieName(int cookie); 711 712 /** 713 * {@hide} 714 */ 715 public native static final int getGlobalAssetCount(); 716 717 /** 718 * {@hide} 719 */ 720 public native static final String getAssetAllocations(); 721 722 /** 723 * {@hide} 724 */ 725 public native static final int getGlobalAssetManagerCount(); 726 727 private native final int newTheme(); 728 private native final void deleteTheme(int theme); 729 /*package*/ native static final void applyThemeStyle(int theme, int styleRes, boolean force); 730 /*package*/ native static final void copyTheme(int dest, int source); 731 /*package*/ native static final int loadThemeAttributeValue(int theme, int ident, 732 TypedValue outValue, 733 boolean resolve); 734 /*package*/ native static final void dumpTheme(int theme, int priority, String tag, String prefix); 735 736 private native final int openXmlAssetNative(int cookie, String fileName); 737 738 private native final String[] getArrayStringResource(int arrayRes); 739 private native final int[] getArrayStringInfo(int arrayRes); 740 /*package*/ native final int[] getArrayIntResource(int arrayRes); 741 742 private native final void init(); 743 private native final void destroy(); 744 745 private final void incRefsLocked(int id) { 746 if (DEBUG_REFS) { 747 if (mRefStacks == null) { 748 mRefStacks = new HashMap<Integer, RuntimeException>(); 749 RuntimeException ex = new RuntimeException(); 750 ex.fillInStackTrace(); 751 mRefStacks.put(this.hashCode(), ex); 752 } 753 } 754 mNumRefs++; 755 } 756 757 private final void decRefsLocked(int id) { 758 if (DEBUG_REFS && mRefStacks != null) { 759 mRefStacks.remove(id); 760 } 761 mNumRefs--; 762 //System.out.println("Dec streams: mNumRefs=" + mNumRefs 763 // + " mReleased=" + mReleased); 764 if (mNumRefs == 0) { 765 destroy(); 766 } 767 } 768 } 769