当我按一下按钮空指针异常引发 [英] nullpointer exception raises when i click on the button

查看:103
本文介绍了当我按一下按钮空指针异常引发的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我点击CheckData 按钮在Android上,它抛出一个空指针异常

SaveData.java

 公共类保存数据扩展活动实现OnClickListener {
    静态最终诠释DIALOG_ID = 0;

    私人乌里mImageCaptureUri;
    私人ImageView的mImageView;
    公共静态类证书{
    私人BMP位图;

        公证书(位图B){
        BMP = B;
        }
        公共位图getBitmap(){返回BMP; }
    }

    私有静态最终诠释PICK_FROM_CAMERA = 1;
    私有静态最终诠释PICK_FROM_FILE = 2;

    @覆盖
        保护无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        的setContentView(R.layout.addname);

        查看button1Click = findViewById(R.id.btn_choose);
        button1Click.setOnClickListener(本);
        查看button2Click = findViewById(R.id.Button01add);
        button2Click.setOnClickListener(本);
        查看button3Click = findViewById(R.id.Button01check);
        button3Click.setOnClickListener(本);
    }

        公共无效的onClick(视图v){
        开关(v.getId()){

        案例R.id.Button01add:
            的ShowDialog(DIALOG_ID);
            打破;

        案例R.id.Button01check:
            startActivity(新意图(SaveData.this,CheckData.class));
            打破;
        }

//从相机或画廊挑选图片
            最终的String []项目=新的String [] {来自相机,从SD卡};
            ArrayAdapter<字符串>适配器=新的ArrayAdapter<字符串> (这一点,android.R.layout.select_dialog_item,项目);
            AlertDialog.Builder建设者=新AlertDialog.Builder(本);

            builder.setTitle(选择图片);
            builder.setAdapter(适配器,新DialogInterface.OnClickListener(){
                公共无效的onClick(DialogInterface对话,诠释项){
                    如果(项目== 0){
                        意向意图=新的意图(MediaStore.ACTION_IM​​AGE_CAPTURE);
                        档案文件=新的文件(Environment.getExternalStorageDirectory(),tmp_avatar_+将String.valueOf(System.currentTimeMillis的())+.JPG);
                        mImageCaptureUri = Uri.fromFile(文件);

                        尝试 {
                            intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT,mImageCaptureUri);
                            intent.putExtra(回归数据,真正的);
                            startActivityForResult(意向,PICK_FROM_CAMERA);
                    }赶上(例外五){
                            e.printStackTrace();
                        }

                        dialog.cancel();
                    } 其他 {
                        意向意图=新的意图();

                        intent.setType(图像/ *);
                        intent.setAction(Intent.ACTION_GET_CONTENT);

                        startActivityForResult(Intent.createChooser(原意,用完整的行动),PICK_FROM_FILE);
                    }
                }
            });

            最后AlertDialog对话框= builder.create();

            mImageView =(ImageView的)findViewById(R.id.image1);

            ((按钮)findViewById(R.id.btn_choose))。setOnClickListener(新View.OnClickListener(){
                @覆盖
                公共无效的onClick(视图v){
                    dialog.show();
                }
            });
        }

        @覆盖
        保护无效onActivityResult(INT申请code,INT结果code,意图数据){
            如果(结果code = RESULT_OK!)回报;

            点阵位图= NULL;
            字符串路径=;

            如果(要求code == PICK_FROM_FILE){
                mImageCaptureUri = data.getData();
                PATH = getRealPathFromURI(mImageCaptureUri); //从图库

                如果(路径== NULL)
                    PATH = mImageCaptureUri.getPath(); //从文件管理器

                如果(路径!= NULL)
                    位= BitmapFactory.de codeFILE(路径);
            } 其他 {
                PATH = mImageCaptureUri.getPath();
                位= BitmapFactory.de codeFILE(路径);
            }

            mImageView.setImageBitmap(位);
        }

        公共字符串getRealPathFromURI(URI contentUri){
            的String []凸出= {MediaStore.Images.Media.DATA};
            光标光标= managedQuery(contentUri,凸出,NULL,NULL,NULL);

            如果(光标== NULL)返回NULL;

            INT与Column_Index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);

            cursor.moveToFirst();

            返回cursor.getString(Column_Index中);
        }

    受保护的最后一个对话框onCreateDialog(最终诠释的id){
        对话对话框= NULL;
        开关(ID){
        案例DIALOG_ID:
            AlertDialog.Builder建设者=新AlertDialog.Builder(本);
            builder.setMessage(信息保存成功!添加其他信息?)
            .setCancelable(假)
            .setPositiveButton(否,新DialogInterface.OnClickListener(){
                公共无效的onClick(DialogInterface对话框,INT ID){

                    SaveData.this.finish();
              }
            })
        .setNegativeButton(是,新DialogInterface.OnClickListener(){
                公共无效的onClick(DialogInterface对话框,INT ID){
                    dialog.cancel();
                }
            });
            AlertDialog警报= builder.create();
            对话框=警报;
            打破;
        默认:
        }
        返回对话框;
    }
