了解为什么不可以直接预置
因为sdcard是挂载之后出现的,没法在编译的时候直接添加内容
无法直接预置,那么用什么替代
找一个已知会在编译的时候,可以预置文件的位置,这里用的是system文件夹
路径:
\device\mediatek\common\mid\common\system
原理是什么?
开启一个复制的服务,让sdcard加载的时候,复制system目录下的文件到需要的位置
方法
1.将需要的文件放到system目录下,可以有多级目录,这里在定义位置的时候做出选择
我这里放置来3张图片到extra目录下
2.这里建立一个package,做类似预置应用的操作
这里取个名叫CopyMedia,可以随便取名,注意在加包的时候做对应的修改
看目录可以知道有3个大文件,最上面那个就是我们的项目的代码。类似写安卓项目,目录层级是一样的,jni就是java和c#的互相调用,mk文件是编译的关键,据说12变成了bp文件,这里是11.
mk文件可以理解为说明书,具体可以看
https://blog.csdn.net/hejnhong/article/details/120585740
直接上CopyMedia的代码
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mid.copymedia"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="android.uid.system" >
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<service android:name=".CopyService"
android:directBootAware="true">
<intent-filter >
<action android:name="mid.intent.action.COPYMEDIA"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
<receiver android:name=".CopyReceiver"
android:directBootAware="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>
可以看到这里面没有Activity,只有一个服务和一个广播接收者,说明什么,没有界面。
CopyReceiver.java
package com.mid.copymedia;
import java.io.File;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
import android.os.SystemProperties;
public class CopyReceiver extends BroadcastReceiver {
private String BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
private static final String SRC_S = "/system/extra";
private static final String SRC_D = "/data/extra";
private static final String DST_STR = "persist.sys.sd.defaultpath";
private static final String DST = "sdcard";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
boolean copyOk = SystemProperties.get("persist.sys.mid.cpok","fail").equals("ok");//CopyJni.isCopyDown();
CopyUtil.debug("onReceive action " + action);
if(action.equals(BOOT_COMPLETED)){
if(!copyOk){
Intent copyIntent = new Intent(context, CopyService.class);
context.startService(copyIntent);
}
}
}
public void doCopyStuff(final Context mContext){
CopyJni.startProcess(mContext, SystemProperties.get("ro.preinstall.canreset", "yes").equals("yes") ?SRC_S:SRC_D, DST);
}
}
这个类,做了啥,定义了一堆的常量,还拿到了一个系统值,先通过action来筛选服务,这里没有直接调用doCopyStuff方法,而是通过开启一个服务,让服务去调用这个方法,为什么?很显然,这种操作只有可能是耗时,不方便在这里调用,为什么服务可以?这个需要去看对服务的定义了,不展开描述
CopyService.java
package com.mid.copymedia;
import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
import android.os.IBinder;
public class CopyService extends Service {
private CopyReceiver mCopyReceiver = new CopyReceiver();
public static void debug(String msg){
android.util.Log.d("xxczy","mid=>"+msg);
}
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
debug("CopyService onCreate");
mCopyReceiver.doCopyStuff(this);
}
@Override
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
debug("CopyService onDestroy");
super.onDestroy();
}
}
回过头去看CopyJni.startProcess(),这里好像啥都没做,但不可能,不在src文件中具体实现,那么就是在jni了,直接打开,可以看到
JNIEXPORT void JNICALL Java_com_mid_copymedia_CopyJni_startProcess
(JNIEnv *env, jclass cls, jobject ctx, jstring src, jstring dst)
{
/**
jclass FindClass(JNIEnv *env, const char *name);
*/
jclass CopyUtilClz = env->FindClass("com/mid/copymedia/CopyUtil");
if(CopyUtilClz == NULL){
LOGD("CopyJni_startProcess can't find CopyUtilClz \n");
return;
}
/*
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
*/
jmethodID doCopyMthdId = env->GetStaticMethodID(CopyUtilClz, "doCopy","(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V");
/**
jstring NewStringUTF(JNIEnv *env, const char *bytes);
*/
// jstring src = env->NewStringUTF("/system/vendor_prebuilt");
// jstring dst = env->NewStringUTF("/storage/sdcard0/vendor_media");
/*
NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz,
jmethodID methodID, ...);
*/
env->CallStaticVoidMethod(cls,doCopyMthdId,ctx,src,dst);
}
这一看,就有点懵,反手查资料,可以知道
FindClass 该函数用于加载本地定义的类。它将搜索由CLASSPATH 环境变量为具有指定名称的类所指定的目录和 zip文件
CallStaticVoidMethod 调用静态方法
资料链接:
https://blog.csdn.net/prike/article/details/72790351?utm_source=blogkpcl7
嗯,可以看到又回去了,直接的意思就是把CopyUtil.java的静态方法全部调用了
CopyUtil.java
package com.mid.copymedia;
import java.io.File;
import java.io.IOException;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
import android.os.SystemProperties;
public class CopyUtil {
public static void debug(String msg){
android.util.Log.d("xxczy","mid=>"+msg);
}
public static String getRealPath(File file){
try {
debug("getRealPath getCanonicalPath="+file.getCanonicalPath()+" absPath="+file.getAbsolutePath());
if(!file.getCanonicalPath().equals(file.getAbsolutePath())){
return getRealPath(new File(file.getCanonicalPath()));
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return file.getAbsolutePath();
}
public static void doCopy(final Context ctx , final String SRC, final String DST){
boolean copyOk = SystemProperties.get("persist.sys.mid.cpok","fail").equals("ok");//CopyJni.isCopyDown();
debug("doCopyStuff copy "+ copyOk);
if(!copyOk){
new Thread(){
public void run() {
File src = new File(SRC);
File dst = new File(DST);
String dstPath = dst.getAbsolutePath();
String dstCanoinacal = dstPath;
debug("dstPath="+dstPath);
dstCanoinacal = getRealPath(dst);
debug("dstCanoinacal="+dstCanoinacal);
dst = new File(dstCanoinacal);
boolean ret = true;
if(!dst.exists()){
ret = dst.mkdirs();
}
if(!ret){
debug("mkdir error return now "+dstCanoinacal);
return;
}
CopyJni.doSomething(src, dst);
CopyJni.copyDown(ctx, dstPath);
};
}.start();
}
}
public static void updateMedia(Context context, String filename){
MediaScannerConnection.scanFile(context,
new String[] { filename }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
debug("Scanned " + path + ":");
debug("-> uri=" + uri);
}
});
}
}
很显然这里做了3件事:
1.拿具体路径
2.复制文件
3.跟新数据
直接看复制文件
嗯,前面一看就知道,就是对路径的是否存在的判断,没有就创建对应的文件夹,后面又调用了**CopyJni.doSomething(src, dst);和CopyJni.copyDown(ctx, dstPath);**方法
/*
* Class: com_mid_copymedia_CopyJni
* Method: doSomething
* Signature: (Ljava/io/File;Ljava/io/File;)V
*/
JNIEXPORT void JNICALL Java_com_mid_copymedia_CopyJni_doSomething
(JNIEnv *env, jclass cls, jobject srcFile, jobject dstFile)
{
jclass FileUtilClz = env->FindClass("com/mid/copymedia/FileUtil");
if(FileUtilClz == NULL){
LOGD("CopyJni_doSomethind can't find FileUtilClz \n");
return;
}
jmethodID copyFolderId = env->GetStaticMethodID(FileUtilClz, "copyFolder","(Ljava/io/File;Ljava/io/File;)V");
env->CallStaticVoidMethod(cls,copyFolderId,srcFile,dstFile);
}
/*
* Class: com_mid_copymedia_CopyJni
* Method: copyDown
* Signature: (Landroid/content/Context;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_mid_copymedia_CopyJni_copyDown
(JNIEnv *, jclass, jobject, jstring);
嗯,感觉是吃太饱了,直接看FileUtil.java的代码
FileUtil.java
package com.mid.copymedia;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import android.util.Log;
public class FileUtil {
private static void debug(String msg) {
android.util.Log.d("xxczy", "mid=>" + msg);
}
public static void copyFolder(File src, File dst) {
debug("copyFolder " + src.getAbsolutePath() + " to "
+ dst.getAbsolutePath());
long startTime = System.currentTimeMillis();
debug("src.isDir " + src.isDirectory() + " dst.isDir "
+ dst.isDirectory());
if (src.isDirectory() && dst.isDirectory()) {
File[] srcFiles = src.listFiles();
debug("srcFiles.len " + srcFiles.length);
if (srcFiles != null) {
File toFile = null;
for (int i = 0; i < srcFiles.length; i++) {
toFile = new File(dst, new String(srcFiles[i].getName()
.getBytes()));
if (srcFiles[i].isDirectory()) {
if (!toFile.exists()) {
if (!toFile.mkdirs()) {
debug("mkdirs failed "
+ toFile.getAbsolutePath());
continue;
}
}
copyFolder(srcFiles[i], toFile);
} else {
copyfile(srcFiles[i], toFile, true);
}
}
}
}
long endTime = System.currentTimeMillis();
long totalMs = (endTime - startTime) / 1000;
debug("totalTime = " + totalMs / 1000 + "s " + totalMs % 1000 + " ms");
}
public static void copyfile(File fromFile, File toFile, Boolean rewrite) {
debug("start copy " + new String(fromFile.getAbsolutePath().getBytes())
+ " to " + toFile.getAbsolutePath());
long startTime = System.currentTimeMillis();
if (!fromFile.exists()) {
return;
}
if (!fromFile.isFile()) {
return;
}
if (!fromFile.canRead()) {
return;
}
if (!toFile.getParentFile().exists()) {
toFile.getParentFile().mkdirs();
}
if (toFile.exists() && rewrite) {
toFile.delete();
}
debug("check permission ok !");
try {
FileInputStream fosfrom = new FileInputStream(fromFile);
FileOutputStream fosto = new FileOutputStream(toFile);
byte[] bt = new byte[1024 * 128];
int c;
while ((c = fosfrom.read(bt)) > 0) {
fosto.write(bt, 0, c);
}
fosfrom.close();
fosto.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
long totalMs = (endTime - startTime) / 1000;
debug("end copy file time = " + totalMs / 1000 + "s " + totalMs % 1000
+ " ms");
}
public static void delete(File file) {
if (file.isFile()) {
file.delete();
return;
}
if (file.isDirectory()) {
File[] childFiles = file.listFiles();
if (childFiles == null || childFiles.length == 0) {
file.delete();
return;
}
for (int i = 0; i < childFiles.length; i++) {
delete(childFiles[i]);
}
file.delete();
}
}
}
这里才是关键,用流去读取和写入文件
看过全部代码就知道了,有很多是多余的,就是个人对安卓系统开发的了解还较为浅,没有做实际修改
具体文件:
https://www.aliyundrive.com/s/aX6QJNadRbf
最后别忘了在\device\mediatek\system\common\device.mk中添加这个包名
PRODUCT_PACKAGES += CopyMedia