JNI实例3---扫描SD卡中mp3文件,native层调用Java自定义的类

现将native函数贴出来。

#include <jni.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <com_coder80_scaner_MainActivity.h>
//#include <fcntl.h>


#define  LOG_TAG    "SCANER"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define ARRAY_LENGTH 1024
static int i = 0;

static JavaVM *gvm;
static jobject giface;
static const char *classPathName = "com/coder80/scaner/MainActivity";

static JNINativeMethod methods[] = {
 { "scanDir", "(Ljava/lang/String;)V", (void *)Java_com_coder80_scaner_MainActivity_scanDir},
 { "getPathArray", "(Ljava/lang/String;)[Ljava/lang/String;", (void *)Java_com_coder80_scaner_MainActivity_getPathArray},
 { "getTracksArray", "(Ljava/lang/String;)[Lcom/coder80/scaner/Track_Info;", (void *)Java_com_coder80_scaner_MainActivity_getTracksArray}
};

jint JNI_OnLoad(JavaVM* vm, void* reserved) {

	jclass clazz = NULL;
	JNIEnv* env = NULL;
	jmethodID constr = NULL;
	jobject obj = NULL;

	LOGE("JNI_OnLoad");
	gvm = vm;
	if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
		LOGE("GetEnv FAILED");
		return -1;
	}

	clazz = (*env)->FindClass(env,classPathName);
	if (!clazz) {
		LOGE("Registration unable to find class '%s'", classPathName);
		return -1;
	}
	constr = (*env)->GetMethodID(env, clazz, "<init>", "()V");
	if (!constr) {
		LOGE("Failed to get constructor");
		return -1;
	}
	obj = (*env)->NewObject(env, clazz, constr);
	if (!obj) {
		LOGE("Failed to create an interface object");
		return -1;
	}
	giface = (*env)->NewGlobalRef(env, obj);

	if ((*env)->RegisterNatives(env, clazz, methods,
			sizeof(methods) / sizeof(methods[0])) < 0) {
		LOGE("Registration failed for '%s'", classPathName);
		return -1;
	}
	return JNI_VERSION_1_4;
}


jobjectArray Java_com_coder80_scaner_MainActivity_getPathArray(JNIEnv *env, jobject obj, jstring jdirPath)
 {
	const char *dirPath = (*env)->GetStringUTFChars(env, jdirPath, NULL);
    jclass objClass = (*env)->FindClass(env, "java/lang/String");
    jobjectArray textsArray = (*env)->NewObjectArray(env,(jsize)ARRAY_LENGTH,objClass,0);
    i = 0;
    scan_dir2(env,dirPath,textsArray);
	return textsArray;
}


void scan_dir2(JNIEnv *env,const char *directory,jobjectArray array)
{
    DIR *dp;
    struct dirent *entry;
    struct stat statbuf;
    if((dp = opendir(directory)) == NULL)
    {
        perror("opendir");
        return;
    }
    chdir(directory);
    jstring jstr;

    while ((entry = readdir(dp)) != NULL)
    {
        stat(entry->d_name, &statbuf);
        if (S_ISDIR(statbuf.st_mode))
        {
            if ((strcmp(entry->d_name, ".") != 0) &&
                    (strcmp(entry->d_name, "..") != 0) &&
                    (entry->d_name[0] != '.'))
            {
            	scan_dir2(env,entry->d_name,array);
            }
        }
        else
        {
		    int size = strlen(entry->d_name);
            if (entry->d_name[0] != '.'  //隐藏文件
            		&& (statbuf.st_size/1024) > 300  //大于300k,表示肯能有mp3文件(忽略 <300k的mp3)
            		&& strcmp( ( entry->d_name + (size - 4) ) , ".mp3") == 0)
            {
			    char* parentPath = (char*)malloc(1024);
				char* absolutePath = (char*)malloc(1024);
				//首先获取工作路径
                getcwd(parentPath,1024);
                //LOGE("parentPath = %s \n", parentPath);
				strcpy(absolutePath,parentPath);
				char *p = "/";
				absolutePath = strcat(absolutePath,p);
				absolutePath = strcat(absolutePath,entry->d_name);
				//statbuf.st_size,
//				LOGE("scan_dir(),file absolutePath = %s \n", absolutePath);
			    jstr = (*env)->NewStringUTF(env,absolutePath);
			    (*env)->SetObjectArrayElement(env,array,i,jstr);//必须放入jstring
			    i++;
//			    (*env)->ReleaseStringUTFChars(env,js, s);
//	            LOGE("scan_dir2(),i = %d,file absolutePath = %s \n\n",i, absolutePath);
				free(parentPath);
				parentPath = NULL;
				free(absolutePath);
				absolutePath = NULL;
            }
        }
    }
    chdir("..");
    closedir(dp);
}