//菜单选项
    @覆盖
    公共布尔onCreateOptionsMenu(功能菜单){
        MenuInflater充气= getMenuInflater();
        inflater.inflate(R.layout.mymenu,菜单);
        返回true;
    }
    @覆盖
    公共布尔onOptionsItemSelected(菜单项项){
        如果(item.getItemId()== R.id.item1){
            Log.d(选项,保存选项点击);
        }
        如果(item.getItemId()== R.id.item2){
            Log.d(机器,删除选项点击);
        }
        如果(item.getItemId()== R.id.item3){
            Log.d(选项,退出选项点击);
        }
        返回super.onOptionsItemSelected(项目);
    }
}
 


DataManipulator.java:

 公共类DataManipulator {
公共静态最后弦乐KEY_IMG =形象;

 私人DatabaseHelper mDbHelper;
    私人SQLiteDatabase MDB;

    私有静态最后弦乐DATABASE_NAME =DBtest;
    私有静态最终诠释DATABASE_VERSION = 1;

    私有静态最后弦乐CERTIFICATES_TABLE =证书;

    私有静态最后弦乐CREATE_CERTIFICATES_TABLE =创建表+ CERTIFICATES_TABLE +(+ KEY_IMG +BLOB NOT NULL);

    私人最终语境mCtx;
    私有静态类DatabaseHelper扩展SQLiteOpenHelper {
        DatabaseHelper(上下文的背景下){
            超(背景下,DATABASE_NAME,空,DATABASE_VERSION);
        }

        公共无效的onCreate(SQLiteDatabase DB){
            db.execSQL(CREATE_CERTIFICATES_TABLE);
        }

        公共无效onUpgrade(SQLiteDatabase分贝,INT oldVersion,诠释静态网页){
            db.execSQL(DROP TABLE IF EXISTS+ CERTIFICATES_TABLE);
            的onCreate(DB);
        }
    }
    公共无效复位(){mDbHelper.onUpgrade(this.mDb,1,1); }

    公共DataManipulator(上下文CTX){
        mCtx = CTX;
        mDbHelper =新DatabaseHelper(mCtx);
    }

    公共DataManipulator的open()抛出的SQLException {
        MDB = mDbHelper.getWritableDatabase();
        回到这一点;
    }

    公共无效的close(){mDbHelper.close(); }

    公共无效createCertificatesEntry(证书证书){
        ByteArrayOutputStream OUT =新ByteArrayOutputStream();
        certificates.getBitmap()的COM preSS(Bitmap.Com pressFormat.PNG,100,出去)。
        ContentValues​​ CV =新ContentValues​​();
        cv.put(KEY_IMG,out.toByteArray());
        mDb.insert(CERTIFICATES_TABLE,空,CV);
    }
    公证书getFirstCertificatesFromDB()抛出的SQLException {
        光标CUR = mDb.query(真,CERTIFICATES_TABLE,新的String [] {} KEY_IMG,NULL,NULL,NULL,NULL,NULL,NULL);
        如果(cur.moveToFirst()){
            byte []的BLOB = cur.getBlob(cur.getColumnIndex(KEY_IMG));
            ByteArrayInputStream进行的InputStream =新ByteArrayInputStream的(BLOB);
            点阵位图= BitmapFactory.de codeStream(InputStream的);
            cur.close();
            返回新的证书(位);
        }
        cur.close();
        返回null;
    }
}
 

DataManipulator.java:60 的是 certificates.getBitmap()的COM preSS(Bitmap.Com pressFormat.PNG,100,出);


