Android "فقط السلسلة الأصلية التي أنشأت تسلسل هرمي لواجهة العرض يمكنها لمس مشاهداتها".




multithreading (11)

لقد بنيت مشغل موسيقى بسيطًا في Android. يحتوي العرض لكل أغنية على SeekBar ، مطبق كالتالي:

public class Song extends Activity implements OnClickListener,Runnable {
    private SeekBar progress;
    private MediaPlayer mp;

    // ...

    private ServiceConnection onService = new ServiceConnection() {
          public void onServiceConnected(ComponentName className,
            IBinder rawBinder) {
              appService = ((MPService.LocalBinder)rawBinder).getService(); // service that handles the MediaPlayer
              progress.setVisibility(SeekBar.VISIBLE);
              progress.setProgress(0);
              mp = appService.getMP();
              appService.playSong(title);
              progress.setMax(mp.getDuration());
              new Thread(Song.this).start();
          }
          public void onServiceDisconnected(ComponentName classname) {
              appService = null;
          }
    };

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.song);

        // ...

        progress = (SeekBar) findViewById(R.id.progress);

        // ...
    }

    public void run() {
    int pos = 0;
    int total = mp.getDuration();
    while (mp != null && pos<total) {
        try {
            Thread.sleep(1000);
            pos = appService.getSongPosition();
        } catch (InterruptedException e) {
            return;
        } catch (Exception e) {
            return;
        }
        progress.setProgress(pos);
    }
}

هذا يعمل بشكل جيد. الآن أريد جهاز توقيت عد ثواني / دقيقة من تقدم الأغنية. لذلك أنا وضعت TextView في التخطيط ، والحصول عليها مع findViewById() في onCreate() ، ووضع هذا في run() بعد progress.setProgress(pos) :

String time = String.format("%d:%d",
            TimeUnit.MILLISECONDS.toMinutes(pos),
            TimeUnit.MILLISECONDS.toSeconds(pos),
            TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(
                    pos))
            );
currentTime.setText(time);  // currentTime = (TextView) findViewById(R.id.current_time);

لكن هذا السطر الأخير يعطيني الاستثناء:

android.view.ViewRoot $ CalledFromWrongThreadException: فقط مؤشر الترابط الأصلي الذي أنشأ التسلسل الهرمي لطريقة العرض يمكنه لمس طرق العرض الخاصة به.

ومع ذلك فأنا أعمل في الأساس نفس الشيء هنا كما أفعل مع SeekBar - خلق الرأي في onCreate ، ثم لمسه في run() - ولا يعطيني هذه الشكوى.


أرى أنك قبلت إجابة @ Providence. فقط في حالة ، يمكنك أيضا استخدام المعالج أيضا! أولا ، القيام الحقول int.

    private static final int SHOW_LOG = 1;
    private static final int HIDE_LOG = 0;

بعد ذلك ، قم بإنشاء مثيل معالج كحقل.

    //TODO __________[ Handler ]__________
    @SuppressLint("HandlerLeak")
    protected Handler handler = new Handler()
    {
        @Override
        public void handleMessage(Message msg)
        {
            // Put code here...

            // Set a switch statement to toggle it on or off.
            switch(msg.what)
            {
            case SHOW_LOG:
            {
                ads.setVisibility(View.VISIBLE);
                break;
            }
            case HIDE_LOG:
            {
                ads.setVisibility(View.GONE);
                break;
            }
            }
        }
    };

اصنع طريقة.

//TODO __________[ Callbacks ]__________
@Override
public void showHandler(boolean show)
{
    handler.sendEmptyMessage(show ? SHOW_LOG : HIDE_LOG);
}

أخيرا ، ضع هذا في onCreate() الطريقة.

showHandler(true);

أنا استخدم Handler مع Looper.getMainLooper() . انها عملت بشكل جيد بالنسبة لي.

    Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
              // Any UI task, example
              textView.setText("your text");
        }
    };
    handler.sendEmptyMessage(1);

إذا كنت لا ترغب في استخدام runOnUiThread API ، يمكنك في الواقع تطبيق AsynTask للعمليات التي تستغرق بعض الثواني لإكمالها. ولكن في هذه الحالة ، أيضًا بعد معالجة عملك في doinBackground() ، تحتاج إلى إعادة العرض النهائي في onPostExecute() . يسمح تطبيق Android لمؤشر واجهة المستخدم الرئيسي فقط بالتفاعل مع طرق العرض.


استخدم هذا الكود ، ولا حاجة إلى runOnUiThread الدالة runOnUiThread :

private Handler handler;
private Runnable handlerTask;

void StartTimer(){
    handler = new Handler();   
    handlerTask = new Runnable()
    {
        @Override 
        public void run() { 
            // do something  
            textView.setText("some text");
            handler.postDelayed(handlerTask, 1000);    
        }
    };
    handlerTask.run();
}

حلا لهذا:

private void setText(final TextView text,final String value){
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            text.setText(value);
        }
    });
}

استدعاء هذا الأسلوب على مؤشر ترابط الخلفية.


عادة ، يجب أن يتم أي إجراء يتضمن واجهة المستخدم في الموضوع الرئيسي أو مؤشر ترابط واجهة المستخدم ، وهو الذي يتم فيه تنفيذ onCreate() ومعالجة الحدث. إحدى الطرق للتأكد من ذلك هو استخدام Activity.runOnUiThread ، يستخدم آخر معالجات.