void Java_com_coder80_scaner_MainActivity_scanDir(JNIEnv *env, jobject obj, jstring jdirPath)
{
	const char *path = (*env)->GetStringUTFChars(env,jdirPath,NULL);
	LOGE("begin to call scan_dir() in the JNI,and path = %s \n",path);
	scan_dir(path);
}
void scan_dir(const char *directory)
{
    DIR *dp;
    struct dirent *entry;
    struct stat statbuf;

    if((dp = opendir(directory)) == NULL)
    {
        perror("opendir");
        return;
    }
    chdir(directory);
    //LOGE("pyb chdir directory = %s\n",directory);
	while ((entry = readdir(dp)) != NULL) {
		stat(entry->d_name, &statbuf);
		if (S_ISDIR(statbuf.st_mode)) {
			//printf("name = %s, size = %d\n", entry->d_name, (int)statbuf.st_size);
			if ((strcmp(entry->d_name, ".") != 0)
					&& (strcmp(entry->d_name, "..") != 0)
					&& (entry->d_name[0] != '.')) {
				scan_dir(entry->d_name);
			}
		} else {
	        int size = strlen(entry->d_name);
			if (entry->d_name[0] != '.'
					&& (statbuf.st_size/1024) > 300  //大于300k,表示肯能有mp3文件(忽略 <300k的mp3)
                    && strcmp(entry->d_name + (size - 4), ".mp3") == 0){
				//LOGE("scan_dir(),file st_size = %d \n\n",(statbuf.st_size/1024));
			    char* parentPath = (char*)malloc(1024);
				char* absolutePath = (char*)malloc(1024);
				//首先获取工作路径
                getcwd(parentPath,1024);
                //LOGE("parentPath = %s \n", parentPath);
				strcpy(absolutePath,parentPath);
				char *p = "/";
				absolutePath = strcat(absolutePath,p);
				absolutePath = strcat(absolutePath,entry->d_name);
				//statbuf.st_size,
				LOGE("scan_dir(),file absolutePath = %s \n", absolutePath);
				free(parentPath);
				parentPath = NULL;
				free(absolutePath);
				absolutePath = NULL;
			}
		}
	}
    chdir("..");
    closedir(dp);
}

displayName
//jfieldID disName;
size
//jfieldID trackSize;
ext
//jfieldID extName;
filePath
//jfieldID path;
parentPath
//jfieldID parent;
//
//jobject obj_main;