CheckData.java:

 公共类CheckData扩展ListActivity {
    TextView的选择;
    DataManipulator DM;
    私人DataManipulator DataManipulator;

        保护无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        的setContentView(R.layout.check);
        DM =新DataManipulator(本);

        的LinearLayout布局=新的LinearLayout(本);
        ImageView的形象=新ImageView的(这一点);
        DataManipulator =新DataManipulator(本);

        证书testCertificates =新证书(BitmapFactory.de codeFILE(Context.STORAG​​E_SERVICE));

        DataManipulator.open();
        DataManipulator.createCertificatesEntry((证书)testCertificates);
        DataManipulator.close();

        testCertificates = NULL;

        DataManipulator.open();
        testCertificates = DataManipulator.getFirstCertificatesFromDB();
        DataManipulator.close();

        image.setImageBitmap(((证书)testCertificates).getBitmap());

        的setContentView(布局);
    }
}
 

CheckData.java:29 的是 DataManipulator.createCertificatesEntry((证书)testCertificates);


logcat的错误:

 了java.lang.RuntimeException:无法启动活动ComponentInfo {list.certificates / list.certificates.CheckData}:显示java.lang.NullPointerException
在android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1955)
在android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1980)
在android.app.ActivityThread.access $ 600(ActivityThread.java:122)
在android.app.ActivityThread $ H.handleMessage(ActivityThread.java:1146)
在android.os.Handler.dispatchMessage(Handler.java:99)
在android.os.Looper.loop(Looper.java:137)
在android.app.ActivityThread.main(ActivityThread.java:4340)
在java.lang.reflect.Method.invokeNative(本机方法)
在java.lang.reflect.Method.invoke(Method.java:511)
在com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:784)
在com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
在dalvik.system.NativeStart.main(本机方法)
显示java.lang.NullPointerException:产生的原因
在list.certificates.DataManipulator.createCertificatesEntry(DataManipulator.java:60)
在list.certificates.CheckData.onCreate(CheckData.java:29)
在android.app.Activity.performCreate(Activity.java:4465)
在android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
在android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1919)
 

解决方案

您正在从空certificates.getBitmap()因为没有一个位图存在。在您的初始分配,你试图去一个不存在codeA文件。使用 Context.STORAG​​E_SERVICE 不工作的方式,它只是一个字符串,相当于存储

例如$ C $的如何正确使用存储管理器C,请尝试<一href="http://www.devdaily.com/java/jwarehouse/android/core/java/android/os/storage/StorageManager.java.shtml">this.

如果你只是试图去codeA资源(绘制,原材料等)使用的去code另一种形式:

 的InputStream是= context.getResources()openRawResource(渣油)。
点阵位图= BitmapFactory.de codeStream(是);
 


更新: 假设我知道它是什么,你想要做的,问题是,你切换到 CheckData 的活动,而没有经过选择位图以任何方式。该活动有没有办法知道用什么样的位图,而 Context.STORAG​​E_SERVICE 不是一个位图以任何方式,这样你就可以在c它不是去$ C $

有关如何将位图传递到新的活动很好的答案,看看这里

总之,你可以在 mBitmap 变量添加到保存数据,并沿<$ C $传递C> CheckData

对于这一点,改变 SaveData.onClick的code()来:

 案例R.id.Button01check:
    意向意图=新的意图(SaveData.this,CheckData.class);
    intent.putExtra(位图数据,mBitmap)
    startActivity(意向);
    打破;
 

和,在 onActivityResult(),在最后补充一点:

  mBitmap =位图;
 

CheckData.onCreate()

  BMP位= getIntent()getExtras()获得(位图数据)。;
如果(BMP!= NULL){
    证书testCertificates =新证书(BMP);
} 其他 {
    //回到了正常//
}
 

When I click on CheckData button on android, it is throwing a nullpointer exception.

SaveData.java

public class SaveData extends Activity implements OnClickListener {     
    static final int DIALOG_ID = 0;

    private Uri mImageCaptureUri;
    private ImageView mImageView;   
    public static class Certificates {
    private Bitmap bmp;

        public Certificates(Bitmap b) {
        bmp = b;
        }
        public Bitmap getBitmap() { return bmp; }
    }

    private static final int PICK_FROM_CAMERA = 1;
    private static final int PICK_FROM_FILE = 2;