ProgressBar.setProgress() على آلية سيتم تنفيذها دائمًا على الخيط الرئيسي ، ولهذا السبب نجحت.

انظر خيوط غير مؤلم .


كان لدي مشكلة مماثلة ، وكان الحل قبيحًا ، ولكنه يعمل:

void showCode() {
    hideRegisterMessage(); // Hides view 
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            showRegisterMessage(); // Shows view
        }
    }, 3000); // After 3 seconds
}

كنت أعمل مع فصل دراسي لا يحتوي على إشارة إلى السياق. لذلك لم يكن من الممكن بالنسبة لي استخدام runOnUIThread(); اعتدت view.post(); وقد تم حلها.

timer.scheduleAtFixedRate(new TimerTask() {

    @Override
    public void run() {
        final int currentPosition = mediaPlayer.getCurrentPosition();
        audioMessage.seekBar.setProgress(currentPosition / 1000);
        audioMessage.tvPlayDuration.post(new Runnable() {
            @Override
            public void run() {
                audioMessage.tvPlayDuration.setText(ChatDateTimeFormatter.getDuration(currentPosition));
            }
        });
    }
}, 0, 1000);

لقد كنت في هذا الموقف ، لكنني وجدت حلا مع كائن معالج.

في حالتي ، أريد تحديث ProgressDialog بنمط المراقب . طريقة العرض الخاصة بي تطبق مراقب وتتجاوز أسلوب التحديث.

لذلك ، إنشاء مؤشر الترابط الرئيسي الخاص بي طريقة العرض مؤشر ترابط آخر استدعاء أسلوب التحديث الذي تحديث ProgressDialop و ....:

يمكن فقط لمؤشر الترابط الأصلي الذي أنشأ تسلسلًا هرميًا للعرض لمس طرق العرض الخاصة به.

من الممكن حل المشكلة باستخدام كائن معالج.

أدناه ، أجزاء مختلفة من رمز بلدي:

public class ViewExecution extends Activity implements Observer{

    static final int PROGRESS_DIALOG = 0;
    ProgressDialog progressDialog;
    int currentNumber;

    public void onCreate(Bundle savedInstanceState) {

        currentNumber = 0;
        final Button launchPolicyButton =  ((Button) this.findViewById(R.id.launchButton));
        launchPolicyButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                showDialog(PROGRESS_DIALOG);
            }
        });
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        switch(id) {
        case PROGRESS_DIALOG:
            progressDialog = new ProgressDialog(this);
            progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            progressDialog.setMessage("Loading");
            progressDialog.setCancelable(true);
            return progressDialog;
        default:
            return null;
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
        switch(id) {
        case PROGRESS_DIALOG:
            progressDialog.setProgress(0);
        }

    }

    // Define the Handler that receives messages from the thread and update the progress
    final Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            int current = msg.arg1;
            progressDialog.setProgress(current);
            if (current >= 100){
                removeDialog (PROGRESS_DIALOG);
            }
        }
    };

    // The method called by the observer (the second thread)
    @Override
    public void update(Observable obs, Object arg1) {

        Message msg = handler.obtainMessage();
        msg.arg1 = ++currentPluginNumber;
        handler.sendMessage(msg);
    }
}

يمكن العثور على هذا التفسير في هذه الصفحة ، ويجب عليك قراءة "مثال ProgressDialog مع مؤشر ترابط آخر".


هذا هو تتبع مكدس الاستثناء المذكورة

        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6149)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:843)
        at android.view.View.requestLayout(View.java:16474)
        at android.view.View.requestLayout(View.java:16474)
        at android.view.View.requestLayout(View.java:16474)
        at android.view.View.requestLayout(View.java:16474)
        at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:352)
        at android.view.View.requestLayout(View.java:16474)
        at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:352)
        at android.view.View.setFlags(View.java:8938)
        at android.view.View.setVisibility(View.java:6066)

حتى إذا ذهبت وحفر ثم تعرف

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

حيث يتم تهيئة mThread في منشئ مثل أدناه

mThread = Thread.currentThread();

كل ما أقصد قوله أنه عندما أنشأنا عرضًا معينًا ، أنشأناه على مؤشر ترابط واجهة المستخدم وحاولنا في وقت لاحق التعديل في مؤشر ترابط العاملين.

يمكننا التحقق من ذلك عبر مقتطف الشفرة أدناه

Thread.currentThread().getName()

عند تضخيم التنسيق ولاحقًا حيث تحصل على استثناء.


يجب عليك نقل جزء من مهمة الخلفية التي تقوم بتحديث واجهة المستخدم على مؤشر الترابط الرئيسي. هناك جزء بسيط من التعليمات البرمجية لهذا:

runOnUiThread(new Runnable() {

    @Override
    public void run() {

        // Stuff that updates the UI

    }
});

وثائق عن Activity.runOnUiThread .

فقط قم بتعشيش هذا داخل الطريقة التي تعمل في الخلفية ، ثم قم بنسخ التعليمة البرمجية التي تقوم بتنفيذ أي تحديثات في منتصف المقطع. تضمين أصغر مقدار ممكن من التعليمات البرمجية ، وإلا فإنك تبدأ في هزيمة الغرض من مؤشر ترابط الخلفية.





multithreading