JNIEXPORT jobjectArray JNICALL Java_com_coder80_scaner_MainActivity_getTracksArray(JNIEnv *env, jobject obj, jstring jdirPath){

	const char *dirPath = (*env)->GetStringUTFChars(env,jdirPath,NULL);

	jclass  objectClass = (*env)->FindClass(env, "com/coder80/scaner/Track_Info");
	jobjectArray jobj_arr= (*env)->NewObjectArray(env,(jsize)ARRAY_LENGTH,objectClass, 0);
	obj_main = obj;
	LOGE("in JNI getTracksArray(),dirPath = %s \n", dirPath);
	//获取Java对象
	/*
	 * 或者 同样可以获取Java对象
	 * jclass objectClass = env->GetObjectClass(jobject);
	 * */
	//displayName
//	disName = (*env)->GetFieldID(env,objectClass,"displayName","Ljava/lang/String;");
    //size
    trackSize = (*env)->GetFieldID(env,objectClass,"size"," L");
//    //ext
//    extName = (*env)->GetFieldID(env,objectClass,"ext","Ljava/lang/String;");
//    //filePath
//    path = (*env)->GetFieldID(env,objectClass,"filePath","Ljava/lang/String;");
//    //parentPath
//    parent = (*env)->GetFieldID(env,objectClass,"parentPath","Ljava/lang/String;");
    i = 0;

	scan_dir3(env,dirPath,jobj_arr);
	return jobj_arr;
}

jmethodID JavaMid = NULL;
 JNIEnv * jniEnvPlaying = NULL;
/*
 * para obj: Track_Info object
 * */