    @Override
        protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.addname);

        View button1Click=findViewById(R.id.btn_choose);
        button1Click.setOnClickListener(this);
        View button2Click = findViewById(R.id.Button01add);
        button2Click.setOnClickListener(this);
        View button3Click = findViewById(R.id.Button01check);
        button3Click.setOnClickListener(this);  
    }

        public void onClick(View v){        
        switch(v.getId()){

        case R.id.Button01add:
            showDialog(DIALOG_ID);
            break;

        case R.id.Button01check:
            startActivity(new Intent (SaveData.this,CheckData.class));
            break;
        }

// picking an image from camera or gallery         
            final String [] items           = new String [] {"From Camera", "From SD Card"};                
            ArrayAdapter<String> adapter    = new ArrayAdapter<String> (this, android.R.layout.select_dialog_item,items);
            AlertDialog.Builder builder     = new AlertDialog.Builder(this);

            builder.setTitle("Select Image");
            builder.setAdapter( adapter, new DialogInterface.OnClickListener() {
                public void onClick( DialogInterface dialog, int item ) {
                    if (item == 0) {
                        Intent intent    = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                        File file        = new File(Environment.getExternalStorageDirectory(), "tmp_avatar_" + String.valueOf(System.currentTimeMillis()) + ".jpg");
                        mImageCaptureUri = Uri.fromFile(file);

                        try {           
                            intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, mImageCaptureUri);
                            intent.putExtra("return-data", true);                           
                            startActivityForResult(intent, PICK_FROM_CAMERA);
                    }   catch (Exception e) {
                            e.printStackTrace();
                        }           

                        dialog.cancel();
                    } else {
                        Intent intent = new Intent();

                        intent.setType("image/*");
                        intent.setAction(Intent.ACTION_GET_CONTENT);

                        startActivityForResult(Intent.createChooser(intent, "Complete action using"), PICK_FROM_FILE);
                    }
                }
            } );

            final AlertDialog dialog = builder.create();

            mImageView = (ImageView) findViewById(R.id.image1);

            ((Button) findViewById(R.id.btn_choose)).setOnClickListener(new View.OnClickListener() {            
                @Override
                public void onClick(View v) {
                    dialog.show();
                }
            });
        }

        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (resultCode != RESULT_OK) return;

            Bitmap bitmap   = null;
            String path     = "";

            if (requestCode == PICK_FROM_FILE) {
                mImageCaptureUri = data.getData(); 
                path = getRealPathFromURI(mImageCaptureUri); //from Gallery 

                if (path == null)
                    path = mImageCaptureUri.getPath(); //from File Manager

                if (path != null) 
                    bitmap  = BitmapFactory.decodeFile(path);
            } else {
                path    = mImageCaptureUri.getPath();
                bitmap  = BitmapFactory.decodeFile(path);
            }

            mImageView.setImageBitmap(bitmap);      
        }

        public String getRealPathFromURI(Uri contentUri) {
            String [] proj      = {MediaStore.Images.Media.DATA};
            Cursor cursor       = managedQuery( contentUri, proj, null, null,null);

            if (cursor == null) return null;

            int column_index    = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);

            cursor.moveToFirst();

            return cursor.getString(column_index);
        }

    protected final Dialog onCreateDialog(final int id) {
        Dialog dialog = null;
        switch(id) {
        case DIALOG_ID:
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("Information saved successfully ! Add Another Info?")
            .setCancelable(false)
            .setPositiveButton("No", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {

                    SaveData.this.finish();
              }
            })
        .setNegativeButton("Yes", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    dialog.cancel();
                }
            });
            AlertDialog alert = builder.create(); 
            dialog = alert;
            break;
        default:
        }
        return dialog;
    }
// menu option  
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.layout.mymenu, menu);        
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(item.getItemId() == R.id.item1) {
            Log.d("Option", "Save option is clicked");           
        }
        if(item.getItemId() == R.id.item2) {
            Log.d("Option", "Delete option is clicked");
        }
        if(item.getItemId() == R.id.item3) {
            Log.d("Option", "Exit option is clicked");
        }       
        return super.onOptionsItemSelected(item);       
    }
}


DataManipulator.java:

public class DataManipulator {
public static final String KEY_IMG = "image";

 private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;

    private static final String DATABASE_NAME = "DBtest";
    private static final int DATABASE_VERSION = 1;

    private static final String CERTIFICATES_TABLE = "certificates";

    private static final String CREATE_CERTIFICATES_TABLE = "create table "+CERTIFICATES_TABLE+" (" +KEY_IMG+" blob not null) ";

