问题一:更新ADT20后,创建工程时无法点击Finish。
问题二:刚才更新SDK, ADT,可是新建项目的时候却出现问题了,操作一路默认,出现了下面的提示:
This template depends on the Android Support library, which is either not installed, or the
template depends on a more recent version than the one you hava installed.
http://developer.android.com/tools/extras/support-library.html
Required version: 8
Installed version: Not installed
You can install or upgrade it by clicking the Install button below, or alternatively, you can
install it outside of Eclipse with the SDK Manager, the click on "Check Again" to proceed.
两个按钮:Install/Upgrade Check Again
按照提示安装升级了一下,再次Check Again, 还是这个错误提示。
================================
解决办法一:
1.进入安装目录“android-sdk-windows”下,打开“SDK Manager.exe”;(不要在eclipse里打开,这算是一个BUG吧...)
2.找到Extras,选中Android Support Library,Delete package卸载掉当前的Android Support Library;
3.在Eclipse中创建一个新的工程,在最后一步的时候重新安装Android Support Library即可。
解决办法二:
1.关闭 eclipse ,结束 adb.exe 进程<TextView
android:id="@+id/top_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:layout_alignWithParentIfMissing="true"
android:layout_centerInParent="true"
android:layout_toLeftOf="@id/top_btn_right"
android:layout_toRightOf="@id/top_btn_left"
android:enabled="true"
android:scrollHorizontally="true"
android:gravity="center"
android:focusable="true"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:focusableInTouchMode="true"
android:textColor="#ffffffff"
android:textSize="24.0sp" />
1.加蓝色部分注意啦都必须加上,不然没效果!原因是其他视图影响后 android:focusable="true"可能获取不到焦点。
2.所以加上android:focusableInTouchMode="true" 解决获取不到焦点,触摸模式获取后及滚动开始。
3.如果还是不能解决效果就只能在代码中获取焦点了,使用textview.requestFocus() 方法获取。
花开两朵,各表一枝。上回书讲到了数据cache的一些流程。这回书要说的就是gallery3d刚开始在手机上运行cache数据的流程。
如果你切换手机的语言,跟此流程一致。这个在上回书也提到了。
首先来看看startNewCacheThread函数:
private void startNewCacheThread() { restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() //开启线程,如果之前有同样的线程已经存在,关闭之前线程 { public void run() { refresh(CacheService.this);//cache媒体数据 } }); }
restartThread函数的源代码如下,不细说。
private static final void restartThread(final AtomicReference<Thread> threadRef, final String name, final Runnable action) { // Create a new thread. final Thread newThread = new Thread() { public void run() { try { action.run(); } finally { threadRef.compareAndSet(this, null); } } }; newThread.setName(name); newThread.start();
// Interrupt any existing thread. final Thread existingThread = threadRef.getAndSet(newThread); if (existingThread != null) { existingThread.interrupt(); } }
接下来分析关键的refresh函数:
private final static void refresh(final Context context) { // First we build the album cache. // This is the meta-data about the albums / buckets on the SD card. Log.i(TAG, "Refreshing cache."); sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); final ArrayList<MediaSet> sets = new ArrayList<MediaSet>(); LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>(); Log.i(TAG, "Building albums."); final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build(); final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build(); final ContentResolver cr = context.getContentResolver(); try { final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, null, null, DEFAULT_BUCKET_SORT_ORDER);//搜索图像 final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, null, null, DEFAULT_BUCKET_SORT_ORDER);//搜索视频 Cursor[] cursors = new Cursor[2]; cursors[0] = cursorImages; cursors[1] = cursorVideos; final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.BUCKET_DISPLAY_NAME, SortCursor.TYPE_STRING, true); try { if (sortCursor != null && sortCursor.moveToFirst()) { sets.ensureCapacity(sortCursor.getCount()); acceleratedSets = new LongSparseArray<MediaSet>(sortCursor.getCount()); MediaSet cameraSet = new MediaSet(); cameraSet.mId = LocalDataSource.CAMERA_BUCKET_ID;//相机的bucket id cameraSet.mName = context.getResources().getString(R.string.camera); sets.add(cameraSet); acceleratedSets.put(cameraSet.mId, cameraSet);//将相机的照片集加入acceleratedSets映射表 do { if (Thread.interrupted()) { return; } long setId = sortCursor.getLong(BUCKET_ID_INDEX); MediaSet mediaSet = findSet(setId, acceleratedSets); if (mediaSet == null) {//如果在acceleratedSets映射表找不到setId, 那么加入acceleratedSets映射表 mediaSet = new MediaSet(); mediaSet.mId = setId; mediaSet.mName = sortCursor.getString(BUCKET_NAME_INDEX); sets.add(mediaSet); acceleratedSets.put(setId, mediaSet); } mediaSet.mHasImages |= (sortCursor.getCurrentCursorIndex() == 0); mediaSet.mHasVideos |= (sortCursor.getCurrentCursorIndex() == 1); } while (sortCursor.moveToNext()); sortCursor.close(); } } finally { if (sortCursor != null) sortCursor.close(); } sAlbumCache.put(ALBUM_CACHE_INCOMPLETE_INDEX, sDummyData, 0);//写入dummy data到ALBUM_CACHE_INCOMPLETE_INDEX块,说明目前cache开始 writeSetsToCache(sets);//cache媒体集 Log.i(TAG, "Done building albums."); // Now we must cache the items contained in every album / bucket. populateMediaItemsForSets(context, sets, acceleratedSets, false);//将media item归类到sets,并将item信息cache } catch (Exception e) { // If the database operation failed for any reason. ; } sAlbumCache.delete(ALBUM_CACHE_INCOMPLETE_INDEX);//删除ALBUM_CACHE_INCOMPLETE_INDEX块,说明目前cache技术 }
CAMERA_BUCKET_ID的定义是这样的,其实就是相机目录的hash码。
public static final int CAMERA_BUCKET_ID = getBucketId(CAMERA_BUCKET_NAME);
public static final String CAMERA_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/DCIM/" + CAMERA_STRING;//相机目录
public static int getBucketId(String path) { return (path.toLowerCase().hashCode()); }
下面是writeSetsToCache函数的功能,就是cache相册信息:
private static final void writeSetsToCache(final ArrayList<MediaSet> sets) { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final int numSets = sets.size(); final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256)); try { dos.writeInt(numSets); for (int i = 0; i < numSets; ++i) { if (Thread.interrupted()) { return; } final MediaSet set = sets.get(i); dos.writeLong(set.mId);//相册id Utils.writeUTF(dos, set.mName);//相册名称 dos.writeBoolean(set.mHasImages);//是否是图片集 dos.writeBoolean(set.mHasVideos);//是否是视频集 } dos.flush(); sAlbumCache.put(ALBUM_CACHE_METADATA_INDEX, bos.toByteArray(), 0);//写chunk文件 dos.close(); if (numSets == 0) { sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); } sAlbumCache.flush();//写index文件 } catch (IOException e) { Log.e(TAG, "Error writing albums to diskcache."); sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); } }
关键函数sAlbumCache.put(ALBUM_CACHE_METADATA_INDEX, bos.toByteArray(), 0),我们来看看put函数做了些什么?
public void put(long key, byte[] data, long timestamp) { // Check to see if the record already exists. Record record = null; Log.i(TAG, "DiskCache, put key:"+key); synchronized (mIndexMap) { record = mIndexMap.get(key); } if (record != null && data.length <= record.sizeOnDisk) {//如果记录在mIndexMap已经存在,替换当前记录 // We just replace the chunk. int currentChunk = record.chunk; try { Log.i(TAG, "DiskCache, get chunk file"); RandomAccessFile chunkFile = getChunkFile(record.chunk);//获取chunk文件 if (chunkFile != null) { chunkFile.seek(record.offset); chunkFile.write(data); synchronized (mIndexMap) { mIndexMap.put(key, new Record(currentChunk, record.offset, data.length, record.sizeOnDisk, timestamp));//更改mIndexMap的key的记录数据 } return; } } catch (Exception e) { Log.e(TAG, "Unable to read from chunk file"); } } //如果记录不存在,将新的chunk添加到当前chunk尾部 // Append a new chunk to the current chunk. final int chunk = mTailChunk; final RandomAccessFile chunkFile = getChunkFile(chunk); if (chunkFile != null) { try { //将数据写入chunk文件 final int offset = (int) chunkFile.length(); chunkFile.seek(offset); chunkFile.write(data); synchronized (mIndexMap) { mIndexMap.put(key, new Record(chunk, offset, data.length, data.length, timestamp));//将新纪录添加到mIndexMap } if (offset + data.length > CHUNK_SIZE) { ++mTailChunk; } if (++mNumInsertions == 64) { // CR: 64 => constant // Flush the index file at a regular interval. To avoid // writing the entire // index each time the format could be changed to an // append-only journal with // a snapshot generated on exit. flush();//写64次后,写索引文件 } } catch (IOException e) { Log.e(TAG, "Unable to write new entry to chunk file"); } } else { Log.e(TAG, "getChunkFile() returned null"); } }
put函数就是把相册集的信息写入chunk文件。
那继续看sAlbumCache.flush()函数:
public void flush() { if (mNumInsertions != 0) { mNumInsertions = 0; writeIndex(); } }
private void writeIndex() { final String indexFilePath = getIndexFilePath(); try { // Create a temporary file to write the index into. File tempFile = File.createTempFile("DiskCacheIndex", null);//创建临时索引文件 final FileOutputStream fileOutput = new FileOutputStream(tempFile); final BufferedOutputStream bufferedOutput = new BufferedOutputStream(fileOutput, 1024); final DataOutputStream dataOutput = new DataOutputStream(bufferedOutput); // Write the index header. final int numRecords = mIndexMap.size(); dataOutput.writeInt(INDEX_HEADER_MAGIC);//索引文件头部的关键字 dataOutput.writeInt(INDEX_HEADER_VERSION);//索引文件头部的关键字 dataOutput.writeShort(mTailChunk);//尾部chunk索引数 dataOutput.writeInt(numRecords);//记录条数 // Write the records. //将chunk文件信息写入索引文件 for (int i = 0; i < numRecords; ++i) { final long key = mIndexMap.keyAt(i); final Record record = mIndexMap.valueAt(i); dataOutput.writeLong(key); dataOutput.writeShort(record.chunk); dataOutput.writeInt(record.offset); dataOutput.writeInt(record.size); dataOutput.writeInt(record.sizeOnDisk); dataOutput.writeLong(record.timestamp); } // Close the file. dataOutput.close(); Log.d(TAG, "Wrote index with " + numRecords + " records."); // Atomically overwrite the old index file. tempFile.renameTo(new File(indexFilePath));//将临时索引文件重命名 } catch (IOException e) { Log.e(TAG, "Unable to write the index file " + indexFilePath); } }
再回到refresh函数,populateMediaItemsForSets做了哪些事情?
private final static void populateMediaItemsForSets(final Context context, final ArrayList<MediaSet> sets, final LongSparseArray<MediaSet> acceleratedSets, boolean useWhere) { if (sets == null || sets.size() == 0 || Thread.interrupted()) { return; } Log.i(TAG, "Building items."); final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI; final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI; final ContentResolver cr = context.getContentResolver(); String whereClause = null; if (useWhere) { int numSets = sets.size(); StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + " in ("); for (int i = 0; i < numSets; ++i) { whereString.append(sets.get(i).mId); if (i != numSets - 1) { whereString.append(","); } } whereString.append(")"); whereClause = whereString.toString(); Log.i(TAG, "Updating dirty albums where " + whereClause); } try { final Cursor cursorImages = cr.query(uriImages, PROJECTION_IMAGES, whereClause, null, DEFAULT_IMAGE_SORT_ORDER); final Cursor cursorVideos = cr.query(uriVideos, PROJECTION_VIDEOS, whereClause, null, DEFAULT_VIDEO_SORT_ORDER); final Cursor[] cursors = new Cursor[2]; cursors[0] = cursorImages; cursors[1] = cursorVideos; final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.DATE_TAKEN, SortCursor.TYPE_NUMERIC, true); if (Thread.interrupted()) { return; } try { if (sortCursor != null && sortCursor.moveToFirst()) { final int count = sortCursor.getCount(); final int numSets = sets.size(); final int approximateCountPerSet = count / numSets; for (int i = 0; i < numSets; ++i) { final MediaSet set = sets.get(i); set.setNumExpectedItems(approximateCountPerSet); } do { if (Thread.interrupted()) { return; } final MediaItem item = new MediaItem(); final boolean isVideo = (sortCursor.getCurrentCursorIndex() == 1); if (isVideo) { populateVideoItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_VIDEOS);//获取视频item的信息 } else { populateMediaItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_IMAGES);//获取图片item的信息 } final long setId = sortCursor.getLong(MEDIA_BUCKET_ID_INDEX); final MediaSet set = findSet(setId, acceleratedSets); if (set != null) { set.addItem(item);//将item加入相册集 } } while (sortCursor.moveToNext()); } } finally { if (sortCursor != null) sortCursor.close(); } } catch (Exception e) { // If the database operation failed for any reason ; } if (sets.size() > 0) { writeItemsToCache(sets);//缓存sets的item Log.i(TAG, "Done building items."); } }
关键函数writeItemsToCache就是缓存每个set的items。
private static final void writeItemsToCache(final ArrayList<MediaSet> sets) { final int numSets = sets.size(); for (int i = 0; i < numSets; ++i) { if (Thread.interrupted()) { return; } writeItemsForASet(sets.get(i)); } sAlbumCache.flush(); }
writeItemsForASet就是将item的信息写入chunk文件,sAlbumCache.flush就是将chunk文件信息写入index文件。
private static final void writeItemsForASet(final MediaSet set) { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256)); try { final ArrayList<MediaItem> items = set.getItems(); final int numItems = items.size(); dos.writeInt(numItems); dos.writeLong(set.mMinTimestamp); dos.writeLong(set.mMaxTimestamp); for (int i = 0; i < numItems; ++i) { MediaItem item = items.get(i); if (set.mId == LocalDataSource.CAMERA_BUCKET_ID || set.mId == LocalDataSource.DOWNLOAD_BUCKET_ID) { // Reverse the display order for the camera bucket - want // the latest first. item = items.get(numItems - i - 1); } dos.writeLong(item.mId); Utils.writeUTF(dos, item.mCaption); Utils.writeUTF(dos, item.mMimeType); dos.writeInt(item.getMediaType()); dos.writeDouble(item.mLatitude); dos.writeDouble(item.mLongitude); dos.writeLong(item.mDateTakenInMs); dos.writeBoolean(item.mTriedRetrievingExifDateTaken); dos.writeLong(item.mDateAddedInSec); dos.writeLong(item.mDateModifiedInSec); dos.writeInt(item.mDurationInSec); dos.writeInt((int) item.mRotation); Utils.writeUTF(dos, item.mFilePath); } dos.flush(); sAlbumCache.put(set.mId, bos.toByteArray(), 0); dos.close(); } catch (IOException e) { Log.e(TAG, "Error writing to diskcache for set " + set.mName); sAlbumCache.deleteAll(); putLocaleForAlbumCache(Locale.getDefault()); } }