Android實現Glide/Coil樣式圖/視頻加載框架,Kotlin

Android實現Glide/Coil樣式圖/視頻加載框架,Kotlin

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /><uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@android:color/white"android:padding="1px"><ImageViewandroid:id="@+id/image"android:layout_width="match_parent"android:layout_height="180px"android:scaleType="centerCrop" /></LinearLayout>


/** Copyright (C) 2011 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** A cache that uses a bounded amount of space on a filesystem. Each cache* entry has a string key and a fixed number of values. Each key must match* the regex <strong>[a-z0-9_-]{1,120}</strong>. Values are byte sequences,* accessible as streams or files. Each value must be between {@code 0} and* {@code Integer.MAX_VALUE} bytes in length.** <p>The cache stores its data in a directory on the filesystem. This* directory must be exclusive to the cache; the cache may delete or overwrite* files from its directory. It is an error for multiple processes to use the* same cache directory at the same time.** <p>This cache limits the number of bytes that it will store on the* filesystem. When the number of stored bytes exceeds the limit, the cache will* remove entries in the background until the limit is satisfied. The limit is* not strict: the cache may temporarily exceed it while waiting for files to be* deleted. The limit does not include filesystem overhead or the cache* journal so space-sensitive applications should set a conservative limit.** <p>Clients call {@link #edit} to create or update the values of an entry. An* entry may have only one editor at one time; if a value is not available to be* edited then {@link #edit} will return null.* <ul>* <li>When an entry is being <strong>created</strong> it is necessary to* supply a full set of values; the empty value should be used as a* placeholder if necessary.* <li>When an entry is being <strong>edited</strong>, it is not necessary* to supply data for every value; values default to their previous* value.* </ul>* Every {@link #edit} call must be matched by a call to {@link Editor#commit}* or {@link Editor#abort}. Committing is atomic: a read observes the full set* of values as they were before or after the commit, but never a mix of values.** <p>Clients call {@link #get} to read a snapshot of an entry. The read will* observe the value at the time that {@link #get} was called. Updates and* removals after the call do not impact ongoing reads.** <p>This class is tolerant of some I/O errors. If files are missing from the* filesystem, the corresponding entries will be dropped from the cache. If* an error occurs while writing a cache value, the edit will fail silently.* Callers should handle other problems by catching {@code IOException} and* responding appropriately.*/
public final class DiskLruCache implements Closeable {static final String JOURNAL_FILE = "journal";static final String JOURNAL_FILE_TEMP = "journal.tmp";static final String JOURNAL_FILE_BACKUP = "journal.bkp";static final String MAGIC = "libcore.io.DiskLruCache";static final String VERSION_1 = "1";static final long ANY_SEQUENCE_NUMBER = -1;static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);private static final String CLEAN = "CLEAN";private static final String DIRTY = "DIRTY";private static final String REMOVE = "REMOVE";private static final String READ = "READ";/** This cache uses a journal file named "journal". A typical journal file* looks like this:*     libcore.io.DiskLruCache*     1*     100*     2**     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054*     DIRTY 335c4c6028171cfddfbaae1a9c313c52*     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342*     REMOVE 335c4c6028171cfddfbaae1a9c313c52*     DIRTY 1ab96a171faeeee38496d8b330771a7a*     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234*     READ 335c4c6028171cfddfbaae1a9c313c52*     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6** The first five lines of the journal form its header. They are the* constant string "libcore.io.DiskLruCache", the disk cache's version,* the application's version, the value count, and a blank line.** Each of the subsequent lines in the file is a record of the state of a* cache entry. Each line contains space-separated values: a state, a key,* and optional state-specific values.*   o DIRTY lines track that an entry is actively being created or updated.*     Every successful DIRTY action should be followed by a CLEAN or REMOVE*     action. DIRTY lines without a matching CLEAN or REMOVE indicate that*     temporary files may need to be deleted.*   o CLEAN lines track a cache entry that has been successfully published*     and may be read. A publish line is followed by the lengths of each of*     its values.*   o READ lines track accesses for LRU.*   o REMOVE lines track entries that have been deleted.** The journal file is appended to as cache operations occur. The journal may* occasionally be compacted by dropping redundant lines. A temporary file named* "journal.tmp" will be used during compaction; that file should be deleted if* it exists when the cache is opened.*/private final File directory;private final File journalFile;private final File journalFileTmp;private final File journalFileBackup;private final int appVersion;private long maxSize;private final int valueCount;private long size = 0;private Writer journalWriter;private final LinkedHashMap<String, Entry> lruEntries =new LinkedHashMap<String, Entry>(0, 0.75f, true);private int redundantOpCount;/*** To differentiate between old and current snapshots, each entry is given* a sequence number each time an edit is committed. A snapshot is stale if* its sequence number is not equal to its entry's sequence number.*/private long nextSequenceNumber = 0;/** This cache uses a single background thread to evict entries. */final ThreadPoolExecutor executorService =new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());private final Callable<Void> cleanupCallable = new Callable<Void>() {public Void call() throws Exception {synchronized (DiskLruCache.this) {if (journalWriter == null) {return null; // Closed.}trimToSize();if (journalRebuildRequired()) {rebuildJournal();redundantOpCount = 0;}}return null;}};private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {this.directory = directory;this.appVersion = appVersion;this.journalFile = new File(directory, JOURNAL_FILE);this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);this.valueCount = valueCount;this.maxSize = maxSize;}/*** Opens the cache in {@code directory}, creating a cache if none exists* there.** @param directory a writable directory* @param valueCount the number of values per cache entry. Must be positive.* @param maxSize the maximum number of bytes this cache should use to store* @throws IOException if reading or writing the cache directory fails*/public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)throws IOException {if (maxSize <= 0) {throw new IllegalArgumentException("maxSize <= 0");}if (valueCount <= 0) {throw new IllegalArgumentException("valueCount <= 0");}// If a bkp file exists, use it instead.File backupFile = new File(directory, JOURNAL_FILE_BACKUP);if (backupFile.exists()) {File journalFile = new File(directory, JOURNAL_FILE);// If journal file also exists just delete backup file.if (journalFile.exists()) {backupFile.delete();} else {renameTo(backupFile, journalFile, false);}}// Prefer to pick up where we left off.DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);if (cache.journalFile.exists()) {try {cache.readJournal();cache.processJournal();return cache;} catch (IOException journalIsCorrupt) {System.out.println("DiskLruCache "+ directory+ " is corrupt: "+ journalIsCorrupt.getMessage()+ ", removing");cache.delete();}}// Create a new empty cache.directory.mkdirs();cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);cache.rebuildJournal();return cache;}private void readJournal() throws IOException {StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);try {String magic = reader.readLine();String version = reader.readLine();String appVersionString = reader.readLine();String valueCountString = reader.readLine();String blank = reader.readLine();if (!MAGIC.equals(magic)|| !VERSION_1.equals(version)|| !Integer.toString(appVersion).equals(appVersionString)|| !Integer.toString(valueCount).equals(valueCountString)|| !"".equals(blank)) {throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "+ valueCountString + ", " + blank + "]");}int lineCount = 0;while (true) {try {readJournalLine(reader.readLine());lineCount++;} catch (EOFException endOfJournal) {break;}}redundantOpCount = lineCount - lruEntries.size();// If we ended on a truncated line, rebuild the journal before appending to it.if (reader.hasUnterminatedLine()) {rebuildJournal();} else {journalWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));}} finally {Util.closeQuietly(reader);}}private void readJournalLine(String line) throws IOException {int firstSpace = line.indexOf(' ');if (firstSpace == -1) {throw new IOException("unexpected journal line: " + line);}int keyBegin = firstSpace + 1;int secondSpace = line.indexOf(' ', keyBegin);final String key;if (secondSpace == -1) {key = line.substring(keyBegin);if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {lruEntries.remove(key);return;}} else {key = line.substring(keyBegin, secondSpace);}Entry entry = lruEntries.get(key);if (entry == null) {entry = new Entry(key);lruEntries.put(key, entry);}if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {String[] parts = line.substring(secondSpace + 1).split(" ");entry.readable = true;entry.currentEditor = null;entry.setLengths(parts);} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {entry.currentEditor = new Editor(entry);} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {// This work was already done by calling lruEntries.get().} else {throw new IOException("unexpected journal line: " + line);}}/*** Computes the initial size and collects garbage as a part of opening the* cache. Dirty entries are assumed to be inconsistent and will be deleted.*/private void processJournal() throws IOException {deleteIfExists(journalFileTmp);for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {Entry entry = i.next();if (entry.currentEditor == null) {for (int t = 0; t < valueCount; t++) {size += entry.lengths[t];}} else {entry.currentEditor = null;for (int t = 0; t < valueCount; t++) {deleteIfExists(entry.getCleanFile(t));deleteIfExists(entry.getDirtyFile(t));}i.remove();}}}/*** Creates a new journal that omits redundant information. This replaces the* current journal if it exists.*/private synchronized void rebuildJournal() throws IOException {if (journalWriter != null) {journalWriter.close();}Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));try {writer.write(MAGIC);writer.write("\n");writer.write(VERSION_1);writer.write("\n");writer.write(Integer.toString(appVersion));writer.write("\n");writer.write(Integer.toString(valueCount));writer.write("\n");writer.write("\n");for (Entry entry : lruEntries.values()) {if (entry.currentEditor != null) {writer.write(DIRTY + ' ' + entry.key + '\n');} else {writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');}}} finally {writer.close();}if (journalFile.exists()) {renameTo(journalFile, journalFileBackup, true);}renameTo(journalFileTmp, journalFile, false);journalFileBackup.delete();journalWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));}private static void deleteIfExists(File file) throws IOException {if (file.exists() && !file.delete()) {throw new IOException();}}private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {if (deleteDestination) {deleteIfExists(to);}if (!from.renameTo(to)) {throw new IOException();}}/*** Returns a snapshot of the entry named {@code key}, or null if it doesn't* exist is not currently readable. If a value is returned, it is moved to* the head of the LRU queue.*/public synchronized Snapshot get(String key) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (entry == null) {return null;}if (!entry.readable) {return null;}// Open all streams eagerly to guarantee that we see a single published// snapshot. If we opened streams lazily then the streams could come// from different edits.InputStream[] ins = new InputStream[valueCount];try {for (int i = 0; i < valueCount; i++) {ins[i] = new FileInputStream(entry.getCleanFile(i));}} catch (FileNotFoundException e) {// A file must have been deleted manually!for (int i = 0; i < valueCount; i++) {if (ins[i] != null) {Util.closeQuietly(ins[i]);} else {break;}}return null;}redundantOpCount++;journalWriter.append(READ + ' ' + key + '\n');if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);}/*** Returns an editor for the entry named {@code key}, or null if another* edit is in progress.*/public Editor edit(String key) throws IOException {return edit(key, ANY_SEQUENCE_NUMBER);}private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null|| entry.sequenceNumber != expectedSequenceNumber)) {return null; // Snapshot is stale.}if (entry == null) {entry = new Entry(key);lruEntries.put(key, entry);} else if (entry.currentEditor != null) {return null; // Another edit is in progress.}Editor editor = new Editor(entry);entry.currentEditor = editor;// Flush the journal before creating files to prevent file leaks.journalWriter.write(DIRTY + ' ' + key + '\n');journalWriter.flush();return editor;}/** Returns the directory where this cache stores its data. */public File getDirectory() {return directory;}/*** Returns the maximum number of bytes that this cache should use to store* its data.*/public synchronized long getMaxSize() {return maxSize;}/*** Changes the maximum number of bytes the cache can store and queues a job* to trim the existing store, if necessary.*/public synchronized void setMaxSize(long maxSize) {this.maxSize = maxSize;executorService.submit(cleanupCallable);}/*** Returns the number of bytes currently being used to store the values in* this cache. This may be greater than the max size if a background* deletion is pending.*/public synchronized long size() {return size;}private synchronized void completeEdit(Editor editor, boolean success) throws IOException {Entry entry = editor.entry;if (entry.currentEditor != editor) {throw new IllegalStateException();}// If this edit is creating the entry for the first time, every index must have a value.if (success && !entry.readable) {for (int i = 0; i < valueCount; i++) {if (!editor.written[i]) {editor.abort();throw new IllegalStateException("Newly created entry didn't create value for index " + i);}if (!entry.getDirtyFile(i).exists()) {editor.abort();return;}}}for (int i = 0; i < valueCount; i++) {File dirty = entry.getDirtyFile(i);if (success) {if (dirty.exists()) {File clean = entry.getCleanFile(i);dirty.renameTo(clean);long oldLength = entry.lengths[i];long newLength = clean.length();entry.lengths[i] = newLength;size = size - oldLength + newLength;}} else {deleteIfExists(dirty);}}redundantOpCount++;entry.currentEditor = null;if (entry.readable | success) {entry.readable = true;journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');if (success) {entry.sequenceNumber = nextSequenceNumber++;}} else {lruEntries.remove(entry.key);journalWriter.write(REMOVE + ' ' + entry.key + '\n');}journalWriter.flush();if (size > maxSize || journalRebuildRequired()) {executorService.submit(cleanupCallable);}}/*** We only rebuild the journal when it will halve the size of the journal* and eliminate at least 2000 ops.*/private boolean journalRebuildRequired() {final int redundantOpCompactThreshold = 2000;return redundantOpCount >= redundantOpCompactThreshold //&& redundantOpCount >= lruEntries.size();}/*** Drops the entry for {@code key} if it exists and can be removed. Entries* actively being edited cannot be removed.** @return true if an entry was removed.*/public synchronized boolean remove(String key) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (entry == null || entry.currentEditor != null) {return false;}for (int i = 0; i < valueCount; i++) {File file = entry.getCleanFile(i);if (file.exists() && !file.delete()) {throw new IOException("failed to delete " + file);}size -= entry.lengths[i];entry.lengths[i] = 0;}redundantOpCount++;journalWriter.append(REMOVE + ' ' + key + '\n');lruEntries.remove(key);if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return true;}/** Returns true if this cache has been closed. */public synchronized boolean isClosed() {return journalWriter == null;}private void checkNotClosed() {if (journalWriter == null) {throw new IllegalStateException("cache is closed");}}/** Force buffered operations to the filesystem. */public synchronized void flush() throws IOException {checkNotClosed();trimToSize();journalWriter.flush();}/** Closes this cache. Stored values will remain on the filesystem. */public synchronized void close() throws IOException {if (journalWriter == null) {return; // Already closed.}for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {if (entry.currentEditor != null) {entry.currentEditor.abort();}}trimToSize();journalWriter.close();journalWriter = null;}private void trimToSize() throws IOException {while (size > maxSize) {Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();remove(toEvict.getKey());}}/*** Closes the cache and deletes all of its stored values. This will delete* all files in the cache directory including files that weren't created by* the cache.*/public void delete() throws IOException {close();Util.deleteContents(directory);}private void validateKey(String key) {Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);if (!matcher.matches()) {throw new IllegalArgumentException("keys must match regex "+ STRING_KEY_PATTERN + ": \"" + key + "\"");}}private static String inputStreamToString(InputStream in) throws IOException {return Util.readFully(new InputStreamReader(in, Util.UTF_8));}/** A snapshot of the values for an entry. */public final class Snapshot implements Closeable {private final String key;private final long sequenceNumber;private final InputStream[] ins;private final long[] lengths;private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {this.key = key;this.sequenceNumber = sequenceNumber;this.ins = ins;this.lengths = lengths;}/*** Returns an editor for this snapshot's entry, or null if either the* entry has changed since this snapshot was created or if another edit* is in progress.*/public Editor edit() throws IOException {return DiskLruCache.this.edit(key, sequenceNumber);}/** Returns the unbuffered stream with the value for {@code index}. */public InputStream getInputStream(int index) {return ins[index];}/** Returns the string value for {@code index}. */public String getString(int index) throws IOException {return inputStreamToString(getInputStream(index));}/** Returns the byte length of the value for {@code index}. */public long getLength(int index) {return lengths[index];}public void close() {for (InputStream in : ins) {Util.closeQuietly(in);}}}private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {@Overridepublic void write(int b) throws IOException {// Eat all writes silently. Nom nom.}};/** Edits the values for an entry. */public final class Editor {private final Entry entry;private final boolean[] written;private boolean hasErrors;private boolean committed;private Editor(Entry entry) {this.entry = entry;this.written = (entry.readable) ? null : new boolean[valueCount];}/*** Returns an unbuffered input stream to read the last committed value,* or null if no value has been committed.*/public InputStream newInputStream(int index) throws IOException {synchronized (DiskLruCache.this) {if (entry.currentEditor != this) {throw new IllegalStateException();}if (!entry.readable) {return null;}try {return new FileInputStream(entry.getCleanFile(index));} catch (FileNotFoundException e) {return null;}}}/*** Returns the last committed value as a string, or null if no value* has been committed.*/public String getString(int index) throws IOException {InputStream in = newInputStream(index);return in != null ? inputStreamToString(in) : null;}/*** Returns a new unbuffered output stream to write the value at* {@code index}. If the underlying output stream encounters errors* when writing to the filesystem, this edit will be aborted when* {@link #commit} is called. The returned output stream does not throw* IOExceptions.*/public OutputStream newOutputStream(int index) throws IOException {if (index < 0 || index >= valueCount) {throw new IllegalArgumentException("Expected index " + index + " to "+ "be greater than 0 and less than the maximum value count "+ "of " + valueCount);}synchronized (DiskLruCache.this) {if (entry.currentEditor != this) {throw new IllegalStateException();}if (!entry.readable) {written[index] = true;}File dirtyFile = entry.getDirtyFile(index);FileOutputStream outputStream;try {outputStream = new FileOutputStream(dirtyFile);} catch (FileNotFoundException e) {// Attempt to recreate the cache directory.directory.mkdirs();try {outputStream = new FileOutputStream(dirtyFile);} catch (FileNotFoundException e2) {// We are unable to recover. Silently eat the writes.return NULL_OUTPUT_STREAM;}}return new FaultHidingOutputStream(outputStream);}}/** Sets the value at {@code index} to {@code value}. */public void set(int index, String value) throws IOException {Writer writer = null;try {writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);writer.write(value);} finally {Util.closeQuietly(writer);}}/*** Commits this edit so it is visible to readers.  This releases the* edit lock so another edit may be started on the same key.*/public void commit() throws IOException {if (hasErrors) {completeEdit(this, false);remove(entry.key); // The previous entry is stale.} else {completeEdit(this, true);}committed = true;}/*** Aborts this edit. This releases the edit lock so another edit may be* started on the same key.*/public void abort() throws IOException {completeEdit(this, false);}public void abortUnlessCommitted() {if (!committed) {try {abort();} catch (IOException ignored) {}}}private class FaultHidingOutputStream extends FilterOutputStream {private FaultHidingOutputStream(OutputStream out) {super(out);}@Override public void write(int oneByte) {try {out.write(oneByte);} catch (IOException e) {hasErrors = true;}}@Override public void write(byte[] buffer, int offset, int length) {try {out.write(buffer, offset, length);} catch (IOException e) {hasErrors = true;}}@Override public void close() {try {out.close();} catch (IOException e) {hasErrors = true;}}@Override public void flush() {try {out.flush();} catch (IOException e) {hasErrors = true;}}}}private final class Entry {private final String key;/** Lengths of this entry's files. */private final long[] lengths;/** True if this entry has ever been published. */private boolean readable;/** The ongoing edit or null if this entry is not being edited. */private Editor currentEditor;/** The sequence number of the most recently committed edit to this entry. */private long sequenceNumber;private Entry(String key) {this.key = key;this.lengths = new long[valueCount];}public String getLengths() throws IOException {StringBuilder result = new StringBuilder();for (long size : lengths) {result.append(' ').append(size);}return result.toString();}/** Set lengths using decimal numbers like "10123". */private void setLengths(String[] strings) throws IOException {if (strings.length != valueCount) {throw invalidLengths(strings);}try {for (int i = 0; i < strings.length; i++) {lengths[i] = Long.parseLong(strings[i]);}} catch (NumberFormatException e) {throw invalidLengths(strings);}}private IOException invalidLengths(String[] strings) throws IOException {throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));}public File getCleanFile(int i) {return new File(directory, key + "." + i);}public File getDirtyFile(int i) {return new File(directory, key + "." + i + ".tmp");}}
}


/** Copyright (C) 2012 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;/*** Buffers input from an {@link InputStream} for reading lines.** <p>This class is used for buffered reading of lines. For purposes of this class, a line ends* with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated* line at end of input is invalid and will be ignored, the caller may use {@code* hasUnterminatedLine()} to detect it after catching the {@code EOFException}.** <p>This class is intended for reading input that strictly consists of lines, such as line-based* cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction* with {@link java.io.InputStreamReader} provides similar functionality, this class uses different* end-of-input reporting and a more restrictive definition of a line.** <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13* and 10, respectively, and the representation of no other character contains these values.* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.* The default charset is US_ASCII.*/
class StrictLineReader implements Closeable {private static final byte CR = (byte) '\r';private static final byte LF = (byte) '\n';private final InputStream in;private final Charset charset;/** Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end* and the data in the range [pos, end) is buffered for reading. At end of input, if there is* an unterminated line, we set end == -1, otherwise end == pos. If the underlying* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.*/private byte[] buf;private int pos;private int end;/*** Constructs a new {@code LineReader} with the specified charset and the default capacity.** @param in the {@code InputStream} to read data from.* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are* supported.* @throws NullPointerException if {@code in} or {@code charset} is null.* @throws IllegalArgumentException if the specified charset is not supported.*/public StrictLineReader(InputStream in, Charset charset) {this(in, 8192, charset);}/*** Constructs a new {@code LineReader} with the specified capacity and charset.** @param in the {@code InputStream} to read data from.* @param capacity the capacity of the buffer.* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are* supported.* @throws NullPointerException if {@code in} or {@code charset} is null.* @throws IllegalArgumentException if {@code capacity} is negative or zero* or the specified charset is not supported.*/public StrictLineReader(InputStream in, int capacity, Charset charset) {if (in == null || charset == null) {throw new NullPointerException();}if (capacity < 0) {throw new IllegalArgumentException("capacity <= 0");}if (!(charset.equals(Util.US_ASCII))) {throw new IllegalArgumentException("Unsupported encoding");}this.in = in;this.charset = charset;buf = new byte[capacity];}/*** Closes the reader by closing the underlying {@code InputStream} and* marking this reader as closed.** @throws IOException for errors when closing the underlying {@code InputStream}.*/public void close() throws IOException {synchronized (in) {if (buf != null) {buf = null;in.close();}}}/*** Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},* this end of line marker is not included in the result.** @return the next line from the input.* @throws IOException for underlying {@code InputStream} errors.* @throws EOFException for the end of source stream.*/public String readLine() throws IOException {synchronized (in) {if (buf == null) {throw new IOException("LineReader is closed");}// Read more data if we are at the end of the buffered data.// Though it's an error to read after an exception, we will let {@code fillBuf()}// throw again if that happens; thus we need to handle end == -1 as well as end == pos.if (pos >= end) {fillBuf();}// Try to find LF in the buffered data and return the line if successful.for (int i = pos; i != end; ++i) {if (buf[i] == LF) {int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;String res = new String(buf, pos, lineEnd - pos, charset.name());pos = i + 1;return res;}}// Let's anticipate up to 80 characters on top of those already read.ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {@Overridepublic String toString() {int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;try {return new String(buf, 0, length, charset.name());} catch (UnsupportedEncodingException e) {throw new AssertionError(e); // Since we control the charset this will never happen.}}};while (true) {out.write(buf, pos, end - pos);// Mark unterminated line in case fillBuf throws EOFException or IOException.end = -1;fillBuf();// Try to find LF in the buffered data and return the line if successful.for (int i = pos; i != end; ++i) {if (buf[i] == LF) {if (i != pos) {out.write(buf, pos, i - pos);}pos = i + 1;return out.toString();}}}}}public boolean hasUnterminatedLine() {return end == -1;}/*** Reads new input data into the buffer. Call only with pos == end or end == -1,* depending on the desired outcome if the function throws.*/private void fillBuf() throws IOException {int result = in.read(buf, 0, buf.length);if (result == -1) {throw new EOFException();}pos = 0;end = result;}
}


/** Copyright (C) 2010 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.Charset;/** Junk drawer of utility methods. */
final class Util {static final Charset US_ASCII = Charset.forName("US-ASCII");static final Charset UTF_8 = Charset.forName("UTF-8");private Util() {}static String readFully(Reader reader) throws IOException {try {StringWriter writer = new StringWriter();char[] buffer = new char[1024];int count;while ((count = reader.read(buffer)) != -1) {writer.write(buffer, 0, count);}return writer.toString();} finally {reader.close();}}/*** Deletes the contents of {@code dir}. Throws an IOException if any file* could not be deleted, or if {@code dir} is not a readable directory.*/static void deleteContents(File dir) throws IOException {File[] files = dir.listFiles();if (files == null) {throw new IOException("not a readable directory: " + dir);}for (File file : files) {if (file.isDirectory()) {deleteContents(file);}if (!file.delete()) {throw new IOException("failed to delete file: " + file);}}}static void closeQuietly(/*Auto*/Closeable closeable) {if (closeable != null) {try {closeable.close();} catch (RuntimeException rethrown) {throw rethrown;} catch (Exception ignored) {}}}
}


import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.LruCache
import androidx.lifecycle.lifecycleScope
import com.mediaapp.disk.DiskLruCache
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.security.MessageDigest
import java.security.NoSuchAlgorithmExceptionclass BitmapCache {companion object {const val TAG = "fly/BitmapCache"const val MAX_DISK_SIZE: Long = 1024 * 1024 * 1024const val CACHE_FILE_PATH = "fly_disk_cache"}private var mCtx: Context? = nullprivate var mDiskCache: DiskLruCache? = nullprivate var mMemoryCache = LruCache<String, Bitmap>(1000)constructor(ctx: Context) {mCtx = ctxval file = File(ctx.cacheDir.path, CACHE_FILE_PATH)if (!file.exists()) {file.mkdirs()}mDiskCache = DiskLruCache.open(file, 1, 1, MAX_DISK_SIZE)}fun getMemoryCache(key: String): Bitmap? {return mMemoryCache.get(key)}@WorkerThreadfun putCache(key: String, bmp: Bitmap) {putMemoryCache(key, bmp)putDiskCache(key, bmp)}@WorkerThreadfun getCache(key: String): Bitmap? {var bmp = getMemoryCache(key)if (bmp != null) {Log.d(TAG, "內存緩存存在 $key")return bmp} else {bmp = getDiskCache(key)if (bmp != null) {Log.d(TAG, "Disk緩存存在 $key")putMemoryCache(key, bmp!!) //刷新內存的緩存return bmp}}return null}fun putMemoryCache(key: String, bmp: Bitmap) {mMemoryCache.put(key, bmp)}@WorkerThreadprivate fun getDiskCache(key: String): Bitmap? {val snapshot = mDiskCache?.get(getMD5(key))val bmp = BitmapFactory.decodeStream(snapshot?.getInputStream(0))//mDiskCache?.close()return bmp}@WorkerThreadprivate fun putDiskCache(key: String, bmp: Bitmap) {(mCtx as AppCompatActivity).lifecycleScope.launch(Dispatchers.IO) {val editor = mDiskCache?.edit(getMD5(key))try {bmp.compress(Bitmap.CompressFormat.PNG, 100, editor?.newOutputStream(0)!!)editor.commit()//mDiskCache?.flush()//mDiskCache?.close()Log.d(TAG, "寫入成功 $key")} catch (e: Exception) {Log.e(TAG, "editor $e $key")}}}private fun getMD5(msg: String): String {var md: MessageDigest? = nulltry {md = MessageDigest.getInstance("MD5")} catch (e: NoSuchAlgorithmException) {e.printStackTrace()}md?.reset()md?.update(msg.toByteArray())val bytes = md?.digest()var result = ""if (bytes != null) {for (b in bytes) {// byte轉換成16進制result += String.format("%02x", b)}}return result}
}


import android.graphics.Bitmap
import android.util.Log
import wseemann.media.FFmpegMediaMetadataRetrieverclass BitmapDecoder : Loader.Decoder {companion object {const val TAG = "fly/BitmapDecoder"}override fun decode(data: Any?): Bitmap? {val mData = data as MyDataLog.d(TAG, "開始解碼 $mData")val ffMMR = FFmpegMediaMetadataRetriever()var bmp: Bitmap? = nulltry {ffMMR.setDataSource(mData.path)bmp = ffMMR.frameAtTime} catch (e: Exception) {Log.e(TAG, "FFmpeg MMR: ${e.message}:$mData")} finally {try {ffMMR.release()} catch (exc: Exception) {Log.e(TAG, "$exc")}}return bmp}
}


import android.content.Context
import android.graphics.Bitmapclass Loader {companion object {const val TAG = "fly/Loader"}private var mCtx: Context? = nullprivate var mListener: Listener? = nullprivate var mBitmapCache: BitmapCache? = nullprivate var mDecoder: Decoder? = nullprivate var mData: MyData? = nullconstructor(ctx: Context) {mCtx = ctx}fun cache(cache: BitmapCache): Loader {mBitmapCache = cachereturn this}fun load(data: MyData): Loader {mData = datareturn this}fun start(): Loader {var bmp: Bitmap?bmp = mBitmapCache?.getCache(mData.toString())if (bmp != null) {mListener?.onSuccess(bmp)} else {bmp = mDecoder?.decode(mData)if (bmp != null) {mListener?.onSuccess(bmp)mBitmapCache?.putCache(mData.toString(), bmp)}}mListener?.onError()return this}fun decoder(decoder: Decoder): Loader {mDecoder = decoderreturn this}fun setListener(l: Listener): Loader {mListener = lreturn this}interface Listener {fun onSuccess(bitmap: Bitmap) {}fun onError() {}}interface Decoder {fun decode(data: Any?): Bitmap? {return null}}
}


import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launchclass MainActivity : AppCompatActivity() {companion object {const val TAG = "fly/MainActivity"const val SPAN_COUNT = 6const val VIDEO = 1}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val rv = findViewById<RecyclerView>(R.id.rv)val layoutManager = GridLayoutManager(this, SPAN_COUNT)layoutManager.orientation = GridLayoutManager.VERTICALrv.layoutManager = layoutManagerval adapter = MyAdapter(this)rv.adapter = adapterrv.layoutManager = layoutManagerval ctx = thislifecycleScope.launch(Dispatchers.IO) {val videoList = readAllVideo(ctx)Log.d(TAG, "readAllVideo size=${videoList.size}")val lists = arrayListOf<MyData>()lists.addAll(videoList)lifecycleScope.launch(Dispatchers.Main) {adapter.dataChanged(lists)}}}private fun readAllVideo(ctx: Context): ArrayList<MyData> {val videos = ArrayList<MyData>()//讀取視頻Videoval cursor = ctx.contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,null,null,null,null)while (cursor!!.moveToNext()) {//路徑val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA))val id = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)val videoUri: Uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(id))//名稱//val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME))//大小//val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE))videos.add(MyData(videoUri, path, VIDEO))}cursor.close()return videos}
}


import android.content.Context
import android.graphics.Bitmap
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launchclass MyAdapter : RecyclerView.Adapter<MyAdapter.VideoHolder> {companion object {const val TAG = "fly/MyAdapter"}private var mCtx: Context? = nullprivate var mItems = ArrayList<MyData>()private var mBitmapCache: BitmapCache? = nullconstructor(ctx: Context) : super() {mCtx = ctxmBitmapCache = BitmapCache(mCtx!!)}fun dataChanged(items: ArrayList<MyData>) {this.mItems = itemsnotifyDataSetChanged()}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoHolder {val v = LayoutInflater.from(mCtx).inflate(R.layout.image_layout, null)return VideoHolder(v)}override fun onBindViewHolder(holder: VideoHolder, position: Int) {loadVideoCover(mItems[position], holder.image)}override fun getItemCount(): Int {return mItems.size}class VideoHolder : RecyclerView.ViewHolder {var image: ImageView? = nullconstructor(itemView: View) : super(itemView) {image = itemView.findViewById<ImageView>(R.id.image)image?.setImageResource(android.R.drawable.ic_menu_gallery)}}private fun loadVideoCover(data: MyData, image: ImageView?) {val cacheBmp = mBitmapCache?.getMemoryCache(data.toString())if (cacheBmp != null) {image?.setImageBitmap(cacheBmp)} else {(mCtx as AppCompatActivity).lifecycleScope.launch(Dispatchers.IO) {Loader(mCtx!!).cache(mBitmapCache!!).load(data).decoder(BitmapDecoder()).setListener(object : Loader.Listener {override fun onSuccess(bitmap: Bitmap) {(mCtx as AppCompatActivity).lifecycleScope.launch(Dispatchers.Main) {image?.setImageBitmap(bitmap)}}}).start()}}}
}


import android.net.Uri
import android.text.TextUtilsopen class MyData {var uri: Uri? = nullvar path: String? = nullvar lastModified = 0Lvar width = 0var height = 0var position = -1var type = -1  //-1未知。1,普通圖。2,視頻。constructor(uri: Uri?, path: String?, type: Int = -1) {this.uri = urithis.path = paththis.type = type}override fun equals(other: Any?): Boolean {return TextUtils.equals(this.toString(), other.toString())}override fun toString(): String {return "MyData(uri=$uri, path=$path, lastModified=$lastModified, width=$width, height=$height, position=$position, type=$type)"}
}

Android Coil3視頻封面抽取封面幀存Disk緩存,Kotlin-CSDN博客文章瀏覽閱讀610次,點贊21次,收藏6次。本文介紹了一個基于Coil3的Android視頻封面抽取實現方案。項目通過MediaStore獲取設備視頻列表,使用MediaMetadataRetriever提取視頻首幀作為縮略圖,并采用二級緩存(內存+磁盤)優化性能。核心功能包括:1)聲明讀寫存儲權限;2)RecyclerView網格布局展示;3)協程處理耗時操作;4)自定義ImageLoader配置緩存策略(最大2GB)。相比原生方案,該實現通過Coil的緩存機制提升了縮略圖加載效率,同時處理了視頻損壞等異常情況。相關技術細節可參考作者CSDN博客中 https://blog.csdn.net/zhangphil/article/details/150224812

Android快速視頻解碼抽幀FFmpegMediaMetadataRetriever,Kotlin(2)-CSDN博客文章瀏覽閱讀216次。本文介紹了兩種Android視頻封面提取方案對比:1)原生MediaMetadataRetriever速度較慢;2)第三方FFmpegMediaMetadataRetriever(FFMMR)實現快速抽幀。詳細說明了FFMMR的集成方法(添加依賴和權限),并提供了完整的Kotlin實現代碼,包括視頻列表讀取、緩存管理、協程異步處理等核心功能。通過LruCache緩存縮略圖提升性能,記錄處理耗時和失敗情況。相比前文介紹的原生方案,本文重點突出了FFMMR在解碼效率和性能上的優勢,為需要快速獲取視頻幀的場景提供 https://blog.csdn.net/zhangphil/article/details/150061648

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/93187.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/93187.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/93187.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【k8s】pvc 配置的兩種方式volumeClaimTemplates 和 PersistentVolumeClaim

pvc配置實例 實例1在Deployment中配置 template:xxxxxxvolumeClaimTemplates:- metadata:name: dataspec:accessModes:- ReadWriteOnceresources:requests:storage: 1GistorageClassName: nfsdev-storageclass (創建好的storageClassName)實例2#先創建一個pvc 然后在 Deploym…

Logistic Loss Function|邏輯回歸代價函數

----------------------------------------------------------------------------------------------- 這是我在我的網站中截取的文章&#xff0c;有更多的文章歡迎來訪問我自己的博客網站rn.berlinlian.cn&#xff0c;這里還有很多有關計算機的知識&#xff0c;歡迎進行留言或…

計算機網絡技術-知識篇(Day.1)

一、網絡概述 1、網絡的概念 兩個不在同一地理位置的主機&#xff0c;通過傳輸介質和通信協議&#xff0c;實現通信和資源共享。 2、網絡發展史 第一階段&#xff08;20世紀60年代&#xff09; 標志性事件&#xff1a;ARPANET的誕生關鍵技術&#xff1a;分組交換技術 第二…

工業元宇宙:邁向星辰大海的“玄奘之路”

一、從認知革命到工業革命&#xff1a;文明躍遷的底層邏輯1.1 認知革命&#xff1a;人類協作的基石時間線&#xff1a;約7萬年前&#xff0c;智人通過語言和想象力構建共同虛擬現實&#xff0c;形成部落協作模式。核心突破&#xff1a;虛構能力&#xff1a;創造神、國家、法律等…

9. React組件生命周期

2. React組件生命周期 2.1. 認識生命周期 2.1.1. 很多事物都有從創建到銷毀的整個過程&#xff0c;這個過程稱之為生命周期&#xff1b;2.1.2. React組件也有自己的生命周期&#xff0c;了解生命周期可以讓我們在最合適的地方完成想要的功能2.1.3. 生命周期和生命周期函數的關系…

【單板硬件開發】關于復位電路的理解

閱讀紫光同創供應商提供的FPGA單板硬件開發手冊&#xff0c;發現復位電路他們家解釋的很通俗易懂&#xff0c;所以分享一下。如下圖&#xff0c;RST_N 是低有效的異步全芯片復位信號&#xff0c;一般外部連接電路有 3 種形式如圖 3–2&#xff0c;可根據實際需要選擇合適的電路…

《Unity Shader入門精要》學習筆記一

1、本書的源代碼 https://github.com/candycat1992/Unity_Shaders_Book 2、第1章 Shader是面向GPU的工作方式 3、第2章 渲染流水線 Shader&#xff1a;著色器 渲染流水線&#xff1a;目標是渲染一張二維紋理&#xff0c;輸入是一個虛擬攝像機、一些光源、一些Shader以及紋…

從零到一:TCP 回聲服務器與客戶端的完整實現與原理詳解

目錄 一、TCP 通信的核心邏輯 二、TCP 服務器編程步驟 步驟 1&#xff1a;創建監聽 Socket 步驟 2&#xff1a;綁定地址與端口&#xff08;bind&#xff09; 步驟 3&#xff1a;設置監聽狀態&#xff08;listen&#xff09; 步驟 4&#xff1a;接收客戶端連接&#xff08…

MyBatis-Plus核心內容

MyBatis-Plus MyBatis-Plus 是一個基于 MyBatis的增強工具&#xff0c;旨在簡化開發過程&#xff0c;減少重復代碼。它在MyBatis的基礎上增加了CRUD操作封裝&#xff0c;條件構造器、代碼生成器等功能。 一、核心特性與優勢 1. 核心特性 無侵入&#xff1a;只做增強不做改變&am…

計算機網絡摘星題庫800題筆記 第4章 網絡層

第4章 網絡層4.1 網絡層概述題組闖關1.在 Windows 的網絡配置中&#xff0c;“默認網關” 一般被設置為 ( ) 的地址。 A. DNS 服務器 B. Web 服務器 C. 路由器 D. 交換機1.【參考答案】C 【解析】只有在計算機上正確安裝網卡驅動程序和網絡協議&#xff0c;并正確設置 IP 地址信…

非root用戶在linux中配置zsh(已解決ncurses-devel報錯)

Zsh&#xff08;Z Shell&#xff09;是一款功能強大的交互式 Unix shell&#xff0c;以其高度可定制性和豐富的功能著稱&#xff0c;被視為 Bash 的增強替代品。它支持智能補全、主題美化、插件擴展&#xff08;如 Oh My Zsh 框架&#xff09;、自動糾錯、全局別名等特性&#…

《Foundations and Recent Trends in Multimodal Mobile Agents: A Survey》論文精讀筆記

論文鏈接&#xff1a;https://arxiv.org/pdf/2411.02006 摘要 文章首先介紹了核心組件&#xff0c;并探討了移動基準和交互環境中的關鍵代表性作品&#xff0c;旨在全面理解研究重點及其局限性。 接著&#xff0c;將這些進展分為兩種主要方法&#xff1a; 基于提示的方法&a…

npm安裝時一直卡住的解決方法

npm install 卡住通常是由于網絡問題或緩存問題導致的。以下是幾種解決方法&#xff1a; 方法1&#xff1a;清理npm緩存 npm cache clean --force npm install方法2&#xff1a;刪除node_modules和package-lock.json重新安裝 rm -rf node_modules package-lock.json npm instal…

[密碼學實戰]使用Java生成國密SM2加密證書等(四十三)

[密碼學實戰]使用Java生成國密SM2加密證書等(四十三) 本文將詳細介紹如何通過Java代碼生成符合國密標準的SM2加密證書,包括密鑰對生成、證書擴展屬性配置、PEM格式保存等關鍵步驟。 一. 運行結果示例 二. 國密算法與加密證書 國密算法(SM系列)是中國自主研發的密碼算法體…

從零開始之stm32之CAN通信

從小白的視角了解并實現簡單的STM32F103的CAN通信&#xff0c;直接上手。一、CAN協議簡介CAN總線上傳輸的信息稱為報文&#xff0c;當總線空閑時任何連接的單元都可以開始發送新的報文&#xff0c;有5種類型的幀&#xff1a;數據幀、遙控幀、錯誤幀、過載幀、幀間隔。數據幀有兩…

Java 課程,每天解讀一個簡單Java之利用條件運算符的嵌套來完成此題:學習成績>=90分的同學用A表示,60-89分之間的用B表示, * 60分以下

package ytr250812;/*題目&#xff1a;利用條件運算符的嵌套來完成此題&#xff1a;學習成績>90分的同學用A表示&#xff0c;60-89分之間的用B表示&#xff0c;* 60分以下*/import java.util.Scanner;public class GradeEvaluator {public static void main(String[] args) …

Word XML 批注范圍克隆處理器

該類用于處理 Word 文檔&#xff08;XML 結構&#xff09;中被批注標記的文本范圍&#xff0c; 實現指定內容的深度克隆&#xff0c;并將其插入到目標節點之后。 適用于在生成或修改 .docx 文件時復制批注內容塊。/*** Word XML 批注范圍克隆處理器* * 該類用于處理 Word 文檔&…

MQTT:Java集成MQTT

目錄Git項目路徑一、原生java架構1.1 導入POM文件1.2 編寫測試用例二、SpringBoot集成MQTT2.1 導入POM文件2.2 在YML文件中增加配置2.3 新建Properties配置文件映射配置2.4 創建連接工廠2.5 增加入站規則配置2.6 增加出站規則配置2.7 創建消息發送網關2.8 測試消息發送2.9 項目…

day 16 stm32 IIC

1.IIC概述1基于對話的形式完成&#xff0c;不需要同時進行發送和接收所以刪掉了一根數據線&#xff0c;變成半雙工2為了安全起見添加了應答機制3可以接多個模塊&#xff0c;且互不干擾4異步時序&#xff0c;要求嚴格&#xff0c;發送過程中不能暫停&#xff0c;所以需要同步時序…

AMD KFD的BO設計分析系列 0:開篇

開啟我始終不敢碰的GPU存儲系列&#xff0c;先上個圖把核心關系表達下&#xff0c;以此紀念。注&#xff1a;圖中kfdm_mm誤寫&#xff0c;應該為kfd_mm&#xff0c;不修改了&#xff0c;請大家不要介意。