//void scan_dir3(JNIEnv *env,jobject obj,const char *directory,jobjectArray list)
void scan_dir3(JNIEnv *env,const char *directory,jobjectArray list)
{
    DIR *dp;
    struct dirent *entry;
    struct stat statbuf;
    if((dp = opendir(directory)) == NULL)
    {
        perror("opendir");
        return;
    }
    chdir(directory);
    jstring jstr;

    while ((entry = readdir(dp)) != NULL)
    {
        stat(entry->d_name, &statbuf);
        if (S_ISDIR(statbuf.st_mode))
        {
            if ((strcmp(entry->d_name, ".") != 0) &&
                    (strcmp(entry->d_name, "..") != 0) &&
                    (entry->d_name[0] != '.'))
            {
            	scan_dir3(env,entry->d_name,list);
            }
        }
        else
        {
		    int size = strlen(entry->d_name);
            if (entry->d_name[0] != '.'  //隐藏文件
            		&& (statbuf.st_size/1024) > 300  //大于300k,表示肯能有mp3文件(忽略 <300k的mp3)
            		&& strcmp((entry->d_name + (size - 4)) , ".mp3") == 0)
            {
			    char* parentPath = (char*)malloc(1024);
				char* absolutePath = (char*)malloc(1024);
				//首先获取工作路径
                getcwd(parentPath,1024);
                //LOGE("parentPath = %s \n", parentPath);
				strcpy(absolutePath,parentPath);
				char *p = "/";
				absolutePath = strcat(absolutePath,p);
				absolutePath = strcat(absolutePath,entry->d_name);

//				jstring jstrDis = (*env)->NewStringUTF(env, entry->d_name);
//				(*env)->SetObjectField(env, obj, disName, jstrDis);
//
				(*env)->SetLongField(env, obj, trackSize, statbuf.st_size/1024);
//
//				jstring jstrExt = (*env)->NewStringUTF(env,"mp3");
//				(*env)->SetObjectField(env, obj, extName, jstrExt);
//
//				jstring jstrPath = (*env)->NewStringUTF(env,absolutePath);
//				(*env)->SetObjectField(env, obj, path, jstrPath);
//
//				jstring jstrParentPath = (*env)->NewStringUTF(env,absolutePath);
//				(*env)->SetObjectField(env, obj, parent, jstrParentPath);

				jclass  classTrackInfo = (*env)->FindClass(env,"com/coder80/scaner/Track_Info");
				// 获取Track_Info类的构造函数ID
				jmethodID midInit = (*env)->GetMethodID(env,classTrackInfo,"<init>", "()V");
				//构造Track_Info对象
				jobject objTrackInfo = (*env)->NewObject(env,classTrackInfo,midInit);

				/*查找java中的setDisplayName方法的ID,
				 * @para:obj Track_Info类的对象
				 * @para:setDisplayName为Java中的函数名称
				 * @(Ljava/lang/String;)V 表示String类型的参数。返回值V代表void
				 * */
				JavaMid = (*env)->GetMethodID(env,classTrackInfo,"setDisplayName","(Ljava/lang/String;)V");
				if (JavaMid == NULL) {
					LOGE("pyb setDisplayName() fun not found!");
					return;
				}
				jstring jstrDis = (*env)->NewStringUTF(env,entry->d_name);
				//执行setDisplayName方法   jstrDis --> displayName
				(*env)->CallVoidMethod(env,objTrackInfo,JavaMid,jstrDis);
				JavaMid = NULL;

				//************************************************
				//查找java中的setSize方法的ID,J -----> long
				JavaMid = (*env)->GetMethodID(env,classTrackInfo,"setSize","(J)V");
				if (JavaMid == NULL) {
					LOGE("pyb setSize() fun not found!");
					return;
				}
//				jstring jstrDis = (*env)->NewStringUTF(env,entry->d_name);
//				//执行setSize方法   statbuf.st_size --> size
				(*env)->CallVoidMethod(env,objTrackInfo,JavaMid,statbuf.st_size);
				JavaMid = NULL;
				//end

				//查找java中的setExt方法的ID,
				JavaMid = (*env)->GetMethodID(env,classTrackInfo,"setExt", "(Ljava/lang/String;)V");
				if (JavaMid == NULL) {
					LOGE("pyb setExt() fun not found!");
					return;
				}
				jstring jstrExt = (*env)->NewStringUTF(env,"mp3");
				//执行setExt方法   jstrExt --> ext
				(*env)->CallVoidMethod(env,objTrackInfo,JavaMid,jstrExt);
				JavaMid = NULL;

				//查找java中的setFilePath方法的ID,
				JavaMid = (*env)->GetMethodID(env,classTrackInfo, "setFilePath","(Ljava/lang/String;)V");
				if (JavaMid == NULL) {
					LOGE("setFilePath() fun not found!");
					return;
				}
				jstring jstrPath = (*env)->NewStringUTF(env,absolutePath);
				//执行setFilePath方法   jstrPath --> filePath
				(*env)->CallVoidMethod(env,objTrackInfo,JavaMid,jstrPath);
				JavaMid = NULL;

				//查找java中的setParentPath方法的ID,
				JavaMid = (*env)->GetMethodID(env,classTrackInfo, "setParentPath","(Ljava/lang/String;)V");
				if (JavaMid == NULL) {
					LOGE("setParentPath() fun not found!");
					return;
				}
				jstring jstrParentPath = (*env)->NewStringUTF(env,parentPath);
				//执行setParentPath方法   jstrParentPath --> parentPath
				(*env)->CallVoidMethod(env,objTrackInfo,JavaMid,jstrParentPath);
				JavaMid = NULL;


				(*env)->SetObjectArrayElement(env,list,i,objTrackInfo);
//	            LOGE("scan_dir3(),i = %d,file absolutePath = %s \n\n",i, absolutePath);
				i++;

				free(parentPath);
				parentPath = NULL;
				free(absolutePath);
				absolutePath = NULL;
            }
        }
    }
    chdir("..");
    closedir(dp);
}

     在C中调用Java方法,首先需要构建一个Java中类的实例。

    //查找Track_Info类
	jclass  classTrackInfo = (*env)->FindClass(env,"com/coder80/scaner/Track_Info");
	// 获取Track_Info类的构造函数ID
	jmethodID midInit = (*env)->GetMethodID(env,classTrackInfo,"<init>", "()V");
	//构造Track_Info对象
	jobject objTrackInfo = (*env)->NewObject(env,classTrackInfo,midInit);
    GetMethodID函数中参数值"<init>"表示Track_Info类默认的构造函数。之后开始调用setDisplayName()方法.

	JavaMid = (*env)->GetMethodID(env,classTrackInfo,"setDisplayName","(Ljava/lang/String;)V");
	if (JavaMid == NULL) {
	LOGE("pyb setDisplayName() fun not found!");
	    return;
	}
	jstring jstrDis = (*env)->NewStringUTF(env,entry->d_name);
	//执行setDisplayName方法   jstrDis --> displayName
	(*env)->CallVoidMethod(env,objTrackInfo,JavaMid,jstrDis);
	JavaMid = NULL;
        代码段中 GetMethodID(env,classTrackInfo,"setDisplayName","(Ljava/lang/String;)V");

      1."setDisplayName"表示java中的

	public void setDisplayName(String displayName) {
		this.displayName = displayName;
	}
      2."(Ljava/lang/String;)V"中 Ljava/lang/String;表示参数类型:String。V 表示 返回值类型

      调用Java中的setSize函数。

	//查找java中的setSize方法的ID,J -----> long
	JavaMid = (*env)->GetMethodID(env,classTrackInfo,"setSize","(J)V");
	if (JavaMid == NULL) {
		LOGE("pyb setSize() fun not found!");
		return;
	}