    private final Context mCtx;
    private static class DatabaseHelper extends SQLiteOpenHelper {
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        public void onCreate(SQLiteDatabase db) {
            db.execSQL(CREATE_CERTIFICATES_TABLE);
        }

        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS "+CERTIFICATES_TABLE);
            onCreate(db);
        }
    }
    public void Reset() { mDbHelper.onUpgrade(this.mDb, 1, 1); }

    public DataManipulator(Context ctx) {
        mCtx = ctx;
        mDbHelper = new DatabaseHelper(mCtx);
    }

    public DataManipulator open() throws SQLException {
        mDb = mDbHelper.getWritableDatabase();
        return this;
    }

    public void close() { mDbHelper.close(); }

    public void createCertificatesEntry(Certificates certificates) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        certificates.getBitmap().compress(Bitmap.CompressFormat.PNG, 100, out);
        ContentValues cv = new ContentValues();
        cv.put(KEY_IMG, out.toByteArray());
        mDb.insert(CERTIFICATES_TABLE,  null, cv);
    }
    public Certificates getFirstCertificatesFromDB() throws SQLException {
        Cursor cur = mDb.query(true, CERTIFICATES_TABLE,  new String[] {KEY_IMG}, null, null, null, null, null, null);
        if(cur.moveToFirst()) {
            byte[] blob = cur.getBlob(cur.getColumnIndex(KEY_IMG));
            ByteArrayInputStream inputStream = new ByteArrayInputStream(blob);
            Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
            cur.close();
            return new Certificates(bitmap);
        }
        cur.close();
        return null;
    }    
}

DataManipulator.java:60 is certificates.getBitmap().compress(Bitmap.CompressFormat.PNG, 100, out);


CheckData.java:

    public class CheckData extends ListActivity  {     
    TextView selection;
    DataManipulator dm;
    private DataManipulator DataManipulator;

        protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.check);
        dm = new DataManipulator(this);

        LinearLayout layout = new LinearLayout(this);
        ImageView image = new ImageView(this);
        DataManipulator = new DataManipulator(this);

        Certificates testCertificates = new Certificates(BitmapFactory.decodeFile(Context.STORAGE_SERVICE));

        DataManipulator.open();
        DataManipulator.createCertificatesEntry( (Certificates) testCertificates);
        DataManipulator.close(); 

        testCertificates = null;

        DataManipulator.open();
        testCertificates = DataManipulator.getFirstCertificatesFromDB();
        DataManipulator.close();

        image.setImageBitmap(((Certificates) testCertificates).getBitmap());

        setContentView(layout);
    }
}

CheckData.java:29 is DataManipulator.createCertificatesEntry( (Certificates) testCertificates);


Logcat error:

java.lang.RuntimeException: Unable to start activity ComponentInfo{list.certificates/list.certificates.CheckData}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1955)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1980)
at android.app.ActivityThread.access$600(ActivityThread.java:122)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1146)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4340)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at list.certificates.DataManipulator.createCertificatesEntry(DataManipulator.java:60)
at list.certificates.CheckData.onCreate(CheckData.java:29)
at android.app.Activity.performCreate(Activity.java:4465)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1919)

解决方案

You're getting a null from certificates.getBitmap() because there's not a bitmap there. In your initial assignment, you're trying to decode a file that doesn't exist. Using Context.STORAGE_SERVICE doesn't work that way, it's just a string equaling storage.

For example code of how to use a storage manager properly, try this.

If you're just trying to decode a resource(drawable, raw, etc) use another form of decode:

InputStream is = context.getResources().openRawResource(resID);
Bitmap bitmap = BitmapFactory.decodeStream(is);


Update: Assuming I know what it is you're trying to do, the problem is that you're switching to the CheckData activity without passing the selected bitmap in any way. The activity has no way of knowing what bitmap to use, and Context.STORAGE_SERVICE is not a bitmap in any way, so you can't decode it.

For good answers on how to pass the bitmap to the new activity, look here.

In short, you could add an mBitmap variable to SaveData, and pass it along to CheckData.

For this, change the code in SaveData.onClick() to:

case R.id.Button01check:
    Intent intent = new Intent (SaveData.this,CheckData.class);
    intent.putExtra("bitmapData", mBitmap)
    startActivity(intent);
    break; 

And, in onActivityResult(), add this at the end:

mBitmap = bitmap;

In CheckData.onCreate():

Bitmap bmp = getIntent().getExtras().get("bitmapData");
if(bmp != null) {
    Certificates testCertificates = new Certificates(bmp);
} else {
    // Back out gracefully //
}

这篇关于当我按一下按钮空指针异常引发的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