//	jstring jstrDis = (*env)->NewStringUTF(env,entry->d_name);
//	//执行setSize方法   statbuf.st_size --> size
	(*env)->CallVoidMethod(env,objTrackInfo,JavaMid,statbuf.st_size);
	JavaMid = NULL;
       代码段中:(*env)->GetMethodID(env,classTrackInfo,"setSize","(J)V"); 参数值"(J)V" J表示 java中的long类型数据。

	public void setSize(long size) {
//		Log.e("Track_Info.java", "setSize() called in JNI, size = " + size);
		this.size = size;
	}
jmethodID GetMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
参数 const char *sig表示方法签名,有特定的格式:(param-type)ret-type,括号内表示该方法传入参数类型,后面的是返回类型
其中param-type和ret-type都是由特定符号组成,各java primitive type都有各自对应符号如下表。

                            

例如,在本例中,需要调用 public native Track_Info[] getTracksArray(String dirPath);JNI_OnLoad函数中可以看到
其signature就是 (Ljava/lang/String;)[Lcom/coder80/scaner/Track_Info;

Ljava/lang/String;表示参数类型

[Lcom/coder80/scaner/Track_Info;表示函数getTracksArray的返回值为对象数组。
另外千万别忘记L fully-qualified-class ; 这个格式后面的那个;号。
主要类代码如下:

package com.coder80.scaner;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
	
	private Button mbtn;
	private Button mbtnJava;
	private long mTime;
	private TextView mTextView;
	private File mSdcardFile;
	private String mFlagJava = "java";
	private String mFlagC = "native";
	private boolean mIsClick;  //防止多次点击
	private Track_Info mTrack;
	private List<Track_Info> mTrackList = new ArrayList<Track_Info>();
	private Track_Info[] mTracks;// = new Track_Info[100];
//	char[] a = new char[100];
	private boolean mIsExit;//sdcard是否存在
	
	private void log_msg(String msg) {
		if(true){
			Log.e(getClass().getSimpleName(), msg);
		}
	}

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mIsClick = false;
        mIsExit = true;
        mTrack = new Track_Info();
        mSdcardFile = Environment.getExternalStorageDirectory();
        Log.e("pyb", " mSdcardFile = " + mSdcardFile.toString());
		if (android.os.Environment.MEDIA_MOUNTED.equals(android.os.Environment
				.getExternalStorageState())) {
			mIsExit = true;
		} else {
			mIsExit = false;
			Toast.makeText(MainActivity.this, "SDCard不存在,请安装!",Toast.LENGTH_LONG).show();
		}
        mbtn = (Button) findViewById(R.id.button1);
        mbtn.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				if (mIsExit) {
					if (!mIsClick){
						mIsClick = true;
						Log.e("pyb", " in the JNI scaner");
						new MyAsyncTask().execute(mFlagC);
					} 
				}else {
					Toast.makeText(MainActivity.this, "SDCard不存在,请安装!",Toast.LENGTH_LONG).show();
				}
			}
		});
        
        mbtnJava = (Button) findViewById(R.id.btn2);
        mbtnJava.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				Log.e("pyb", " in the Java scaner");
				new MyAsyncTask().execute(mFlagJava);
			}
		});
        
        mTextView = (TextView) findViewById(R.id.TextView02);
    }
    
    public void addList(Track_Info track){
    	mTrackList.add(track);
    }
    
    public class MyAsyncTask extends AsyncTask<String, Long, Object> {

		@Override
		protected void onPreExecute() {
			// TODO Auto-generated method stub
			super.onPreExecute();
		}

		@Override
		protected void onPostExecute(Object result) {
			// TODO Auto-generated method stub
			super.onPostExecute(result);
			if(mTime < 1000){
				mTextView.setText("花费时间: " + Long.toString(mTime) + " 毫秒");
			}else{
				mTime = mTime/1000;
				mTextView.setText("花费时间: " + Long.toString(mTime) + " 秒");
			}
			mIsClick = false;
		}

		@Override
		protected Object doInBackground(String... params) {
			// TODO Auto-generated method stub
			String flag = params[0];
			Log.e("pyb", " doInBackground called...flag = " + flag);
			long time1 = System.currentTimeMillis();
//			scanDir(mSdcardFile.toString() + "/音乐");
			if(flag.equals("java")){
				getFiles(mSdcardFile);
			for(int i = 0;i< mTrackList.size();i++){
				Log.e("pyb", "mTrackList[" +i+"] path = " + mTrackList.get(i).getFilePath());
			}
			}else if(flag.equals("native")){
//				scanDir(mSdcardFile.toString());
//    		array = getPathArray(mSdcardFile.toString());
    		mTracks = getTracksArray(mSdcardFile.toString());
    		Log.e("pyb", "mTracks.length = "+ mTracks.length);
    		for (int i = 0; i < mTracks.length; i++) {
    			if (mTracks[i] != null) {
    				Log.e("pyb", "mTracks[" +i+"] path = " + mTracks[i].getFilePath());
    			} else {
    				Log.e("pyb", "mTracks == null");
    				break;
    			}
    		}
			}
			long time2 = System.currentTimeMillis();
			mTime = time2 - time1;
			Log.e("pyb", "in the doInBackground(),scaner mp3 cost time = " + (time2 - time1) );
			return null;
		}
    };

    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
    
    static {
    	Log.e("pyb", " System.loadLibrary() called...");
        System.loadLibrary("scan");
    }
    
	/**
	 * 获取音乐文件列表
	 * @param filePath
	 */
	public void getFiles(File filePath) {
		File[] files = filePath.listFiles();
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				if (files[i].isDirectory() && !files[i].isHidden() && files[i].canRead()) {
					getFiles(files[i]);
				} else {
					// Mp3support
					String displayName = files[i].getName();
					if (displayName.endsWith(".mp3") || displayName.endsWith(".MP3")) {
//						String[] strarray = displayName.split("\\.");
//						String path = files[i].getParentFile().toString();
						
						String fileName = files[i].getName();
						String[] strarray=fileName.split("\\."); 
//						String displayName = strarray[0];
						String ext = strarray[1];
						String filepath = files[i].toString();
						String parentPath = files[i].getParentFile().toString() + "/";
						mTrack = new Track_Info();
						mTrack.setDisplayName(displayName);
						mTrack.setExt(ext);
						mTrack.setFilePath(filepath);
						mTrack.setParentPath(parentPath);
						mTrackList.add(mTrack);
					}
				}
			}
		}
	}
    public native void scanDir(String dirPath);
    public native String[] getPathArray(String dirPath);  
    public native Track_Info[] getTracksArray(String dirPath); 
}

       通过本例,可以了解JNI使用方法,对稍微复杂的JNI编程,例如从native层调用Java层对象的讲解,是有一定的帮助的。

      demo代码已经上传到博客资源中!



评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值